From f94fc8bbc201672c669b84c221e7a91f06b22009 Mon Sep 17 00:00:00 2001 From: Sourav Das Date: Sun, 12 Jul 2026 15:37:43 +0530 Subject: [PATCH 1/7] chore: fixed installer to support ubuntu and AMD rocm --- .github/workflows/ci.yml | 15 + .gitignore | 7 + docs/accelerator-installation.md | 21 + docs/runtime-support-matrix.md | 12 + install.ps1 | 39 + install.sh | 61 ++ modiff/compatibility/__init__.py | 1 + modiff/compatibility/accelerators.v1.json | 55 ++ modiff/install.py | 720 ++++++++++++++++++ modiff/runtime_profile.py | 136 ++++ modiff/setup_catalog.py | 103 +++ requirements/profiles/amd-pytorch-windows.txt | 2 + requirements/profiles/amd-rocm-linux.txt | 5 + requirements/profiles/apple-mps.txt | 4 + requirements/profiles/cpu.txt | 4 + requirements/profiles/nvidia-cuda.txt | 4 + run.ps1 | 16 + scripts/lock_accelerator_wheels.py | 42 + tests/test_accelerator_manifest.py | 18 + tests/test_install_detection.py | 55 ++ tests/test_install_guidance.py | 103 +++ 21 files changed, 1423 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 docs/accelerator-installation.md create mode 100644 docs/runtime-support-matrix.md create mode 100644 install.ps1 create mode 100755 install.sh create mode 100644 modiff/compatibility/__init__.py create mode 100644 modiff/compatibility/accelerators.v1.json create mode 100644 modiff/install.py create mode 100644 modiff/runtime_profile.py create mode 100644 modiff/setup_catalog.py create mode 100644 requirements/profiles/amd-pytorch-windows.txt create mode 100644 requirements/profiles/amd-rocm-linux.txt create mode 100644 requirements/profiles/apple-mps.txt create mode 100644 requirements/profiles/cpu.txt create mode 100644 requirements/profiles/nvidia-cuda.txt create mode 100644 run.ps1 create mode 100644 scripts/lock_accelerator_wheels.py create mode 100644 tests/test_accelerator_manifest.py create mode 100644 tests/test_install_detection.py create mode 100644 tests/test_install_guidance.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..16f56cb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,15 @@ +name: CI +on: [push, pull_request] +jobs: + backend: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-14] + python: ['3.12'] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: { python-version: '${{ matrix.python }}' } + - run: python -m unittest tests.test_accelerator_manifest tests.test_install_detection tests.test_install_guidance tests.test_hardware + - run: python -m modiff.install --accelerator cpu --dry-run --json diff --git a/.gitignore b/.gitignore index d5b7a7c..9424656 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ config.ini artifacts/ .pytest_cache/ .ruff_cache/ +*.egg-info/ + +# Installer-managed downloads, toolchains, journals, and diagnostics. +.modiff/ custom/* !custom/.gitkeep @@ -30,6 +34,9 @@ web/user/* !web/user/ExampleField.js .venv +.venv.next/ +.venv.previous/ +.venv.failed/ venv .cursorrules __pycache__ diff --git a/docs/accelerator-installation.md b/docs/accelerator-installation.md new file mode 100644 index 0000000..a166b45 --- /dev/null +++ b/docs/accelerator-installation.md @@ -0,0 +1,21 @@ +# Accelerator installation + +MoDiff owns Python/Torch profile selection. The browser shows the same setup checklist but never installs drivers or mutates Python. + +Run `./install.sh` on Linux/macOS or `.\install.ps1` on Windows. The guided installer explains every action before it runs, installs hash-verified app-local uv/Python 3.12, stages the backend, validates a real device tensor, and preserves the previous environment for rollback. When a sibling client is present and the build is not skipped, it downloads the verified Node 24 toolchain on demand before installing and building that client. Hybrid NVIDIA/AMD machines must explicitly choose a profile. + +Useful modes: + +- `--dry-run` or `--system-check`: explain the plan without changing anything. +- `--resume`: continue from the external `.modiff/install-state.json` journal. +- `--repair`: rebuild and validate the managed environment. +- `--non-interactive`: never invoke sudo/UAC; return structured required actions. +- `--backend-only`: skip sibling client installation and Node provisioning. +- `--guide`: print help for every stable setup error code. +- `--json`: return the same phases and structured steps for automation. + +Profiles are `nvidia-cuda`, `amd-rocm-linux`, `amd-pytorch-windows`, `apple-mps`, and `cpu`. Strix Halo on Ubuntu 24.04.3 uses the AMD ROCm 7.2/PyTorch 2.9.1 profile. Ubuntu 26.04 is experimental and requires `--allow-experimental`; non-interactive Auto otherwise selects CPU. WSL and unqualified accelerators fall back to CPU. + +Missing `video`/`render` membership, `/dev/kfd`, DRM render nodes, a successful `rocminfo` GPU agent, or the minimum kernel is blocking. Allowlisted administrator actions show their exact effect and require immediate confirmation. The installer saves state before offering a reboot and always prints the resume command. + +The installer never changes firmware/BIOS or memory settings. It never executes commands received from the browser or backend API. Declined or unqualified GPU preparation offers the supported CPU command without deleting the GPU setup journal. diff --git a/docs/runtime-support-matrix.md b/docs/runtime-support-matrix.md new file mode 100644 index 0000000..500ef31 --- /dev/null +++ b/docs/runtime-support-matrix.md @@ -0,0 +1,12 @@ +# Runtime support matrix + +| Profile | Tier | Automated proof | Physical proof | +|---|---|---|---| +| NVIDIA CUDA (Windows/Ubuntu x64) | Supported | Manifest, resolver, CPU-host contract | Required for release | +| Apple MPS (Apple Silicon) | Supported installer | Manifest and contract | Required per model | +| AMD ROCm Linux, Ubuntu 24.04.3, gfx1150/gfx1151 | Supported stack | Detector fixtures | MoDiff model proof required | +| AMD ROCm Linux, Ubuntu 26.04 | Experimental | Detector fixtures | Local tensor and model proof required | +| AMD PyTorch Windows | Preview | Detector fixtures | Supported Ryzen/Radeon required | +| CPU | Supported | Install and tensor smoke | Reference host required | + +“Supported” describes installation/runtime qualification. `/model_capabilities` remains the source of per-model qualification. diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..20c8bb8 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,39 @@ +param( + [ValidateSet("auto", "nvidia", "amd", "mps", "cpu")][string]$Accelerator = "auto", + [switch]$DryRun, [switch]$NonInteractive, [switch]$Repair, [switch]$SystemCheck, + [switch]$Resume, [switch]$Json, [switch]$AllowExperimental, [switch]$BackendOnly +) +$ErrorActionPreference = "Stop" +Set-Location $PSScriptRoot +$pythonCommand = Get-Command python -ErrorAction SilentlyContinue +if (!$pythonCommand) { + $bootstrap = Join-Path $PSScriptRoot ".modiff\bootstrap" + New-Item -ItemType Directory -Force -Path $bootstrap | Out-Null + $archive = Join-Path $bootstrap "uv-x86_64-pc-windows-msvc.zip" + $expected = "4e1278ede866be6c0bf32d2f466cc6de7a9fb399ecf20c9ce2d186e52424be47" + if (!(Test-Path $archive)) { + Write-Host "Downloading the verified MoDiff bootstrap tool..." + Invoke-WebRequest "https://github.com/astral-sh/uv/releases/download/0.11.26/uv-x86_64-pc-windows-msvc.zip" -OutFile "$archive.part" + Move-Item "$archive.part" $archive + } + if ((Get-FileHash $archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $expected) { throw "Bootstrap hash verification failed; remove $archive and retry." } + $uvRoot = Join-Path $bootstrap "uv" + Remove-Item $uvRoot -Recurse -Force -ErrorAction SilentlyContinue + Expand-Archive $archive $uvRoot + $uv = Get-ChildItem $uvRoot -Filter uv.exe -Recurse | Select-Object -First 1 + $env:UV_PYTHON_INSTALL_DIR = Join-Path $PSScriptRoot ".modiff\tools\python" + & $uv.FullName python install 3.12 + $pythonCommand = Get-ChildItem $env:UV_PYTHON_INSTALL_DIR -Filter python.exe -Recurse | Select-Object -First 1 +} +$pythonExecutable = if ($pythonCommand.Source) { $pythonCommand.Source } else { $pythonCommand.FullName } +$argsList = @("-m", "modiff.install", "--accelerator", $Accelerator) +if ($DryRun) { $argsList += "--dry-run" } +if ($NonInteractive) { $argsList += "--non-interactive" } +if ($Repair) { $argsList += "--repair" } +if ($SystemCheck) { $argsList += "--system-check" } +if ($Resume) { $argsList += "--resume" } +if ($Json) { $argsList += "--json" } +if ($AllowExperimental) { $argsList += "--allow-experimental" } +if ($BackendOnly) { $argsList += "--backend-only" } +& $pythonExecutable @argsList +exit $LASTEXITCODE diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..dc74132 --- /dev/null +++ b/install.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")" + +if [[ -n "${PYTHON:-}" ]]; then + exec "$PYTHON" -m modiff.install "$@" +fi +if command -v python3 >/dev/null 2>&1; then + exec python3 -m modiff.install "$@" +fi + +os="$(uname -s)" +arch="$(uname -m)" +case "$os/$arch" in + Linux/x86_64) + asset="uv-x86_64-unknown-linux-gnu.tar.gz" + expected="6426a73c3837e6e2483ee344cbc00f36394d179afcba6183cb77437e67db4af0" + ;; + Darwin/arm64) + asset="uv-aarch64-apple-darwin.tar.gz" + expected="8f7fbf1708399b921857bce71e1d60f0d3ccf52a30caebc1c1a2f175dce13ab6" + ;; + *) + echo "No verified bootstrap toolchain is available for $os/$arch. Install Python 3.12 and rerun." >&2 + exit 2 + ;; +esac + +bootstrap=".modiff/bootstrap" +mkdir -p "$bootstrap" +archive="$bootstrap/$asset" +url="https://github.com/astral-sh/uv/releases/download/0.11.26/$asset" +if [[ ! -f "$archive" ]]; then + echo "Downloading the verified MoDiff bootstrap tool..." + if command -v curl >/dev/null 2>&1; then + curl -fL "$url" -o "$archive.part" + elif command -v wget >/dev/null 2>&1; then + wget -O "$archive.part" "$url" + else + echo "curl or wget is required for first-time bootstrap." >&2 + exit 2 + fi + mv "$archive.part" "$archive" +fi +if command -v sha256sum >/dev/null 2>&1; then + actual="$(sha256sum "$archive" | cut -d' ' -f1)" +else + actual="$(shasum -a 256 "$archive" | cut -d' ' -f1)" +fi +if [[ "$actual" != "$expected" ]]; then + echo "Bootstrap hash verification failed; remove $archive and retry." >&2 + exit 2 +fi +rm -rf "$bootstrap/uv" +mkdir -p "$bootstrap/uv" +tar -xf "$archive" -C "$bootstrap/uv" +uv_bin="$(find "$bootstrap/uv" -type f -name uv -perm -u+x | head -n 1)" +export UV_PYTHON_INSTALL_DIR="$PWD/.modiff/tools/python" +"$uv_bin" python install 3.12 +python_bin="$(find "$UV_PYTHON_INSTALL_DIR" -type f -path '*/bin/python3.12' | head -n 1)" +exec "$python_bin" -m modiff.install "$@" diff --git a/modiff/compatibility/__init__.py b/modiff/compatibility/__init__.py new file mode 100644 index 0000000..08b2233 --- /dev/null +++ b/modiff/compatibility/__init__.py @@ -0,0 +1 @@ +"""Release-pinned accelerator compatibility data.""" diff --git a/modiff/compatibility/accelerators.v1.json b/modiff/compatibility/accelerators.v1.json new file mode 100644 index 0000000..0ad89ca --- /dev/null +++ b/modiff/compatibility/accelerators.v1.json @@ -0,0 +1,55 @@ +{ + "schema_version": 1, + "revision": "2026.07.12-rocm72", + "validated_at": "2026-07-12", + "python": "3.12.*", + "profiles": { + "nvidia-cuda": { + "os": ["linux", "windows"], "architectures": ["x86_64"], "tier": "supported", + "torch": "2.8.0", "torchvision": "0.23.0", "torchaudio": "2.8.0", "cuda": "12.8", "rocm": null, + "index": "https://download.pytorch.org/whl/cu128", "requirements": "requirements/profiles/nvidia-cuda.txt", + "required": [], "prohibited": ["amd-rocm-linux", "amd-pytorch-windows"], + "default_dtype": "float16", "capabilities": ["torch-gpu", "nvidia-cuda", "fp16", "cuda-extension"], + "proof": "release-ci-and-live-nvidia", "sources": ["https://pytorch.org/get-started/locally/"] + }, + "amd-rocm-linux": { + "os": ["linux"], "architectures": ["x86_64"], "tier": "conditional", + "torch": "2.9.1+rocm7.2.0", "torchvision": "0.24.0+rocm7.2.0", "torchaudio": "2.9.0+rocm7.2.0", "triton": "3.5.1+rocm7.2.0", "cuda": null, "rocm": "7.2", + "index": "https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/", "requirements": "requirements/profiles/amd-rocm-linux.txt", + "required": [], "prohibited": ["bitsandbytes", "xformers", "nunchaku"], + "default_dtype": "float16", "capabilities": ["torch-gpu", "amd-rocm", "fp16", "unified-memory"], + "device_families": ["gfx1150", "gfx1151"], + "qualified_os_versions": ["24.04.3"], "experimental_os_versions": ["26.04"], "minimum_kernel": "6.14.1018", + "wheel_hashes": { + "torch": "199ba10a8b8b5a7eef64985bf87a41f0f4b28d49b1b861aa2768f6c5314c9b86", + "torchvision": "071667fd26e9af4e05127eabc572015d1f629e318096abc854b6c946f346359b", + "torchaudio": "cf78fd7774ca6187254a63051d07c46ab1f4bbd5a47e64b8d372d3c58e52979a", + "triton": "73d435248c3a207fec6f5a7a2aba631c7079a61230ce4aaa8cde02e800024b47" + }, + "proof": "amd-qualified-stack-awaiting-modiff-model-proof", "sources": ["https://rocm.docs.amd.com/projects/radeon-ryzen/en/docs-7.2/docs/compatibility/compatibilityryz/native_linux/native_linux_compatibility.html", "https://rocm.docs.amd.com/projects/radeon-ryzen/en/docs-7.2/docs/install/installryz/native_linux/install-pytorch.html"] + }, + "amd-pytorch-windows": { + "os": ["windows"], "architectures": ["x86_64"], "tier": "preview", + "torch": "2.8.0", "torchvision": "0.23.0", "torchaudio": "2.8.0", "cuda": null, "rocm": "windows-distribution", + "index": null, "requirements": "requirements/profiles/amd-pytorch-windows.txt", + "required": [], "prohibited": ["bitsandbytes", "xformers", "nunchaku"], + "default_dtype": "float16", "capabilities": ["torch-gpu", "amd-rocm", "fp16"], + "device_families": [], "proof": "manifest-fixtures-only", "sources": ["https://rocm.docs.amd.com/projects/radeon-ryzen/en/latest/docs/compatibility/compatibilityryz/windows/windows_compatibility.html"] + }, + "apple-mps": { + "os": ["macos"], "architectures": ["arm64"], "tier": "supported", + "torch": "2.8.0", "torchvision": "0.23.0", "torchaudio": "2.8.0", "cuda": null, "rocm": null, + "index": "https://pypi.org/simple", "requirements": "requirements/profiles/apple-mps.txt", + "required": [], "prohibited": ["bitsandbytes", "xformers", "nunchaku"], + "default_dtype": "float16", "capabilities": ["torch-gpu", "apple-mps", "fp16", "unified-memory"], + "proof": "release-ci-and-live-apple", "sources": ["https://pytorch.org/docs/stable/notes/mps.html"] + }, + "cpu": { + "os": ["linux", "windows", "macos"], "architectures": ["x86_64", "arm64"], "tier": "supported", + "torch": "2.8.0", "torchvision": "0.23.0", "torchaudio": "2.8.0", "cuda": null, "rocm": null, + "index": "https://download.pytorch.org/whl/cpu", "requirements": "requirements/profiles/cpu.txt", + "required": [], "prohibited": ["bitsandbytes", "xformers", "nunchaku"], + "default_dtype": "float32", "capabilities": [], "proof": "hosted-ci", "sources": ["https://pytorch.org/get-started/locally/"] + } + } +} diff --git a/modiff/install.py b/modiff/install.py new file mode 100644 index 0000000..9f6d8e7 --- /dev/null +++ b/modiff/install.py @@ -0,0 +1,720 @@ +"""Hardware-aware, resumable installer for MoDiff managed environments.""" +from __future__ import annotations + +import argparse +import getpass +import hashlib +import json +import os +import platform +import re +import shutil +import subprocess +import sys +import tarfile +import time +import urllib.request +import zipfile +from pathlib import Path +from typing import Any + +try: + import grp +except ImportError: # Windows does not provide the POSIX group database. + grp = None + +from modiff.runtime_profile import load_manifest, lock_digest, normalized_arch, normalized_os +from modiff.setup_catalog import CATALOG, PHASES, enrich_issue + +ROOT = Path(__file__).resolve().parents[1] +VENV = ROOT / ".venv" +STAGED_VENV = ROOT / ".venv.next" +PREVIOUS_VENV = ROOT / ".venv.previous" +PROFILE_STATE = VENV / "modiff-profile.json" +MANAGED_ROOT = ROOT / ".modiff" +JOURNAL_PATH = MANAGED_ROOT / "install-state.json" +DIAGNOSTICS_DIR = MANAGED_ROOT / "diagnostics" + +TOOL_ARCHIVES = { + ("linux", "x86_64", "uv"): ("https://github.com/astral-sh/uv/releases/download/0.11.26/uv-x86_64-unknown-linux-gnu.tar.gz", "6426a73c3837e6e2483ee344cbc00f36394d179afcba6183cb77437e67db4af0"), + ("macos", "arm64", "uv"): ("https://github.com/astral-sh/uv/releases/download/0.11.26/uv-aarch64-apple-darwin.tar.gz", "8f7fbf1708399b921857bce71e1d60f0d3ccf52a30caebc1c1a2f175dce13ab6"), + ("windows", "x86_64", "uv"): ("https://github.com/astral-sh/uv/releases/download/0.11.26/uv-x86_64-pc-windows-msvc.zip", "4e1278ede866be6c0bf32d2f466cc6de7a9fb399ecf20c9ce2d186e52424be47"), + ("linux", "x86_64", "node"): ("https://nodejs.org/dist/v24.12.0/node-v24.12.0-linux-x64.tar.xz", "bdebee276e58d0ef5448f3d5ac12c67daa963dd5e0a9bb621a53d1cefbc852fd"), + ("macos", "arm64", "node"): ("https://nodejs.org/dist/v24.12.0/node-v24.12.0-darwin-arm64.tar.gz", "319f221adc5e44ff0ed57e8a441b2284f02b8dc6fc87b8eb92a6a93643fd8080"), + ("windows", "x86_64", "node"): ("https://nodejs.org/dist/v24.12.0/node-v24.12.0-win-x64.zip", "9c125f61ae947b52e779095830f9cac267846a043ef7192183c84016aaad2812"), +} + + +def _now() -> str: + return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + + +def _read_journal() -> dict[str, Any]: + try: + value = json.loads(JOURNAL_PATH.read_text(encoding="utf-8")) + return value if isinstance(value, dict) else {} + except (OSError, ValueError): + return {} + + +def _write_journal(**updates: Any) -> dict[str, Any]: + MANAGED_ROOT.mkdir(exist_ok=True) + journal = _read_journal() + journal.update(updates) + journal.setdefault("schema_version", 1) + journal["updated_at"] = _now() + JOURNAL_PATH.write_text(json.dumps(journal, indent=2) + "\n", encoding="utf-8") + return journal + + +def _record_phase(phase: str, *, status: str = "complete", detail: Any = None) -> None: + journal = _read_journal() + phases = journal.setdefault("phases", {}) + phases[phase] = {"status": status, "updated_at": _now(), "detail": detail} + for step in journal.get("steps", []): + if isinstance(step, dict) and step.get("phase") == phase and step.get("status") != "skipped": + step["status"] = status + journal["current_phase"] = phase + _write_journal(**journal) + + +def _command(command: list[str], timeout: int = 8, env: dict[str, str] | None = None) -> dict[str, Any]: + try: + result = subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=False, env=env) + return {"returncode": result.returncode, "stdout": result.stdout, "stderr": result.stderr} + except (OSError, subprocess.SubprocessError) as exc: + return {"returncode": None, "stdout": "", "stderr": str(exc)} + + +def _os_release() -> dict[str, str]: + values: dict[str, str] = {} + try: + for line in Path("/etc/os-release").read_text(encoding="utf-8").splitlines(): + key, separator, value = line.partition("=") + if separator: + values[key] = value.strip().strip('"') + except OSError: + pass + return values + + +def _groups() -> list[str]: + if grp is None or not hasattr(os, "getgroups"): + return [] + result = [] + for gid in os.getgroups(): + try: + result.append(grp.getgrgid(gid).gr_name) + except KeyError: + continue + return sorted(set(result)) + + +def _kernel_tuple(value: str) -> tuple[int, ...]: + return tuple(int(item) for item in re.findall(r"\d+", value)[:3]) + + +def _rocm_library_dirs() -> list[Path]: + candidates = [Path("/opt/rocm/lib")] + candidates.extend(sorted(Path("/opt/rocm").glob("core-*/lib"), reverse=True)) + return [path.resolve() for path in candidates if path.is_dir()] + + +def _rocm_environment() -> dict[str, str]: + environment = os.environ.copy() + library_dirs = [str(path) for path in _rocm_library_dirs()] + existing = environment.get("LD_LIBRARY_PATH") + environment["LD_LIBRARY_PATH"] = os.pathsep.join([*library_dirs, *([existing] if existing else [])]) + environment.setdefault("ROCM_PATH", "/opt/rocm") + environment.setdefault("HIP_PATH", "/opt/rocm") + environment.setdefault("TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL", "1") + return environment + + +def detect_host() -> dict[str, Any]: + """Detect candidates without importing Torch and separate presence from usability.""" + os_name = normalized_os() + release = platform.release() + os_release = _os_release() + wsl = os_name == "linux" and ("microsoft" in release.lower() or "WSL_INTEROP" in os.environ) + nvidia_result = _command(["nvidia-smi", "-L"]) if shutil.which("nvidia-smi") else None + nvidia_usable = bool(nvidia_result and nvidia_result["returncode"] == 0 and "GPU" in nvidia_result["stdout"]) + + lspci = _command(["lspci", "-nn"]) if shutil.which("lspci") else {"stdout": ""} + amd_candidate = "1002:" in lspci["stdout"].lower() or "advanced micro devices" in lspci["stdout"].lower() + rocminfo = _command(["rocminfo"], timeout=15) if shutil.which("rocminfo") else None + rocm_text = f"{rocminfo['stdout']}\n{rocminfo['stderr']}" if rocminfo else "" + architectures = sorted(set(re.findall(r"\bgfx\d+[a-z0-9]*\b", rocm_text.lower()))) + kfd = Path("/dev/kfd") + render_nodes = sorted(str(path) for path in Path("/dev/dri").glob("renderD*")) + groups = _groups() + amd_usable = bool( + amd_candidate + and rocminfo + and rocminfo["returncode"] == 0 + and architectures + and kfd.exists() + and os.access(kfd, os.R_OK | os.W_OK) + and render_nodes + and "video" in groups + and "render" in groups + ) + apple = os_name == "macos" and normalized_arch() == "arm64" + candidates = (["nvidia"] if nvidia_usable else []) + (["amd"] if amd_candidate else []) + (["mps"] if apple else []) + return { + "os": os_name, + "os_id": os_release.get("ID"), + "os_version": os_release.get("VERSION_ID"), + "architecture": normalized_arch(), + "kernel": release, + "wsl": wsl, + "container": Path("/.dockerenv").exists(), + "groups": groups, + "nvidia_usable": nvidia_usable, + "amd_candidate": amd_candidate, + "amd_usable": amd_usable, + "amd_architectures": architectures, + "kfd_present": kfd.exists(), + "kfd_accessible": kfd.exists() and os.access(kfd, os.R_OK | os.W_OK), + "render_nodes": render_nodes, + "rocminfo_returncode": rocminfo["returncode"] if rocminfo else None, + "rocminfo_error": rocminfo["stderr"].strip() if rocminfo and rocminfo["returncode"] else None, + "mps_candidate": apple, + "candidates": candidates, + } + + +def resolve_profile(accelerator: str, host: dict[str, Any], *, allow_experimental: bool = False, non_interactive: bool = False) -> str: + aliases = {"nvidia": "nvidia-cuda", "mps": "apple-mps"} + if accelerator not in {"auto", "amd", "cpu", *aliases}: + raise ValueError(f"Unknown accelerator: {accelerator}") + if accelerator != "auto": + if accelerator == "amd": + return "amd-pytorch-windows" if host["os"] == "windows" else "amd-rocm-linux" + return aliases.get(accelerator, accelerator) + if host.get("wsl"): + return "cpu" + amd_candidate = host.get("amd_candidate", host.get("amd_usable", False)) + if host.get("nvidia_usable") and amd_candidate: + raise ValueError("NVIDIA/AMD hybrid systems require an explicit --accelerator choice") + if host.get("nvidia_usable"): + return "nvidia-cuda" + if amd_candidate: + experimental = host.get("os") == "linux" and host.get("os_version") == "26.04" + if experimental and non_interactive and not allow_experimental: + return "cpu" + return "amd-pytorch-windows" if host["os"] == "windows" else "amd-rocm-linux" + if host.get("mps_candidate"): + return "apple-mps" + return "cpu" + + +def _issue(code: str, message: str, *, blocking: bool = True, command: str | None = None, + action: dict[str, Any] | None = None, requires_reboot: bool = False) -> dict[str, Any]: + return enrich_issue(code, message, blocking=blocking, command=command, action=action, requires_reboot=requires_reboot) + + +def _amd_qualification(host: dict[str, Any], spec: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]: + issues: list[dict[str, Any]] = [] + version = host.get("os_version") + qualified = version in spec.get("qualified_os_versions", []) + experimental = version in spec.get("experimental_os_versions", []) + tier = "supported" if qualified else ("experimental" if experimental else "unqualified") + if host.get("os_id") != "ubuntu" or not (qualified or experimental): + issues.append(_issue("unsupported-os", f"Ubuntu {version or 'unknown'} is not qualified for this AMD profile.")) + if _kernel_tuple(host.get("kernel", "")) < _kernel_tuple(spec.get("minimum_kernel", "0")): + issues.append(_issue("kernel-too-old", f"AMD Ryzen requires kernel {spec['minimum_kernel']} or newer.")) + if not host.get("kfd_present") or not host.get("render_nodes"): + issues.append(_issue("gpu-device-nodes-missing", "/dev/kfd and a DRM render node must exist after the AMD system stack is prepared.")) + if "video" not in host.get("groups", []) or "render" not in host.get("groups", []): + command = f"sudo usermod -a -G video,render {getpass.getuser()}" + issues.append(_issue( + "gpu-groups-missing", + "The current user must belong to video and render; reboot after changing membership.", + command=command, + action={"id": "linux-add-gpu-groups", "argv": ["usermod", "-a", "-G", "video,render", getpass.getuser()], "requires_admin": True}, + requires_reboot=True, + )) + if host.get("kfd_present") and not host.get("kfd_accessible"): + issues.append(_issue("kfd-permission-denied", "The current user cannot read and write /dev/kfd.")) + if host.get("rocminfo_returncode") != 0: + issues.append(_issue("rocminfo-failed", host.get("rocminfo_error") or "rocminfo did not complete successfully.")) + detected = set(host.get("amd_architectures", [])) + supported = set(spec.get("device_families", [])) + if detected and not detected.intersection(supported): + issues.append(_issue("unsupported-amd-architecture", f"Detected {', '.join(sorted(detected))}; expected one of {', '.join(sorted(supported))}.")) + elif not detected: + issues.append(_issue("amd-architecture-unverified", "rocminfo did not expose a supported GPU agent.")) + library_result = _command(["ldconfig", "-p"]) + library_text = library_result["stdout"] + library_names = {path.name for directory in _rocm_library_dirs() for path in directory.glob("*.so*")} + required_libraries = { + "libamdhip64.so.7", "libMIOpen.so.1", "libhipblas.so.3", "libhipblaslt.so.1", + "libhipfft.so.0", "libhiprand.so.1", "libhiprtc.so.7", "libhipsolver.so.1", + "libhipsparse.so.4", "libhipsparselt.so.0", "librccl.so.1", "librocblas.so.5", + "librocsolver.so.0", "libroctracer64.so.4", "libroctx64.so.4", + } + missing_libraries = sorted(name for name in required_libraries if name not in library_text and name not in library_names) + if missing_libraries: + if version == "26.04": + command = "sudo apt install amdrocm-gfx1151" + action = {"id": "ubuntu-install-amdrocm-gfx1151", "argv": ["apt", "install", "-y", "amdrocm-gfx1151"], "requires_admin": True} + else: + command = "sudo amdgpu-install -y --usecase=rocm --no-dkms" + action = {"id": "ubuntu-install-rocm-no-dkms", "argv": ["amdgpu-install", "-y", "--usecase=rocm", "--no-dkms"], "requires_admin": True} + issues.append(_issue( + "rocm-userspace-incomplete", + f"ROCm userspace is missing {len(missing_libraries)} required libraries: {', '.join(missing_libraries)}.", + command=command, + action=action, + )) + return tier, issues + + +def build_plan(args: argparse.Namespace) -> dict[str, Any]: + host = detect_host() + profile = resolve_profile(args.accelerator, host, allow_experimental=args.allow_experimental, non_interactive=args.non_interactive) + manifest = load_manifest() + spec = manifest["profiles"][profile] + issues: list[dict[str, Any]] = [] + tier = spec["tier"] + if host["os"] not in spec["os"] or host["architecture"] not in spec["architectures"]: + issues.append(_issue("unsupported-platform", f"{profile} is unavailable on {host['os']}/{host['architecture']}.")) + if profile == "amd-rocm-linux": + tier, amd_issues = _amd_qualification(host, spec) + issues.extend(amd_issues) + if tier == "experimental" and not args.allow_experimental: + issues.append(_issue("experimental-opt-in-required", "Ubuntu 26.04 AMD setup requires --allow-experimental.")) + requirement = ROOT / spec["requirements"] + if not requirement.is_file(): + issues.append(_issue("profile-lock-missing", f"Profile requirements are missing: {requirement}")) + steps = [ + {"id": "detect", "title": "Detect hardware and operating system", "phase": "detect", "status": "complete", "automatic": True}, + {"id": "resolve-profile", "title": f"Select {profile}", "phase": "plan", "status": "complete", "automatic": True}, + *[{**issue, "phase": "system-preparation"} for issue in issues], + {"id": "toolchain", "title": "Prepare required app-local toolchains", "phase": "toolchain", "status": "pending", "automatic": True}, + {"id": "backend", "title": "Install the staged backend environment", "phase": "backend", "status": "pending", "automatic": True}, + {"id": "client", "title": "Install and build the sibling client", "phase": "client", "status": "skipped" if getattr(args, "backend_only", False) else "pending", "automatic": True}, + {"id": "validation", "title": "Verify packages and execute a device tensor", "phase": "validation", "status": "pending", "automatic": True}, + {"id": "complete", "title": "Finish setup", "phase": "complete", "status": "pending", "automatic": True}, + ] + return { + "manifest_revision": manifest["revision"], + "host": host, + "profile": profile, + "support_tier": tier, + "requirements": str(requirement), + "requirements_exist": requirement.is_file(), + "issues": issues, + "steps": steps, + "phases": PHASES, + "downloads": {"accelerator_bytes_approx": 2_000_000_000 if profile == "amd-rocm-linux" else None}, + "cpu_fallback_command": "./install.sh --accelerator cpu", + "resume_command": "./install.sh --resume" + (" --allow-experimental" if tier == "experimental" else ""), + "execution_ready": not any(issue["blocking"] for issue in issues), + } + + +ALLOWED_SYSTEM_ACTIONS = { + "linux-add-gpu-groups": ("usermod", "-a", "-G", "video,render"), + "ubuntu-install-amdrocm-gfx1151": ("apt", "install", "-y", "amdrocm-gfx1151"), + "ubuntu-install-rocm-no-dkms": ("amdgpu-install", "-y", "--usecase=rocm", "--no-dkms"), +} + + +def _action_is_allowed(action: dict[str, Any]) -> bool: + expected = ALLOWED_SYSTEM_ACTIONS.get(str(action.get("id"))) + argv = tuple(str(item) for item in action.get("argv", [])) + return bool(expected and argv[:len(expected)] == expected) + + +def _confirm(prompt: str) -> bool: + try: + return input(f"{prompt} [y/N] ").strip().lower() in {"y", "yes"} + except (EOFError, KeyboardInterrupt): + return False + + +def _execute_system_action(issue: dict[str, Any]) -> dict[str, Any]: + action = issue.get("action") + if not isinstance(action, dict) or not _action_is_allowed(action): + raise RuntimeError(f"Refusing non-allowlisted system action for {issue['id']}") + argv = [str(item) for item in action["argv"]] + command = ["sudo", *argv] if action.get("requires_admin") and os.name != "nt" else argv + started = _now() + result = subprocess.run(command, cwd=ROOT, check=False) + receipt = {"id": action["id"], "command": command, "started_at": started, "finished_at": _now(), "returncode": result.returncode} + journal = _read_journal() + receipts = journal.setdefault("system_actions", []) + receipts.append(receipt) + _write_journal(**journal) + if result.returncode != 0: + raise RuntimeError(f"Administrator action failed ({result.returncode}): {' '.join(command)}") + return receipt + + +def _render_plan(plan: dict[str, Any]) -> None: + host = plan["host"] + print("\nMoDiff guided setup") + print("=" * 20) + print(f"Detected: {host['os']} {host.get('os_version') or ''} / {host['architecture']}") + print(f"Hardware: {', '.join(host.get('candidates', [])) or 'CPU'}") + print(f"Profile: {plan['profile']} ({plan['support_tier']})") + if plan["downloads"]["accelerator_bytes_approx"]: + print("Download: approximately 2 GB for the accelerator runtime") + print("\nSetup checklist:") + for index, step in enumerate(plan["steps"], 1): + marker = {"complete": "✓", "blocked": "!", "warning": "!", "skipped": "-"}.get(step.get("status"), "·") + print(f" {index}. [{marker}] {step['title']}") + if step.get("status") in {"blocked", "warning"}: + print(f" {step.get('explanation') or step.get('message')}") + if step.get("command"): + print(f" Command: {step['command']}") + if step.get("verification"): + print(f" Verify: {step['verification']}") + print(f"\nCPU fallback: {plan['cpu_fallback_command']}") + + +def _run(command: list[str], *, cwd: Path = ROOT, env: dict[str, str] | None = None) -> None: + subprocess.run(command, cwd=cwd, check=True, env=env) + + +def _ensure_venv(uv: str, target: Path) -> Path: + python = target / ("Scripts/python.exe" if os.name == "nt" else "bin/python") + version = _command([str(python), "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"]) if python.exists() else None + if version and version["returncode"] == 0 and version["stdout"].strip() == "3.12": + return python + if python.exists(): + raise RuntimeError(f"Existing {target.name} is not Python 3.12") + environment = os.environ.copy() + environment["UV_PYTHON_INSTALL_DIR"] = str(MANAGED_ROOT / "tools" / "python") + _run([uv, "venv", "--python", "3.12", str(target)], env=environment) + return python + + +def _download_tool(name: str) -> Path: + key = (normalized_os(), normalized_arch(), name) + if key not in TOOL_ARCHIVES: + raise RuntimeError(f"No app-local {name} archive is pinned for {key[0]}/{key[1]}") + url, expected_hash = TOOL_ARCHIVES[key] + downloads = MANAGED_ROOT / "downloads" + tools = MANAGED_ROOT / "tools" + downloads.mkdir(parents=True, exist_ok=True) + tools.mkdir(parents=True, exist_ok=True) + archive = downloads / url.rsplit("/", 1)[-1] + if not archive.is_file() or hashlib.sha256(archive.read_bytes()).hexdigest() != expected_hash: + temporary = archive.with_suffix(archive.suffix + ".part") + digest = hashlib.sha256() + with urllib.request.urlopen(url) as response, temporary.open("wb") as handle: + while chunk := response.read(8 * 1024 * 1024): + handle.write(chunk) + digest.update(chunk) + if digest.hexdigest() != expected_hash: + temporary.unlink(missing_ok=True) + raise RuntimeError(f"Hash verification failed for {name}") + temporary.replace(archive) + destination = tools / name + if destination.exists(): + shutil.rmtree(destination) + destination.mkdir(parents=True) + if zipfile.is_zipfile(archive): + with zipfile.ZipFile(archive) as bundle: + bundle.extractall(destination) + else: + with tarfile.open(archive) as bundle: + for member in bundle.getmembers(): + if member.name.startswith("/") or ".." in Path(member.name).parts: + raise RuntimeError(f"Unsafe path in {name} archive") + bundle.extractall(destination, filter="data") + return destination + + +def _find_executable(root: Path, names: tuple[str, ...]) -> str | None: + for name in names: + match = next((path for path in root.rglob(name) if path.is_file()), None) + if match: + match.chmod(match.stat().st_mode | 0o111) + return str(match) + return None + + +def _ensure_uv() -> str: + managed_uv = MANAGED_ROOT / "tools" / "uv" + uv = _find_executable(managed_uv, ("uv.exe", "uv")) if managed_uv.exists() else None + if not uv: + uv = _find_executable(_download_tool("uv"), ("uv.exe", "uv")) + if not uv: + raise RuntimeError("The app-local uv archive did not contain the expected executable") + return uv + + +def _ensure_node() -> dict[str, str]: + managed_node = MANAGED_ROOT / "tools" / "node" + node = _find_executable(managed_node, ("node.exe", "node")) if managed_node.exists() else None + npm_names = ("npm.cmd", "npm") if os.name == "nt" else ("npm", "npm.cmd") + npm = _find_executable(managed_node, npm_names) if managed_node.exists() else None + if not node or not npm: + managed_node = _download_tool("node") + node = _find_executable(managed_node, ("node.exe", "node")) + npm = _find_executable(managed_node, npm_names) + if not node or not npm: + raise RuntimeError("The app-local Node archive did not contain the expected executables") + node_version = _command([node, "-p", "process.versions.node"]) + if node_version["returncode"] != 0 or not node_version["stdout"].strip().startswith("24."): + raise RuntimeError(f"MoDiff requires Node 24; detected {node_version['stdout'].strip() or 'unknown'}") + return {"node": node, "npm": npm, "node_version": node_version["stdout"].strip()} + + +def _promote_staged_environment() -> bool: + rolled_back = False + if PREVIOUS_VENV.exists(): + shutil.rmtree(PREVIOUS_VENV) + if VENV.exists(): + VENV.rename(PREVIOUS_VENV) + try: + STAGED_VENV.rename(VENV) + except Exception: + if PREVIOUS_VENV.exists() and not VENV.exists(): + PREVIOUS_VENV.rename(VENV) + rolled_back = True + raise + return rolled_back + + +def _rollback_promoted_environment() -> None: + failed = ROOT / ".venv.failed" + if failed.exists(): + shutil.rmtree(failed) + if VENV.exists(): + VENV.rename(failed) + if PREVIOUS_VENV.exists(): + PREVIOUS_VENV.rename(VENV) + + +def _client_path() -> Path | None: + candidates = [ROOT.parent / "MoDiff-client", ROOT.parent / "modiff-client"] + return next((path for path in candidates if (path / "package-lock.json").is_file()), None) + + +def _install_client(*, backend_only: bool) -> dict[str, Any]: + if backend_only: + return {"status": "skipped", "reason": "--backend-only"} + client = _client_path() + if not client: + return {"status": "skipped", "reason": "sibling client not found"} + toolchains = _ensure_node() + DIAGNOSTICS_DIR.mkdir(parents=True, exist_ok=True) + environment = os.environ.copy() + environment["PATH"] = str(Path(toolchains["node"]).parent) + os.pathsep + environment.get("PATH", "") + for command, log_name in (([toolchains["npm"], "ci"], "npm-ci.log"), ([toolchains["npm"], "run", "build"], "npm-build.log")): + result = subprocess.run(command, cwd=client, env=environment, capture_output=True, text=True, check=False) + (DIAGNOSTICS_DIR / log_name).write_text(result.stdout + result.stderr, encoding="utf-8") + if result.returncode != 0: + raise RuntimeError(f"Client setup failed during {' '.join(command[1:])}; see {DIAGNOSTICS_DIR / log_name}") + return {"status": "complete", "path": str(client), "node": toolchains["node_version"]} + + +def _smoke_script(profile: str) -> str: + expected = {"amd-rocm-linux": "rocm", "nvidia-cuda": "cuda", "apple-mps": "mps", "cpu": "cpu"}.get(profile, "cpu") + return f""" +import json, torch +backend = 'rocm' if torch.version.hip else ('cuda' if torch.version.cuda else ('mps' if torch.backends.mps.is_built() else 'cpu')) +assert backend == {expected!r}, (backend, {expected!r}) +device = 'cuda:0' if backend in ('cuda', 'rocm') else ('mps:0' if backend == 'mps' else 'cpu:0') +assert device == 'cpu:0' or (torch.cuda.is_available() if device.startswith('cuda') else torch.backends.mps.is_available()) +dtype = torch.float16 if backend in ('cuda', 'rocm', 'mps') else torch.float32 +x = torch.tensor([1.0, 2.0], device=device, dtype=dtype) +y = x * 2 + 1 +assert y.cpu().float().tolist() == [3.0, 5.0] +if backend in ('cuda', 'rocm'): torch.cuda.synchronize() +elif backend == 'mps': torch.mps.synchronize() +del x, y +if backend in ('cuda', 'rocm'): torch.cuda.empty_cache() +elif backend == 'mps': torch.mps.empty_cache() +print(json.dumps({{'backend': backend, 'device': device, 'torch': torch.__version__, 'hip': torch.version.hip, 'cuda': torch.version.cuda}})) +""" + + +def install(args: argparse.Namespace) -> dict[str, Any]: + prior_journal = _read_journal() + if prior_journal.get("consent", {}).get("experimental-platform") is True: + args.allow_experimental = True + plan = build_plan(args) + if args.allow_experimental and plan["support_tier"] == "experimental": + consent = prior_journal.setdefault("consent", {}) + consent["experimental-platform"] = True + _write_journal(**prior_journal) + if args.dry_run or args.system_check: + return plan + _write_journal( + status="running", current_phase="plan", profile=plan["profile"], support_tier=plan["support_tier"], + manifest_revision=plan["manifest_revision"], steps=plan["steps"], resume_command=plan["resume_command"], + completed_phases=["detect", "plan"], rollback={"performed": False}, + ) + experimental_issue = next((item for item in plan["issues"] if item["id"] == "experimental-opt-in-required"), None) + if experimental_issue and not args.non_interactive and _confirm("This platform is experimental. Continue with the GPU profile?"): + args.allow_experimental = True + journal = _read_journal() + consent = journal.setdefault("consent", {}) + consent["experimental-platform"] = True + _write_journal(**journal) + plan = build_plan(args) + + reboot_required = False + for issue in list(plan["issues"]): + action = issue.get("action") + if not issue.get("blocking") or not action: + continue + if args.non_interactive: + continue + print(f"\nAdministrator step: {issue['title']}") + print(issue["explanation"]) + print(f"Command: {issue['command']}") + if _confirm("Allow MoDiff to run this command?"): + _execute_system_action(issue) + reboot_required = reboot_required or bool(issue.get("requires_reboot")) + + if any(issue.get("action") for issue in plan["issues"]): + plan = build_plan(args) + if reboot_required: + _write_journal(status="reboot-required", current_phase="system-preparation", reboot_required=True, + next_action="./install.sh --resume" + (" --allow-experimental" if args.allow_experimental else "")) + print("\nA reboot or complete sign-out is required before GPU access can be verified.") + print(f"Resume afterward with: {_read_journal()['next_action']}") + if not args.non_interactive and _confirm("Reboot now?"): + subprocess.run(["sudo", "reboot"], check=False) + return {**plan, "status": "reboot-required", "reboot_required": True, "journal": str(JOURNAL_PATH)} + + if not plan["execution_ready"]: + details = [] + for issue in plan["issues"]: + if not issue.get("blocking"): + continue + detail = f"{issue['code']}: {issue['message']}" + if issue.get("guided_command"): + detail += f" Run: {issue['guided_command']}" + details.append(detail) + _write_journal(status="blocked", current_phase="system-preparation", steps=plan["steps"], next_action=plan["resume_command"]) + raise RuntimeError("System preparation is required. " + " ".join(details)) + + _record_phase("system-preparation") + _record_phase("toolchain", status="running") + uv = _ensure_uv() + _record_phase("toolchain", detail={"uv": uv}) + + _record_phase("backend", status="running") + if STAGED_VENV.exists(): + shutil.rmtree(STAGED_VENV) + python = _ensure_venv(uv, STAGED_VENV) + _run([uv, "pip", "install", "--python", str(python), "-r", plan["requirements"]]) + smoke_environment = _rocm_environment() if plan["profile"] == "amd-rocm-linux" else os.environ.copy() + smoke = _command([str(python), "-c", _smoke_script(plan["profile"])], timeout=60, env=smoke_environment) + if smoke["returncode"] != 0: + _write_journal(status="failed", current_phase="validation", failure=smoke["stderr"].strip() or smoke["stdout"].strip(), + rollback={"performed": False, "reason": "staged environment was never promoted"}, next_action=plan["resume_command"]) + raise RuntimeError(f"Device tensor smoke failed: {smoke['stderr'].strip() or smoke['stdout'].strip()}") + _run([uv, "pip", "check", "--python", str(python)]) + requirement = Path(plan["requirements"]) + smoke_result = json.loads(smoke["stdout"].strip().splitlines()[-1]) + state = { + "schema_version": 1, + "profile": plan["profile"], + "support_tier": plan["support_tier"], + "manifest_revision": plan["manifest_revision"], + "requirements": requirement.name, + "lock_digest": lock_digest(requirement), + "host": {key: plan["host"].get(key) for key in ("os", "os_version", "architecture", "kernel", "amd_architectures")}, + "smoke": smoke_result, + } + (STAGED_VENV / "modiff-profile.json").write_text(json.dumps(state, indent=2) + "\n", encoding="utf-8") + _record_phase("validation", detail=smoke_result) + rolled_back = _promote_staged_environment() + promoted_python = VENV / ("Scripts/python.exe" if os.name == "nt" else "bin/python") + promoted_smoke = _command([str(promoted_python), "-c", _smoke_script(plan["profile"])], timeout=60, env=smoke_environment) + if promoted_smoke["returncode"] != 0: + _rollback_promoted_environment() + _write_journal(status="failed", current_phase="validation", failure=promoted_smoke["stderr"].strip(), + rollback={"performed": True, "reason": "promoted environment failed validation"}, next_action=plan["resume_command"]) + raise RuntimeError(f"Promoted environment validation failed and was rolled back: {promoted_smoke['stderr'].strip()}") + _record_phase("backend", detail={"promoted": True, "previous_environment": PREVIOUS_VENV.exists()}) + + _record_phase("client", status="running") + client_result = _install_client(backend_only=args.backend_only) + _record_phase("client", status=client_result["status"], detail=client_result) + _record_phase("complete") + _write_journal(status="complete", current_phase="complete", completed_phases=PHASES, + rollback={"performed": rolled_back}, next_action="./run.sh", application_url="http://127.0.0.1:8088") + plan["state"] = state + plan["client"] = client_result + plan["status"] = "complete" + plan["application_url"] = "http://127.0.0.1:8088" + plan["journal"] = str(JOURNAL_PATH) + return plan + + +def parser() -> argparse.ArgumentParser: + result = argparse.ArgumentParser(description=__doc__) + result.add_argument("--accelerator", default="auto", choices=["auto", "nvidia", "amd", "mps", "cpu"]) + result.add_argument("--dry-run", action="store_true") + result.add_argument("--non-interactive", action="store_true") + result.add_argument("--repair", action="store_true") + result.add_argument("--system-check", action="store_true") + result.add_argument("--resume", action="store_true") + result.add_argument("--allow-experimental", action="store_true") + result.add_argument("--backend-only", action="store_true", help="Skip sibling client dependency installation and build.") + result.add_argument("--guide", action="store_true", help="Print the step-by-step troubleshooting catalog and exit.") + result.add_argument("--json", action="store_true") + return result + + +def main(argv: list[str] | None = None) -> int: + args = parser().parse_args(argv) + if args.json: + args.non_interactive = True + if _read_journal().get("consent", {}).get("experimental-platform") is True: + args.allow_experimental = True + try: + if args.guide: + if args.json: + print(json.dumps(CATALOG, indent=2)) + else: + print("MoDiff setup guide\n==================") + for code, item in CATALOG.items(): + print(f"\n{item['title']} ({code})") + print(f" {item['explanation']}") + print(f" Verify: {item['verification']}") + print(f" Next: {item['failure_help']}") + return 0 + preview = build_plan(args) + if not args.json: + _render_plan(preview) + if args.dry_run or args.system_check: + label = "Dry run" if args.dry_run else "System check" + print(json.dumps(preview, indent=2) if args.json else f"\n{label} complete; no changes were made.") + return 0 if preview["execution_ready"] else 2 + result = install(args) + if args.json: + print(json.dumps(result, indent=2)) + elif result.get("status") == "complete": + print("\n✓ MoDiff installation complete") + print(f" Profile: {result['profile']} ({result['support_tier']})") + print(f" Start: ./run.sh") + print(f" Open: {result['application_url']}") + return 0 if result.get("status") in {"complete", "reboot-required"} else 2 + except Exception as exc: + journal = _read_journal() + _write_journal(status="failed", failure=str(exc), next_action=journal.get("next_action", "./install.sh --resume")) + payload = {"error": str(exc), "completed": journal.get("completed_phases", []), "rollback": journal.get("rollback"), + "resume_command": journal.get("next_action", "./install.sh --resume"), "journal": str(JOURNAL_PATH)} + if args.json: + print(json.dumps(payload, indent=2), file=sys.stderr) + else: + print(f"\nSetup failed: {exc}", file=sys.stderr) + print(f"Completed: {', '.join(payload['completed']) or 'no phases'}", file=sys.stderr) + print(f"Rollback: {payload['rollback'] or {'performed': False}}", file=sys.stderr) + print(f"Resume: {payload['resume_command']}", file=sys.stderr) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/modiff/runtime_profile.py b/modiff/runtime_profile.py new file mode 100644 index 0000000..0409641 --- /dev/null +++ b/modiff/runtime_profile.py @@ -0,0 +1,136 @@ +"""Resolve and validate the managed accelerator environment.""" +from __future__ import annotations + +import hashlib +import json +import os +import platform +from pathlib import Path +from typing import Any + +MANIFEST_PATH = Path(__file__).with_name("compatibility") / "accelerators.v1.json" +STATE_NAME = "modiff-profile.json" +INSTALL_JOURNAL_PATH = MANIFEST_PATH.parents[2] / ".modiff" / "install-state.json" + + +def load_manifest(path: Path = MANIFEST_PATH) -> dict[str, Any]: + data = json.loads(path.read_text(encoding="utf-8")) + if data.get("schema_version") != 1 or not isinstance(data.get("profiles"), dict): + raise ValueError("Unsupported accelerator manifest") + return data + + +def normalized_os(value: str | None = None) -> str: + value = (value or platform.system()).lower() + return {"darwin": "macos", "win32": "windows"}.get(value, value) + + +def normalized_arch(value: str | None = None) -> str: + value = (value or platform.machine()).lower() + return {"amd64": "x86_64", "aarch64": "arm64"}.get(value, value) + + +def state_path(venv: Path | None = None) -> Path: + root = venv or Path(os.environ.get("VIRTUAL_ENV", Path.cwd() / ".venv")) + return root / STATE_NAME + + +def read_state(venv: Path | None = None) -> dict[str, Any] | None: + try: + value = json.loads(state_path(venv).read_text(encoding="utf-8")) + return value if isinstance(value, dict) else None + except (OSError, ValueError): + return None + + +def read_install_journal() -> dict[str, Any] | None: + try: + value = json.loads(INSTALL_JOURNAL_PATH.read_text(encoding="utf-8")) + if not isinstance(value, dict): + return None + steps = [] + for step in value.get("steps", []): + if not isinstance(step, dict): + continue + normalized = {key: step.get(key) for key in ( + "id", "title", "phase", "status", "explanation", "automatic", "requires_admin", + "requires_reboot", "command", "verification", "documentation_url", "failure_help", + ) if step.get(key) is not None} + phase_status = value.get("phases", {}).get(step.get("phase"), {}).get("status") + if phase_status and normalized.get("status") != "skipped": + normalized["status"] = phase_status + steps.append(normalized) + return { + "status": value.get("status"), + "current_phase": value.get("current_phase"), + "completed_phases": value.get("completed_phases", []), + "steps": steps, + "reboot_required": bool(value.get("reboot_required")), + "resume_command": value.get("next_action") or value.get("resume_command"), + "updated_at": value.get("updated_at"), + "failure": value.get("failure"), + } + except (OSError, ValueError): + return None + + +def profile_for_installed_torch(torch_state: dict[str, Any], os_name: str | None = None) -> str | None: + if torch_state.get("hip_version"): + return "amd-pytorch-windows" if normalized_os(os_name) == "windows" else "amd-rocm-linux" + if torch_state.get("cuda_version"): + return "nvidia-cuda" + if torch_state.get("mps_built"): + return "apple-mps" + if torch_state.get("available"): + return "cpu" + return None + + +def runtime_profile(hardware: dict[str, Any], requested: str | None = None, venv: Path | None = None) -> dict[str, Any]: + manifest = load_manifest() + saved = read_state(venv) + requested = requested or (saved or {}).get("profile") + installed = profile_for_installed_torch(hardware.get("torch", {})) + detected = hardware.get("detected_profile") or installed or "cpu" + issues: list[dict[str, str]] = [] + if not saved: + issues.append({"code": "profile-unverified", "severity": "warning", "message": "This environment predates managed accelerator profiles."}) + if requested and installed and requested != installed: + issues.append({"code": "profile-mismatch", "severity": "error", "message": f"Requested {requested}, but installed Torch resolves to {installed}."}) + if detected and installed and detected != installed and detected != "cpu": + issues.append({"code": "hardware-profile-mismatch", "severity": "error", "message": f"Detected hardware resolves to {detected}, but installed Torch resolves to {installed}."}) + selected = requested or installed or detected + spec = manifest["profiles"].get(selected) + if spec and (normalized_os() not in spec["os"] or normalized_arch() not in spec["architectures"]): + issues.append({"code": "unsupported-platform", "severity": "error", "message": f"{selected} is not qualified on this OS/architecture."}) + torch_state = hardware.get("torch", {}) + backend_usable = bool(torch_state.get("available")) + if installed in {"nvidia-cuda", "amd-rocm-linux", "amd-pytorch-windows"}: + backend_usable = backend_usable and bool(torch_state.get("cuda_available")) + elif installed == "apple-mps": + backend_usable = backend_usable and bool(torch_state.get("mps_available")) + if installed and not backend_usable: + issues.append({"code": "installed-backend-unavailable", "severity": "error", "message": f"Installed {installed} Torch cannot execute on its accelerator."}) + if saved and selected == "amd-rocm-linux": + if not str(torch_state.get("version") or "").startswith("2.9.1+rocm7.2") or not str(torch_state.get("hip_version") or "").startswith("7.2"): + issues.append({"code": "profile-version-mismatch", "severity": "error", "message": "The managed AMD profile requires Torch 2.9.1 built for ROCm 7.2."}) + ready = backend_usable and not any(i["severity"] == "error" for i in issues) + repair_accelerator = {"nvidia-cuda": "nvidia", "amd-rocm-linux": "amd", "amd-pytorch-windows": "amd", "apple-mps": "mps"}.get(selected, "cpu") + installation = read_install_journal() + return { + "requested": requested, + "detected": detected, + "installed": installed, + "status": "ready" if ready else ("mismatch" if any(i["code"] == "profile-mismatch" for i in issues) else "setup-required"), + "execution_ready": ready, + "manifest_revision": manifest["revision"], + "support_tier": (saved or {}).get("support_tier") or (spec.get("tier") if spec else "unqualified"), + "capabilities": spec.get("capabilities", []) if spec else [], + "issues": issues, + "repair_command": f"python -m modiff.install --accelerator {repair_accelerator} --repair", + "installation": installation, + } + + +def lock_digest(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() diff --git a/modiff/setup_catalog.py b/modiff/setup_catalog.py new file mode 100644 index 0000000..fa6f3c0 --- /dev/null +++ b/modiff/setup_catalog.py @@ -0,0 +1,103 @@ +"""Shared installer help and safe system-action metadata.""" +from __future__ import annotations + +CATALOG = { + "unsupported-platform": { + "title": "Unsupported platform", + "explanation": "The selected accelerator profile has not been qualified for this operating system or architecture.", + "verification": "Run the installer with --system-check --json.", + "failure_help": "Choose the CPU profile or use a qualified host.", + }, + "unsupported-os": { + "title": "Operating system is not qualified", + "explanation": "GPU packages are tightly coupled to the operating system and kernel.", + "verification": "Read /etc/os-release and compare it with the runtime support matrix.", + "failure_help": "Use CPU or migrate to a qualified operating system.", + }, + "kernel-too-old": { + "title": "Kernel update required", + "explanation": "The AMD compute runtime requires a newer kernel than the one currently booted.", + "verification": "uname -r", + "failure_help": "Install the documented kernel, reboot, then run ./install.sh --resume.", + }, + "gpu-device-nodes-missing": { + "title": "GPU device nodes are missing", + "explanation": "Linux must expose /dev/kfd and a DRM render node before ROCm can execute.", + "verification": "ls -l /dev/kfd /dev/dri/render*", + "failure_help": "Complete the ROCm system preparation, reboot, and resume.", + }, + "gpu-groups-missing": { + "title": "GPU access permission required", + "explanation": "Your account needs video and render group membership to access the AMD compute device.", + "verification": "groups", + "failure_help": "Add the groups, reboot or sign out completely, then resume.", + }, + "kfd-permission-denied": { + "title": "GPU device access denied", + "explanation": "The compute device exists but is not accessible to the current account.", + "verification": "test -r /dev/kfd -a -w /dev/kfd", + "failure_help": "Verify video/render membership and udev permissions, then sign in again.", + }, + "rocminfo-failed": { + "title": "ROCm hardware probe failed", + "explanation": "ROCm cannot enumerate a usable GPU agent.", + "verification": "rocminfo", + "failure_help": "Review rocminfo output and repair the system ROCm installation.", + }, + "amd-architecture-unverified": { + "title": "AMD architecture could not be verified", + "explanation": "A supported gfx architecture must be reported before installing GPU Torch.", + "verification": "rocminfo | grep -E 'gfx[0-9]+'", + "failure_help": "Repair ROCm or choose CPU.", + }, + "unsupported-amd-architecture": { + "title": "AMD GPU is not qualified", + "explanation": "The detected GPU family is not in this release manifest.", + "verification": "rocminfo | grep -E 'gfx[0-9]+'", + "failure_help": "Choose CPU; do not install a guessed ROCm wheel.", + }, + "rocm-userspace-incomplete": { + "title": "ROCm system libraries required", + "explanation": "The AMD PyTorch wheel depends on ROCm libraries supplied by the operating system installation.", + "verification": "find /opt/rocm -name 'libamdhip64.so*' -o -name 'libMIOpen.so*'", + "failure_help": "Install the displayed allowlisted ROCm package, then resume.", + }, + "experimental-opt-in-required": { + "title": "Experimental platform confirmation required", + "explanation": "This platform can be tested but is not represented as supported.", + "verification": "Review docs/runtime-support-matrix.md.", + "failure_help": "Rerun with --allow-experimental or select CPU.", + }, + "profile-lock-missing": { + "title": "Release dependency lock is missing", + "explanation": "MoDiff will not install an unmanaged accelerator build.", + "verification": "Check requirements/profiles for the selected profile.", + "failure_help": "Restore the release files or reinstall MoDiff.", + }, +} + +DOCUMENTATION_URL = "docs/accelerator-installation.md" +PHASES = ["detect", "plan", "system-preparation", "toolchain", "backend", "client", "validation", "complete"] + + +def enrich_issue(code: str, message: str, *, blocking: bool, command: str | None = None, + action: dict | None = None, requires_reboot: bool = False) -> dict: + help_item = CATALOG.get(code, {}) + return { + "id": code, + "code": code, + "title": help_item.get("title", code.replace("-", " ").title()), + "status": "blocked" if blocking else "warning", + "message": message, + "explanation": help_item.get("explanation", message), + "automatic": bool(action), + "blocking": blocking, + "requires_admin": bool(action and action.get("requires_admin")), + "requires_reboot": requires_reboot, + "command": command, + "guided_command": command, + "verification": help_item.get("verification"), + "documentation_url": DOCUMENTATION_URL, + "failure_help": help_item.get("failure_help", "Run ./install.sh --system-check --json for details."), + "action": action, + } diff --git a/requirements/profiles/amd-pytorch-windows.txt b/requirements/profiles/amd-pytorch-windows.txt new file mode 100644 index 0000000..2bae69e --- /dev/null +++ b/requirements/profiles/amd-pytorch-windows.txt @@ -0,0 +1,2 @@ +# AMD Windows wheels must be updated only with a release-qualified direct source. +-e . diff --git a/requirements/profiles/amd-rocm-linux.txt b/requirements/profiles/amd-rocm-linux.txt new file mode 100644 index 0000000..87ec66e --- /dev/null +++ b/requirements/profiles/amd-rocm-linux.txt @@ -0,0 +1,5 @@ +https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torch-2.9.1%2Brocm7.2.0.lw.git7e1940d4-cp312-cp312-linux_x86_64.whl --hash=sha256:199ba10a8b8b5a7eef64985bf87a41f0f4b28d49b1b861aa2768f6c5314c9b86 +https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchvision-0.24.0%2Brocm7.2.0.gitb919bd0c-cp312-cp312-linux_x86_64.whl --hash=sha256:071667fd26e9af4e05127eabc572015d1f629e318096abc854b6c946f346359b +https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/torchaudio-2.9.0%2Brocm7.2.0.gite3c6ee2b-cp312-cp312-linux_x86_64.whl --hash=sha256:cf78fd7774ca6187254a63051d07c46ab1f4bbd5a47e64b8d372d3c58e52979a +https://repo.radeon.com/rocm/manylinux/rocm-rel-7.2/triton-3.5.1%2Brocm7.2.0.gita272dfa8-cp312-cp312-linux_x86_64.whl --hash=sha256:73d435248c3a207fec6f5a7a2aba631c7079a61230ce4aaa8cde02e800024b47 +-e . diff --git a/requirements/profiles/apple-mps.txt b/requirements/profiles/apple-mps.txt new file mode 100644 index 0000000..488a71d --- /dev/null +++ b/requirements/profiles/apple-mps.txt @@ -0,0 +1,4 @@ +torch==2.8.0 +torchvision==0.23.0 +torchaudio==2.8.0 +-e .[apple-silicon] diff --git a/requirements/profiles/cpu.txt b/requirements/profiles/cpu.txt new file mode 100644 index 0000000..73b0526 --- /dev/null +++ b/requirements/profiles/cpu.txt @@ -0,0 +1,4 @@ +torch==2.8.0 --index-url https://download.pytorch.org/whl/cpu +torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cpu +torchaudio==2.8.0 --index-url https://download.pytorch.org/whl/cpu +-e . diff --git a/requirements/profiles/nvidia-cuda.txt b/requirements/profiles/nvidia-cuda.txt new file mode 100644 index 0000000..6c48101 --- /dev/null +++ b/requirements/profiles/nvidia-cuda.txt @@ -0,0 +1,4 @@ +torch==2.8.0 --index-url https://download.pytorch.org/whl/cu128 +torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cu128 +torchaudio==2.8.0 --index-url https://download.pytorch.org/whl/cu128 +-e .[cuda] diff --git a/run.ps1 b/run.ps1 new file mode 100644 index 0000000..7f3a3a2 --- /dev/null +++ b/run.ps1 @@ -0,0 +1,16 @@ +$ErrorActionPreference = "Stop" +Set-Location $PSScriptRoot +$python = Join-Path $PSScriptRoot ".venv\Scripts\python.exe" +$state = Join-Path $PSScriptRoot ".venv\modiff-profile.json" +if (!(Test-Path $python) -or !(Test-Path $state)) { throw "Managed environment missing. Run .\install.ps1 -Accelerator auto first." } +$profile = Get-Content $state -Raw | ConvertFrom-Json +if ($profile.profile -eq "amd-rocm-linux") { + if (!$env:TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL) { $env:TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL = "1" } + if (!$env:ROCM_PATH) { $env:ROCM_PATH = "/opt/rocm" } + if (!$env:HIP_PATH) { $env:HIP_PATH = "/opt/rocm" } + $env:LD_LIBRARY_PATH = "/opt/rocm/lib" + $(if ($env:LD_LIBRARY_PATH) { ":$env:LD_LIBRARY_PATH" } else { "" }) +} +& $python -c 'from modiff.hardware import get_hardware_snapshot; from modiff.runtime_profile import runtime_profile; import sys; sys.exit(0 if runtime_profile(get_hardware_snapshot(refresh=True))["execution_ready"] else 2)' +if ($LASTEXITCODE -ne 0) { throw "Managed runtime profile is not execution-ready. Run .\install.ps1 -Accelerator auto -Repair -SystemCheck -Json." } +& $python main.py @args +exit $LASTEXITCODE diff --git a/scripts/lock_accelerator_wheels.py b/scripts/lock_accelerator_wheels.py new file mode 100644 index 0000000..1a30c94 --- /dev/null +++ b/scripts/lock_accelerator_wheels.py @@ -0,0 +1,42 @@ +"""Generate a hash-locked direct-wheel requirements file for a release manifest profile.""" +from __future__ import annotations + +import argparse +import hashlib +import json +import tempfile +import urllib.parse +import urllib.request +from pathlib import Path + +from modiff.runtime_profile import MANIFEST_PATH + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--profile", default="amd-rocm-linux") + parser.add_argument("--output", type=Path) + args = parser.parse_args() + manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) + profile = manifest["profiles"][args.profile] + requirement = Path(profile["requirements"]) + output = args.output or Path(__file__).resolve().parents[1] / requirement + urls = [line.strip().split()[0] for line in output.read_text(encoding="utf-8").splitlines() if line.strip().startswith("https://")] + lines = [] + with tempfile.TemporaryDirectory(prefix="modiff-wheel-lock-") as temporary: + for url in urls: + name = urllib.parse.unquote(url.rsplit("/", 1)[-1]) + target = Path(temporary) / name + digest = hashlib.sha256() + with urllib.request.urlopen(url) as response, target.open("wb") as handle: + while chunk := response.read(8 * 1024 * 1024): + handle.write(chunk) + digest.update(chunk) + lines.append(f"{url} --hash=sha256:{digest.hexdigest()}") + lines.append("-e .") + output.write_text("\n".join(lines) + "\n", encoding="utf-8") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_accelerator_manifest.py b/tests/test_accelerator_manifest.py new file mode 100644 index 0000000..2125d8b --- /dev/null +++ b/tests/test_accelerator_manifest.py @@ -0,0 +1,18 @@ +import unittest + +from modiff.runtime_profile import load_manifest + + +class AcceleratorManifestTests(unittest.TestCase): + def test_all_public_profiles_are_release_pinned(self): + manifest = load_manifest() + self.assertEqual(set(manifest["profiles"]), {"nvidia-cuda", "amd-rocm-linux", "amd-pytorch-windows", "apple-mps", "cpu"}) + for name, profile in manifest["profiles"].items(): + with self.subTest(name=name): + self.assertRegex(profile["torch"], r"^\d+\.\d+\.\d+(?:\+rocm\d+\.\d+(?:\.\d+)?)?$") + self.assertTrue(profile["requirements"]) + self.assertTrue(profile["sources"]) + self.assertIn(profile["tier"], {"supported", "preview", "conditional"}) + if name == "amd-rocm-linux": + self.assertEqual(set(profile["wheel_hashes"]), {"torch", "torchvision", "torchaudio", "triton"}) + self.assertTrue(all(len(value) == 64 for value in profile["wheel_hashes"].values())) diff --git a/tests/test_install_detection.py b/tests/test_install_detection.py new file mode 100644 index 0000000..5161eaf --- /dev/null +++ b/tests/test_install_detection.py @@ -0,0 +1,55 @@ +import unittest +from unittest.mock import patch + +from modiff.install import _amd_qualification, resolve_profile +from modiff.runtime_profile import load_manifest + + +class InstallDetectionTests(unittest.TestCase): + def host(self, **changes): + return {"os": "linux", "architecture": "x86_64", "wsl": False, "nvidia_usable": False, "amd_usable": False, "mps_candidate": False, **changes} + + def test_auto_profiles(self): + self.assertEqual(resolve_profile("auto", self.host(nvidia_usable=True)), "nvidia-cuda") + self.assertEqual(resolve_profile("auto", self.host(amd_usable=True)), "amd-rocm-linux") + self.assertEqual(resolve_profile("auto", self.host()), "cpu") + + def test_hybrid_requires_selection(self): + with self.assertRaisesRegex(ValueError, "explicit"): + resolve_profile("auto", self.host(nvidia_usable=True, amd_usable=True)) + + def test_wsl_is_not_guessed(self): + self.assertEqual(resolve_profile("auto", self.host(wsl=True, nvidia_usable=True)), "cpu") + + def test_noninteractive_experimental_auto_uses_cpu_without_opt_in(self): + host = self.host(amd_usable=True, os_version="26.04") + self.assertEqual(resolve_profile("auto", host, non_interactive=True), "cpu") + self.assertEqual(resolve_profile("auto", host, non_interactive=True, allow_experimental=True), "amd-rocm-linux") + + def test_qualified_strix_halo_host_is_supported(self): + host = self.host( + os_id="ubuntu", os_version="24.04.3", kernel="6.14.1018", amd_candidate=True, + amd_architectures=["gfx1151"], kfd_present=True, kfd_accessible=True, + render_nodes=["/dev/dri/renderD128"], groups=["video", "render"], rocminfo_returncode=0, + ) + libraries = " ".join(( + "libamdhip64.so.7 libMIOpen.so.1 libhipblas.so.3 libhipblaslt.so.1 libhipfft.so.0 " + "libhiprand.so.1 libhiprtc.so.7 libhipsolver.so.1 libhipsparse.so.4 libhipsparselt.so.0 " + "librccl.so.1 librocblas.so.5 librocsolver.so.0 libroctracer64.so.4 libroctx64.so.4" + ).split()) + with patch("modiff.install._command", return_value={"returncode": 0, "stdout": libraries, "stderr": ""}): + tier, issues = _amd_qualification(host, load_manifest()["profiles"]["amd-rocm-linux"]) + self.assertEqual(tier, "supported") + self.assertEqual(issues, []) + + def test_missing_permissions_are_guided_not_mutated(self): + host = self.host( + os_id="ubuntu", os_version="26.04", kernel="7.0.0", amd_candidate=True, + amd_architectures=[], kfd_present=False, kfd_accessible=False, + render_nodes=[], groups=[], rocminfo_returncode=1, rocminfo_error="permission denied", + ) + with patch("modiff.install._command", return_value={"returncode": 0, "stdout": "", "stderr": ""}): + tier, issues = _amd_qualification(host, load_manifest()["profiles"]["amd-rocm-linux"]) + self.assertEqual(tier, "experimental") + group_issue = next(issue for issue in issues if issue["code"] == "gpu-groups-missing") + self.assertIn("sudo usermod", group_issue["guided_command"]) diff --git a/tests/test_install_guidance.py b/tests/test_install_guidance.py new file mode 100644 index 0000000..7a9b340 --- /dev/null +++ b/tests/test_install_guidance.py @@ -0,0 +1,103 @@ +import json +import subprocess +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from modiff import install +from modiff.setup_catalog import PHASES, enrich_issue + + +class GuidedInstallerTests(unittest.TestCase): + def test_structured_issue_contains_help_and_safe_action_metadata(self): + issue = enrich_issue( + "gpu-groups-missing", "groups required", blocking=True, + command="sudo usermod -a -G video,render test", + action={"id": "linux-add-gpu-groups", "argv": ["usermod", "-a", "-G", "video,render", "test"], "requires_admin": True}, + requires_reboot=True, + ) + self.assertEqual(issue["status"], "blocked") + self.assertTrue(issue["requires_admin"]) + self.assertTrue(issue["requires_reboot"]) + self.assertTrue(issue["verification"]) + self.assertTrue(issue["failure_help"]) + + def test_system_action_allowlist_rejects_modified_commands(self): + self.assertTrue(install._action_is_allowed({"id": "ubuntu-install-amdrocm-gfx1151", "argv": ["apt", "install", "-y", "amdrocm-gfx1151"]})) + self.assertFalse(install._action_is_allowed({"id": "ubuntu-install-amdrocm-gfx1151", "argv": ["apt", "remove", "-y", "amdrocm-gfx1151"]})) + self.assertFalse(install._action_is_allowed({"id": "unknown", "argv": ["sh", "-c", "anything"]})) + + def test_group_detection_is_safe_without_posix_grp(self): + with patch.object(install, "grp", None): + self.assertEqual(install._groups(), []) + + def test_malformed_journal_recovers_to_a_new_valid_state(self): + with tempfile.TemporaryDirectory() as temporary: + journal = Path(temporary) / "install-state.json" + journal.write_text("not json", encoding="utf-8") + with patch.object(install, "JOURNAL_PATH", journal), patch.object(install, "MANAGED_ROOT", Path(temporary)): + self.assertEqual(install._read_journal(), {}) + written = install._write_journal(status="running", current_phase="detect") + self.assertEqual(written["schema_version"], 1) + self.assertEqual(json.loads(journal.read_text(encoding="utf-8"))["status"], "running") + + def test_phase_record_updates_matching_setup_steps(self): + with tempfile.TemporaryDirectory() as temporary: + journal = Path(temporary) / "install-state.json" + journal.write_text(json.dumps({"steps": [{"id": "toolchain", "phase": "toolchain", "status": "pending"}]}), encoding="utf-8") + with patch.object(install, "JOURNAL_PATH", journal), patch.object(install, "MANAGED_ROOT", Path(temporary)): + install._record_phase("toolchain") + state = json.loads(journal.read_text(encoding="utf-8")) + self.assertEqual(state["steps"][0]["status"], "complete") + self.assertEqual(state["current_phase"], "toolchain") + + def test_staged_promotion_preserves_previous_environment(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + current, staged, previous = root / ".venv", root / ".venv.next", root / ".venv.previous" + current.mkdir(); staged.mkdir() + (current / "marker").write_text("old", encoding="utf-8") + (staged / "marker").write_text("new", encoding="utf-8") + with patch.object(install, "VENV", current), patch.object(install, "STAGED_VENV", staged), patch.object(install, "PREVIOUS_VENV", previous): + install._promote_staged_environment() + self.assertEqual((current / "marker").read_text(encoding="utf-8"), "new") + self.assertEqual((previous / "marker").read_text(encoding="utf-8"), "old") + + def test_backend_only_skips_node_provisioning(self): + with patch.object(install, "_ensure_node") as ensure_node: + result = install._install_client(backend_only=True) + self.assertEqual(result, {"status": "skipped", "reason": "--backend-only"}) + ensure_node.assert_not_called() + + def test_missing_sibling_client_skips_node_provisioning(self): + with patch.object(install, "_client_path", return_value=None), patch.object(install, "_ensure_node") as ensure_node: + result = install._install_client(backend_only=False) + self.assertEqual(result, {"status": "skipped", "reason": "sibling client not found"}) + ensure_node.assert_not_called() + + def test_client_build_provisions_node_on_demand(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + client = root / "MoDiff-client" + diagnostics = root / "diagnostics" + client.mkdir() + toolchains = {"node": "/tools/node", "npm": "/tools/npm", "node_version": "24.12.0"} + completed = subprocess.CompletedProcess([], 0, stdout="ok", stderr="") + with ( + patch.object(install, "_client_path", return_value=client), + patch.object(install, "_ensure_node", return_value=toolchains) as ensure_node, + patch.object(install, "DIAGNOSTICS_DIR", diagnostics), + patch.object(install.subprocess, "run", return_value=completed) as run, + ): + result = install._install_client(backend_only=False) + self.assertEqual(result, {"status": "complete", "path": str(client), "node": "24.12.0"}) + ensure_node.assert_called_once_with() + self.assertEqual([call.args[0] for call in run.call_args_list], [["/tools/npm", "ci"], ["/tools/npm", "run", "build"]]) + + def test_phase_contract_is_stable(self): + self.assertEqual(PHASES, ["detect", "plan", "system-preparation", "toolchain", "backend", "client", "validation", "complete"]) + + +if __name__ == "__main__": + unittest.main() From 57b9bb93b0cf5434b0a5d57fccdcc1cdcb1c6d4b Mon Sep 17 00:00:00 2001 From: Sourav Das Date: Mon, 13 Jul 2026 16:23:55 +0530 Subject: [PATCH 2/7] chore: updated the rebranding stuff --- .github/pull_request_template.md | 2 +- CONTRIBUTING.md | 6 +- README.md | 9 +- docs/README.md | 3 +- docs/api-reference.md | 2 +- docs/modiff-backend-namespace.md | 55 - mellon/NodeBase.py | 3 - mellon/__init__.py | 5 - mellon/client.py | 3 - mellon/config.py | 3 - mellon/modelstore.py | 3 - mellon/preflight.py | 8 - mellon/server.py | 3 - modiff/NodeBase.py | 4 +- modiff/preflight.py | 11 +- modiff/server.py | 46 +- modules/ModularDiffusers/README.md | 7 +- modules/ModularDiffusers/dynamic_node.py | 5 +- modules/ModularDiffusers/loaders.py | 4 +- modules/ModularDiffusers/modular_utils.py | 631 +++++------ modules/ModularDiffusers/pipeline_schema.py | 1086 +++++++++++++++++++ tests/test_node_base.py | 45 +- tests/test_pipeline_schema.py | 69 ++ tests/test_runtime_status.py | 9 + 24 files changed, 1518 insertions(+), 504 deletions(-) delete mode 100644 docs/modiff-backend-namespace.md delete mode 100644 mellon/NodeBase.py delete mode 100644 mellon/__init__.py delete mode 100644 mellon/client.py delete mode 100644 mellon/config.py delete mode 100644 mellon/modelstore.py delete mode 100644 mellon/preflight.py delete mode 100644 mellon/server.py create mode 100644 modules/ModularDiffusers/pipeline_schema.py create mode 100644 tests/test_pipeline_schema.py diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3db3c92..08b721b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,7 +12,7 @@ ## Compatibility, Security, And Proof -- [ ] Existing graph, HTTP, WebSocket, storage, and `mellon` import compatibility is preserved or the migration is described. +- [ ] Current graph, HTTP, WebSocket, storage, and Python package contracts are preserved or deliberately migrated with tests. - [ ] Client-facing changes include the matching MoDiff-client change and bundled-client update plan. - [ ] File access, custom code, model loading, tokens, origins, and request-size implications were reviewed where relevant. - [ ] Unit/contract, registry, live backend, accelerator, and real model-generation evidence are reported separately. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f1e040a..f59c442 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ MoDiff is an experimental local backend with a separately maintained frontend bundle. Contributions should preserve the local-only security boundary, existing graph/API compatibility, and reproducible dependency state. -Before starting, read [SECURITY.md](SECURITY.md) and [docs/modiff-backend-namespace.md](docs/modiff-backend-namespace.md). +Before starting, read [SECURITY.md](SECURITY.md) and the relevant guide in [docs/README.md](docs/README.md). ## Development setup @@ -23,8 +23,8 @@ Do not commit `config.ini`, `.env` files, model caches, generated outputs, local ## Backend conventions -- Put canonical backend implementation in `modiff/`. Keep `mellon/` limited to thin compatibility wrappers. -- Preserve existing HTTP/WebSocket field names and legacy graph fallbacks unless a deliberate migration includes client changes and compatibility tests. +- Put backend framework implementation in `modiff/` and built-in node implementations in `modules/`. +- Preserve current HTTP/WebSocket and graph-storage contracts unless a deliberate migration includes client changes and contract tests. - Keep hardware probes non-fatal and retain CPU fallback when CUDA or MPS discovery fails. - Treat file access, custom-module installation, remote code, token handling, and mutating routes as security-sensitive changes. - Avoid importing the full model registry from lightweight diagnostics such as preflight. diff --git a/README.md b/README.md index 055f182..b95de72 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ Some nodes require optional packages, specific model repositories, substantial a | Path | Purpose | | -------------- | -------------------------------------------------------------------------------------------------------------------- | | `modiff/` | Canonical backend package. | -| `mellon/` | Thin compatibility shims for older imports and automation. New code should use `modiff.*`. | | `modules/` | Built-in node implementations and registry metadata. | | `custom/` | Locally installed custom Python modules; ignored except for repository placeholders. | | `data/graphs/` | Curated graph examples that are safe to version. | @@ -176,7 +175,7 @@ Run preflight before investigating model-specific failures: uv run python -m modiff.preflight --json --check-port 8088 --fail-on-error ``` -The report checks Python, required imports, CUDA/MPS/CPU discovery, cache and data paths, and port state without importing the full node registry. The older `python -m mellon.preflight` command remains a compatibility entrypoint. +The report checks Python, required imports, CUDA/MPS/CPU discovery, cache and data paths, and port state without importing the full node registry. Useful local endpoints include: @@ -188,11 +187,9 @@ Useful local endpoints include: See [docs/api-reference.md](docs/api-reference.md) for route groups and trust implications. These routes are designed for the bundled same-origin client and are not an authenticated public web API. -## Modular Diffusers and compatibility +## Modular Diffusers -The Modular Diffusers integration is documented in [modules/ModularDiffusers/README.md](modules/ModularDiffusers/README.md). It relies on experimental upstream APIs and intentionally retains external identifiers such as `MellonPipelineConfig`, `MellonParam`, and `diffusers.modular_pipelines.mellon_node_utils` where upstream compatibility requires them. - -The physical `mellon` package is also retained as a thin import shim. New implementation belongs in `modiff`; compatibility policy and removal criteria are documented in [docs/modiff-backend-namespace.md](docs/modiff-backend-namespace.md). +The Modular Diffusers integration is documented in [modules/ModularDiffusers/README.md](modules/ModularDiffusers/README.md). MoDiff owns the pipeline configuration schema used by its dynamic node contracts while relying on upstream Diffusers for model and pipeline execution. ## Updating diff --git a/docs/README.md b/docs/README.md index 1a8a737..39d045c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,7 +9,6 @@ This directory contains the durable technical guides for the MoDiff backend. Sta | Install, configure, launch, or update MoDiff | [Project README](../README.md) and [`config.example.ini`](../config.example.ini) | | Understand HTTP and WebSocket surfaces | [API reference](api-reference.md) | | Diagnose startup, ports, devices, downloads, media, or stale UI | [Troubleshooting](troubleshooting.md) | -| Understand the `modiff` namespace and retained `mellon` compatibility shims | [Backend namespace](modiff-backend-namespace.md) | | Build Modular Diffusers graphs and understand experimental compatibility | [Modular Diffusers guide](../modules/ModularDiffusers/README.md) | | Contribute code, nodes, dependencies, or client-facing changes | [Contributing](../CONTRIBUTING.md) | | Understand the local-only trust boundary or report a vulnerability | [Security policy](../SECURITY.md) | @@ -19,6 +18,6 @@ This directory contains the durable technical guides for the MoDiff backend. Sta Public documentation should describe the supported current behavior and make its proof level clear. Keep commands executable from the directory stated, keep route/config/dependency names synchronized with code, and distinguish static or mocked validation from a live backend or real model-generation result. -Do not publish credentials, private media, personal paths, machine inventories, unredacted provenance, or dated internal execution trackers. Compatibility names retained for existing graphs, clients, imports, or upstream APIs should be explained instead of mechanically renamed. +Do not publish credentials, private media, personal paths, machine inventories, unredacted provenance, or dated internal execution trackers. Document supported product identifiers and contracts exactly as they appear in the current implementation. When behavior changes, update the root README and the narrow guide in the same contribution. Verify repository-relative links and run the validation described in [CONTRIBUTING.md](../CONTRIBUTING.md) before requesting review. diff --git a/docs/api-reference.md b/docs/api-reference.md index 5476734..6a04757 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -86,4 +86,4 @@ Uploads are written under configured data subdirectories. Studio outputs, blocks ## Compatibility -Stable product routes such as `/graph`, `/queue`, `/studio_outputs`, and `/workflows/share` should remain compatible with the separate MoDiff-client repository. The older Python `mellon` namespace and external upstream symbols are covered by [modiff-backend-namespace.md](modiff-backend-namespace.md); they are not alternate HTTP route prefixes. +Stable product routes such as `/graph`, `/queue`, `/studio_outputs`, and `/workflows/share` should remain compatible with the separate MoDiff-client repository. Python integrations should use the `modiff` package, and backend routes do not use a package-name prefix. diff --git a/docs/modiff-backend-namespace.md b/docs/modiff-backend-namespace.md deleted file mode 100644 index a94b610..0000000 --- a/docs/modiff-backend-namespace.md +++ /dev/null @@ -1,55 +0,0 @@ -# MoDiff Backend Namespace - -The backend product and canonical Python namespace are **MoDiff** and `modiff`. New code, launchers, documentation, and user-facing logs should use those names. - -The physical `mellon` package remains only as a compatibility layer. Its modules re-export canonical `modiff.*` objects so older scripts, custom nodes, and automation can migrate without maintaining two implementations. - -## Canonical and compatibility entrypoints - -| Purpose | Canonical | Compatibility | -| --------------- | ---------------------------- | ---------------------------- | -| Backend package | `modiff` | `mellon` | -| Node base | `modiff.NodeBase` | `mellon.NodeBase` | -| Server | `modiff.server` | `mellon.server` | -| Configuration | `modiff.config` | `mellon.config` | -| Model store | `modiff.modelstore` | `mellon.modelstore` | -| Client helpers | `modiff.client` | `mellon.client` | -| Preflight | `python -m modiff.preflight` | `python -m mellon.preflight` | - -Compatibility wrappers should stay thin. Fixes belong in the canonical module and should be exercised through both import paths when identity or import order matters. - -## Current compatibility rules - -- Prefer `python -m modiff.preflight` for new diagnostics. -- Keep legacy `mellon.*` imports and `python -m mellon.preflight` working while the shim package is supported. -- Keep established HTTP and WebSocket contracts, including `/graph`, `/queue`, `/studio_outputs`, and `/workflows/share`, compatible with the separately versioned client. -- Normalize legacy saved graph paths to `data/graphs/modiff` when possible while retaining fallback reads for older `data/graphs/mellon` references. -- Preserve legacy error/metadata aliases at compatibility boundaries when older clients or automation may still read them. -- Treat upstream Diffusers names such as `MellonPipelineConfig`, `MellonParam`, and `diffusers.modular_pipelines.mellon_node_utils` as external API identifiers. They cannot be mechanically renamed inside MoDiff. -- Do not rename existing Hugging Face repository IDs or dataset paths containing `mellon` unless the external resource itself moves. - -These compatibility strings are not evidence that the active backend implementation still lives under the old package. Conversely, removing old Git commit history does not authorize deleting compatibility surfaces or upstream copyright/license notices. - -## Validation expectations - -Namespace changes should prove all of the following in fresh Python processes: - -- Canonical imports work before the full registry has been imported. -- Legacy wrappers resolve to the same classes, configuration object, and server singleton as canonical imports. -- Direct `modiff.NodeBase` and `mellon.NodeBase` imports do not leave a partially initialized module registry. -- Both preflight entrypoints produce the same canonical namespace metadata. -- Normal backend startup still discovers the full module registry. - -Import-order regressions can be hidden by a test suite that imports `modules` first, so subprocess coverage is required for this boundary. - -## Removal criteria - -There is no scheduled removal release. The `mellon` shim package can be retired only after all of these are true: - -- Backend entrypoints, local scripts, packaged launchers, public docs, and supported external integrations use `modiff`. -- Custom-node and pipeline authors have documented replacements for every MoDiff-owned legacy symbol. -- External Diffusers helper names have an upstream migration path or remain isolated behind an adapter. -- At least one released migration window includes deprecation messaging and compatibility tests. -- Saved graphs, output history, workflow shares, WebSocket state, and stable HTTP behavior remain usable after migration. - -Until then, preserve the shim and document intentional legacy names rather than attempting a hard-zero text replacement. diff --git a/mellon/NodeBase.py b/mellon/NodeBase.py deleted file mode 100644 index 6630514..0000000 --- a/mellon/NodeBase.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Legacy compatibility wrapper for ``modiff.NodeBase``.""" - -from modiff.NodeBase import * # noqa: F401,F403 diff --git a/mellon/__init__.py b/mellon/__init__.py deleted file mode 100644 index d3d8e8f..0000000 --- a/mellon/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Legacy compatibility package for MoDiff. - -New code should import from ``modiff``. This package remains as a thin shim so -older scripts and custom nodes that import ``mellon.*`` keep working. -""" diff --git a/mellon/client.py b/mellon/client.py deleted file mode 100644 index f7c344c..0000000 --- a/mellon/client.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Legacy compatibility wrapper for ``modiff.client``.""" - -from modiff.client import * # noqa: F401,F403 diff --git a/mellon/config.py b/mellon/config.py deleted file mode 100644 index 517559e..0000000 --- a/mellon/config.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Legacy compatibility wrapper for ``modiff.config``.""" - -from modiff.config import * # noqa: F401,F403 diff --git a/mellon/modelstore.py b/mellon/modelstore.py deleted file mode 100644 index d8b787c..0000000 --- a/mellon/modelstore.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Legacy compatibility wrapper for ``modiff.modelstore``.""" - -from modiff.modelstore import * # noqa: F401,F403 diff --git a/mellon/preflight.py b/mellon/preflight.py deleted file mode 100644 index daa1fb2..0000000 --- a/mellon/preflight.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Legacy compatibility wrapper for ``modiff.preflight``.""" - -from modiff.preflight import * # noqa: F401,F403 -from modiff.preflight import main - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/mellon/server.py b/mellon/server.py deleted file mode 100644 index 354ded4..0000000 --- a/mellon/server.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Legacy compatibility wrapper for ``modiff.server``.""" - -from modiff.server import * # noqa: F401,F403 diff --git a/modiff/NodeBase.py b/modiff/NodeBase.py index ba200a9..a3eefd4 100644 --- a/modiff/NodeBase.py +++ b/modiff/NodeBase.py @@ -468,8 +468,8 @@ def get_signal_value(self, field: str, timeout: int = 5): raise ValueError("WebSocket session not available for this node.") result = server.get_signal_value(self.node_id, field, self._sid, timeout=timeout) - if isinstance(result, dict) and ('__MODIFF_ERROR' in result or '__MELLON_ERROR' in result): - raise ValueError(result.get('__MODIFF_ERROR') or result.get('__MELLON_ERROR')) + if isinstance(result, dict) and '__MODIFF_ERROR' in result: + raise ValueError(result['__MODIFF_ERROR']) return result diff --git a/modiff/preflight.py b/modiff/preflight.py index 80363b0..92a3a7e 100644 --- a/modiff/preflight.py +++ b/modiff/preflight.py @@ -52,7 +52,6 @@ } CANONICAL_ENTRYPOINT = "python -m modiff.preflight" -LEGACY_ENTRYPOINT = "python -m mellon.preflight" def setup_guidance(root): @@ -70,7 +69,6 @@ def setup_guidance(root): "Use uv run main.py after uv sync, or python main.py after activating a pip-managed environment.", "On Apple Silicon macOS, use the apple-silicon profile so torch resolves from normal PyPI/MPS-capable wheels.", "CUDA acceleration extras are intended for Linux/Windows GPU installs and are not part of the macOS setup path.", - "The mellon package and entrypoints are compatibility shims during the MoDiff namespace migration.", ], } @@ -227,10 +225,7 @@ def build_report(args): "namespace": { "productName": "MoDiff", "canonicalPackage": "modiff", - "legacyPackage": "mellon", "canonicalPreflightCommand": CANONICAL_ENTRYPOINT, - "legacyPreflightCommand": LEGACY_ENTRYPOINT, - "legacyEntrypointSupported": True, }, "setup": setup_guidance(root), "checkedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), @@ -289,11 +284,7 @@ def print_human(report): print(f"- Preferred: {preferred_command}") print(f"- Pip fallback: {pip_command}") print(f"- Recheck: {report['namespace']['canonicalPreflightCommand']} --check-port {report['server']['port']}") - print( - "Namespace: use " - f"{report['namespace']['canonicalPreflightCommand']} " - f"(legacy {report['namespace']['legacyPreflightCommand']} remains supported)" - ) + print(f"Namespace: use {report['namespace']['canonicalPreflightCommand']}") def main(): diff --git a/modiff/server.py b/modiff/server.py index 895aab2..8225e81 100644 --- a/modiff/server.py +++ b/modiff/server.py @@ -1086,7 +1086,6 @@ async def _main_worker(self): terminal_status = "failed" traceback_text = ( getattr(e, 'modiff_traceback', None) - or getattr(e, 'mellon_traceback', None) or traceback.format_exc() ) logger.error(f"Error occurred in {traceback_text}") @@ -1095,8 +1094,8 @@ async def _main_worker(self): e, task_id=task_id, sid=self.current_task["sid"] if self.current_task else None, - node_id=getattr(e, 'modiff_node_id', None) or getattr(e, 'mellon_node_id', None), - node_name=getattr(e, 'modiff_node_name', None) or getattr(e, 'mellon_node_name', None), + node_id=getattr(e, 'modiff_node_id', None), + node_name=getattr(e, 'modiff_node_name', None), traceback_text=traceback_text, ) self._record_auto_resource_failure(e, { @@ -1546,21 +1545,6 @@ async def fileGet(self, request): if not file_path.is_absolute(): file_path = Path(self.work_dir) / file_path - if not file_path.exists(): - legacy_graph_root = Path(self.data_dir) / 'graphs' / 'mellon' - modiff_graph_root = Path(self.data_dir) / 'graphs' / 'modiff' - try: - legacy_graph_relative_path = file_path.resolve(strict=False).relative_to( - legacy_graph_root.resolve(strict=False) - ) - except ValueError: - legacy_graph_relative_path = None - - if legacy_graph_relative_path is not None: - migrated_file_path = modiff_graph_root / legacy_graph_relative_path - if migrated_file_path.exists(): - file_path = migrated_file_path - if not file_path.exists(): return web.json_response({"error": f"The file {file} does not exist."}, status=404) @@ -2235,21 +2219,6 @@ def _resolve_file_route_path(self, file): if not file_path.is_absolute(): file_path = Path(self.work_dir) / file_path - if not file_path.exists(): - legacy_graph_root = Path(self.data_dir) / 'graphs' / 'mellon' - modiff_graph_root = Path(self.data_dir) / 'graphs' / 'modiff' - try: - legacy_graph_relative_path = file_path.resolve(strict=False).relative_to( - legacy_graph_root.resolve(strict=False) - ) - except ValueError: - legacy_graph_relative_path = None - - if legacy_graph_relative_path is not None: - migrated_file_path = modiff_graph_root / legacy_graph_relative_path - if migrated_file_path.exists(): - file_path = migrated_file_path - if not file_path.exists(): return None @@ -3770,8 +3739,8 @@ def execute_graph(self, graph): 'category': classification.get('category'), 'errorCode': classification.get('error_code'), 'error': str(e) or type(e).__name__, - 'node': getattr(e, 'modiff_node_id', None) or getattr(e, 'mellon_node_id', None), - 'nodeName': getattr(e, 'modiff_node_name', None) or getattr(e, 'mellon_node_name', None), + 'node': getattr(e, 'modiff_node_id', None), + 'nodeName': getattr(e, 'modiff_node_name', None), 'loaderDiagnostics': self._loader_diagnostics_snapshot(), 'nextRetryPlan': self._sanitize_retry_plan_for_hints(retry_plans[next_retry_plan_index]), }) @@ -3854,8 +3823,6 @@ def _connected_output_value(self, *, target_node_id, target_node_name, target_pa setattr(error, 'modiff_node_name', 'Unknown upstream node') setattr(error, 'modiff_target_node_id', target_node_id) setattr(error, 'modiff_target_node_name', target_node_name) - setattr(error, 'mellon_target_node_id', target_node_id) - setattr(error, 'mellon_target_node_name', target_node_name) raise error output = getattr(source_node, 'output', None) @@ -3871,8 +3838,6 @@ def _connected_output_value(self, *, target_node_id, target_node_name, target_pa setattr(error, 'modiff_node_name', source_name) setattr(error, 'modiff_target_node_id', target_node_id) setattr(error, 'modiff_target_node_name', target_node_name) - setattr(error, 'mellon_target_node_id', target_node_id) - setattr(error, 'mellon_target_node_name', target_node_name) raise error return output[source_key] @@ -3970,9 +3935,6 @@ def execute_node(self, id, node, sid, quiet=False): setattr(e, 'modiff_node_id', id) setattr(e, 'modiff_node_name', f"{module}.{action}") setattr(e, 'modiff_traceback', traceback_text) - setattr(e, 'mellon_node_id', id) - setattr(e, 'mellon_node_name', f"{module}.{action}") - setattr(e, 'mellon_traceback', traceback_text) self.queue_message({ "type": "node_error", **self._exception_payload( diff --git a/modules/ModularDiffusers/README.md b/modules/ModularDiffusers/README.md index 4907a00..1c25b09 100644 --- a/modules/ModularDiffusers/README.md +++ b/modules/ModularDiffusers/README.md @@ -13,7 +13,7 @@ MoDiff integrates the experimental [Diffusers Modular Pipelines](https://hugging - **Hub-backed blocks:** supported repositories can provide Modular Diffusers configuration/code used to construct a node interface. - **Resource controls:** loaders expose supported quantization and offload modes, subject to package, model, and hardware compatibility. -Upstream APIs still use identifiers such as `MellonPipelineConfig`, `MellonParam`, and `mellon_node_utils`. Those names are external compatibility surfaces, not the active MoDiff product namespace. See [the namespace policy](../../docs/modiff-backend-namespace.md). +MoDiff owns the pipeline configuration schema that supplies dynamic node fields and defaults. Upstream Diffusers remains responsible for model components and Modular Pipeline execution. ## Setup @@ -87,13 +87,14 @@ Only nodes connected to the submitted graph path execute, but shared component s MoDiff can load compatible custom blocks from the Hugging Face Hub. This is a trust-sensitive feature: +Custom block repositories must publish MoDiff's current `modiff_pipeline_config.json` schema. The loader does not fall +back to earlier extension schemas or filenames. + 1. Review the repository, owner, dependencies, license, and exact commit. 2. Prefer immutable revisions rather than a moving branch. 3. Enable `trust_remote_code` only when the repository requires it and you accept that its Python executes with backend-process permissions. 4. Test on a dedicated local environment without sensitive files in `work_dir`. -The example repository ID `diffusers/gemini-prompt-expander-mellon` intentionally retains an external legacy name. If available and compatible, it can generate a prompt-expansion block that feeds the prompt encoder. Its name should not be mechanically rewritten unless the Hub repository itself moves. - [Watch the custom prompt block demo (MP4)](https://github.com/user-attachments/assets/d68bc8c1-1b1c-478a-b94b-1e498c60a4fc) ## Additional nodes diff --git a/modules/ModularDiffusers/dynamic_node.py b/modules/ModularDiffusers/dynamic_node.py index 0476146..91432c1 100644 --- a/modules/ModularDiffusers/dynamic_node.py +++ b/modules/ModularDiffusers/dynamic_node.py @@ -1,7 +1,7 @@ import logging from diffusers import ModularPipeline -from diffusers.modular_pipelines.mellon_node_utils import MellonPipelineConfig +from .pipeline_schema import MoDiffPipelineConfig as PipelineConfig from modiff.NodeBase import NodeBase from modiff.diffusers_offload import ( @@ -68,7 +68,6 @@ def send_node_definition_with_meta(self, params, label=None, header_color=None): "options": { "": "", "YiYiXu/FLUX.2-klein-4B-modular": "FLUX.2-klein-4B", - "diffusers/gemini-prompt-expander-mellon": "Gemini Prompt Expander", }, "fieldOptions": {"noValidation": True}, }, @@ -100,7 +99,7 @@ def __del__(self): super().__del__() def _get_custom_config(self, repo_id): - custom_config = MellonPipelineConfig.load(repo_id) + custom_config = PipelineConfig.load(repo_id) return custom_config def update_node(self, values, ref): diff --git a/modules/ModularDiffusers/loaders.py b/modules/ModularDiffusers/loaders.py index e808c31..2760da2 100644 --- a/modules/ModularDiffusers/loaders.py +++ b/modules/ModularDiffusers/loaders.py @@ -4,7 +4,7 @@ import torch from diffusers import ComponentSpec, ModularPipeline -from diffusers.modular_pipelines.mellon_node_utils import MellonPipelineConfig +from .pipeline_schema import MoDiffPipelineConfig as PipelineConfig from modiff.NodeBase import NodeBase from modiff.diffusers_offload import ( @@ -913,7 +913,7 @@ def execute( if model_type == "DummyCustomPipeline": # update node param - custom_config = MellonPipelineConfig.load(real_repo_id) + custom_config = PipelineConfig.load(real_repo_id) custom_config.label = "Custom" # update repo_id for DummyCustomPipeline diff --git a/modules/ModularDiffusers/modular_utils.py b/modules/ModularDiffusers/modular_utils.py index 1185b6d..fce83a2 100644 --- a/modules/ModularDiffusers/modular_utils.py +++ b/modules/ModularDiffusers/modular_utils.py @@ -3,7 +3,8 @@ from typing import Any, Dict, Optional from diffusers import Flux2KleinModularPipeline -from diffusers.modular_pipelines.mellon_node_utils import MellonParam, MellonPipelineConfig +from .pipeline_schema import MoDiffParam as PipelineParam +from .pipeline_schema import MoDiffPipelineConfig as PipelineConfig logger = logging.getLogger("modiff") @@ -11,19 +12,19 @@ SDXL_NODE_SPECS = { "controlnet": { "inputs": [ - MellonParam.control_image(), - MellonParam.controlnet_conditioning_scale(), - MellonParam.control_guidance_start(), - MellonParam.control_guidance_end(), - MellonParam.height(), - MellonParam.width(), + PipelineParam.control_image(), + PipelineParam.controlnet_conditioning_scale(), + PipelineParam.control_guidance_start(), + PipelineParam.control_guidance_end(), + PipelineParam.height(), + PipelineParam.width(), ], "model_inputs": [ - MellonParam.controlnet(), + PipelineParam.controlnet(), ], "outputs": [ - MellonParam.controlnet_bundle(display="output"), - MellonParam.doc(), + PipelineParam.controlnet_bundle(display="output"), + PipelineParam.doc(), ], "required_inputs": ["control_image"], "required_model_inputs": ["controlnet"], @@ -31,27 +32,27 @@ }, "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.width(), - MellonParam.height(), - MellonParam.seed(), - MellonParam.num_inference_steps(), - MellonParam.guidance_scale(), - MellonParam.image_latents_with_strength(), - MellonParam.strength(), - MellonParam.controlnet_bundle(display="input"), - MellonParam.ip_adapter(), + PipelineParam.embeddings(display="input"), + PipelineParam.width(), + PipelineParam.height(), + PipelineParam.seed(), + PipelineParam.num_inference_steps(), + PipelineParam.guidance_scale(), + PipelineParam.image_latents_with_strength(), + PipelineParam.strength(), + PipelineParam.controlnet_bundle(display="input"), + PipelineParam.ip_adapter(), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.guider(), - MellonParam.scheduler(), - MellonParam.controlnet_bundle(display="input"), + PipelineParam.unet(), + PipelineParam.guider(), + PipelineParam.scheduler(), + PipelineParam.controlnet_bundle(display="input"), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.latents_preview(), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.latents_preview(), + PipelineParam.doc(), ], "required_inputs": ["embeddings"], "required_model_inputs": ["unet", "scheduler"], @@ -59,14 +60,14 @@ }, "vae_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.image_latents(display="output"), - MellonParam.doc(), + PipelineParam.image_latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["vae"], @@ -74,15 +75,15 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), - MellonParam.negative_prompt(), + PipelineParam.prompt(), + PipelineParam.negative_prompt(), ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt"], "required_model_inputs": ["text_encoders"], @@ -90,14 +91,14 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), + PipelineParam.latents(display="input"), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.images(), - MellonParam.doc(), + PipelineParam.images(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -105,7 +106,7 @@ }, } -SDXL_PIPELINE_CONFIG = MellonPipelineConfig( +SDXL_PIPELINE_CONFIG = PipelineConfig( node_specs=SDXL_NODE_SPECS, label="Stable Diffusion XL", default_repo="stabilityai/stable-diffusion-xl-base-1.0", @@ -120,20 +121,20 @@ QWEN_IMAGE_NODE_SPECS = { "controlnet": { "inputs": [ - MellonParam.control_image(), - MellonParam.controlnet_conditioning_scale(), - MellonParam.control_guidance_start(), - MellonParam.control_guidance_end(), - MellonParam.height(), - MellonParam.width(), + PipelineParam.control_image(), + PipelineParam.controlnet_conditioning_scale(), + PipelineParam.control_guidance_start(), + PipelineParam.control_guidance_end(), + PipelineParam.height(), + PipelineParam.width(), ], "model_inputs": [ - MellonParam.controlnet(), - MellonParam.vae(), + PipelineParam.controlnet(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.controlnet_bundle(display="output"), - MellonParam.doc(), + PipelineParam.controlnet_bundle(display="output"), + PipelineParam.doc(), ], "required_inputs": ["control_image"], "required_model_inputs": ["controlnet", "vae"], @@ -141,25 +142,25 @@ }, "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.width(), - MellonParam.height(), - MellonParam.seed(), - MellonParam.num_inference_steps(50), - MellonParam.guidance_scale(4.5), - MellonParam.image_latents_with_strength(), - MellonParam.strength(), - MellonParam.controlnet_bundle(display="input"), + PipelineParam.embeddings(display="input"), + PipelineParam.width(), + PipelineParam.height(), + PipelineParam.seed(), + PipelineParam.num_inference_steps(50), + PipelineParam.guidance_scale(4.5), + PipelineParam.image_latents_with_strength(), + PipelineParam.strength(), + PipelineParam.controlnet_bundle(display="input"), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.guider(), - MellonParam.scheduler(), - MellonParam.controlnet_bundle(display="input"), + PipelineParam.unet(), + PipelineParam.guider(), + PipelineParam.scheduler(), + PipelineParam.controlnet_bundle(display="input"), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["embeddings"], "required_model_inputs": ["unet", "scheduler"], @@ -167,14 +168,14 @@ }, "vae_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.image_latents(display="output"), - MellonParam.doc(), + PipelineParam.image_latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["vae"], @@ -182,15 +183,15 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), - MellonParam.negative_prompt(), + PipelineParam.prompt(), + PipelineParam.negative_prompt(), ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt"], "required_model_inputs": ["text_encoders"], @@ -198,14 +199,14 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), + PipelineParam.latents(display="input"), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.images(), - MellonParam.doc(), + PipelineParam.images(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -213,7 +214,7 @@ }, } -QWEN_IMAGE_PIPELINE_CONFIG = MellonPipelineConfig( +QWEN_IMAGE_PIPELINE_CONFIG = PipelineConfig( node_specs=QWEN_IMAGE_NODE_SPECS, label="Qwen-Image-2512", default_repo="Qwen/Qwen-Image-2512", @@ -229,20 +230,20 @@ "controlnet": None, "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.seed(), - MellonParam.num_inference_steps(40), - MellonParam.guidance_scale(4.0), - MellonParam.image_latents(display="input"), + PipelineParam.embeddings(display="input"), + PipelineParam.seed(), + PipelineParam.num_inference_steps(40), + PipelineParam.guidance_scale(4.0), + PipelineParam.image_latents(display="input"), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.guider(), - MellonParam.scheduler(), + PipelineParam.unet(), + PipelineParam.guider(), + PipelineParam.scheduler(), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["embeddings", "image_latents"], "required_model_inputs": ["unet", "scheduler"], @@ -250,14 +251,14 @@ }, "vae_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.image_latents(display="output"), - MellonParam.doc(), + PipelineParam.image_latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["vae"], @@ -265,16 +266,16 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), - MellonParam.negative_prompt(), - MellonParam.image(), + PipelineParam.prompt(), + PipelineParam.negative_prompt(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt", "image"], "required_model_inputs": ["text_encoders"], @@ -282,14 +283,14 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), + PipelineParam.latents(display="input"), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.images(), - MellonParam.doc(), + PipelineParam.images(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -297,7 +298,7 @@ }, } -QWEN_IMAGE_EDIT_PIPELINE_CONFIG = MellonPipelineConfig( +QWEN_IMAGE_EDIT_PIPELINE_CONFIG = PipelineConfig( node_specs=QWEN_IMAGE_EDIT_NODE_SPECS, label="Qwen-Image-Edit", default_repo="Qwen/Qwen-Image-Edit", @@ -313,20 +314,20 @@ "controlnet": None, "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.seed(), - MellonParam.num_inference_steps(40), - MellonParam.guidance_scale(4.0), - MellonParam.image_latents(display="input"), + PipelineParam.embeddings(display="input"), + PipelineParam.seed(), + PipelineParam.num_inference_steps(40), + PipelineParam.guidance_scale(4.0), + PipelineParam.image_latents(display="input"), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.guider(), - MellonParam.scheduler(), + PipelineParam.unet(), + PipelineParam.guider(), + PipelineParam.scheduler(), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["embeddings", "image_latents"], "required_model_inputs": ["unet", "scheduler"], @@ -334,14 +335,14 @@ }, "vae_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.image_latents(display="output"), - MellonParam.doc(), + PipelineParam.image_latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["vae"], @@ -349,16 +350,16 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), - MellonParam.negative_prompt(), - MellonParam.image(), + PipelineParam.prompt(), + PipelineParam.negative_prompt(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt", "image"], "required_model_inputs": ["text_encoders"], @@ -366,14 +367,14 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), + PipelineParam.latents(display="input"), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.images(), - MellonParam.doc(), + PipelineParam.images(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -381,7 +382,7 @@ }, } -QWEN_IMAGE_EDIT_PLUS_PIPELINE_CONFIG = MellonPipelineConfig( +QWEN_IMAGE_EDIT_PLUS_PIPELINE_CONFIG = PipelineConfig( node_specs=QWEN_IMAGE_EDIT_PLUS_NODE_SPECS, label="Qwen-Image-Edit-2511", default_repo="Qwen/Qwen-Image-Edit-2511", @@ -396,21 +397,21 @@ "controlnet": None, "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.seed(), - MellonParam.num_inference_steps(50), - MellonParam.guidance_scale(4.0), - MellonParam.layers(4), - MellonParam.image_latents(display="input"), + PipelineParam.embeddings(display="input"), + PipelineParam.seed(), + PipelineParam.num_inference_steps(50), + PipelineParam.guidance_scale(4.0), + PipelineParam.layers(4), + PipelineParam.image_latents(display="input"), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.guider(), - MellonParam.scheduler(), + PipelineParam.unet(), + PipelineParam.guider(), + PipelineParam.scheduler(), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["embeddings", "image_latents"], "required_model_inputs": ["unet", "scheduler"], @@ -418,14 +419,14 @@ }, "vae_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.image_latents(display="output"), - MellonParam.doc(), + PipelineParam.image_latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["vae"], @@ -433,16 +434,16 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), - MellonParam.negative_prompt(), - MellonParam.image(), + PipelineParam.prompt(), + PipelineParam.negative_prompt(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt", "image"], "required_model_inputs": ["text_encoders"], @@ -450,14 +451,14 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), + PipelineParam.latents(display="input"), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.images(), - MellonParam.doc(), + PipelineParam.images(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -465,7 +466,7 @@ }, } -QWEN_IMAGE_LAYERED_PIPELINE_CONFIG = MellonPipelineConfig( +QWEN_IMAGE_LAYERED_PIPELINE_CONFIG = PipelineConfig( node_specs=QWEN_IMAGE_LAYERED_NODE_SPECS, label="Qwen-Image-Layered", default_repo="Qwen/Qwen-Image-Layered", @@ -480,23 +481,23 @@ "controlnet": None, # Not yet supported in Modular "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.width(), - MellonParam.height(), - MellonParam.seed(), - MellonParam.num_inference_steps(28), - MellonParam.guidance_scale(3.5), - MellonParam.image_latents_with_strength(), - MellonParam.strength(), + PipelineParam.embeddings(display="input"), + PipelineParam.width(), + PipelineParam.height(), + PipelineParam.seed(), + PipelineParam.num_inference_steps(28), + PipelineParam.guidance_scale(3.5), + PipelineParam.image_latents_with_strength(), + PipelineParam.strength(), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.guider(), - MellonParam.scheduler(), + PipelineParam.unet(), + PipelineParam.guider(), + PipelineParam.scheduler(), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["embeddings"], "required_model_inputs": ["unet", "scheduler"], @@ -504,14 +505,14 @@ }, "vae_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.image_latents(display="output"), - MellonParam.doc(), + PipelineParam.image_latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["vae"], @@ -519,15 +520,15 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), + PipelineParam.prompt(), # No negative_prompt - pipeline does not support this ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt"], "required_model_inputs": ["text_encoders"], @@ -535,14 +536,14 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), + PipelineParam.latents(display="input"), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.images(), - MellonParam.doc(), + PipelineParam.images(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -550,7 +551,7 @@ }, } -FLUX_PIPELINE_CONFIG = MellonPipelineConfig( +FLUX_PIPELINE_CONFIG = PipelineConfig( node_specs=FLUX_NODE_SPECS, label="Flux", default_repo="black-forest-labs/FLUX.1-dev", @@ -566,20 +567,20 @@ "controlnet": None, "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.seed(), - MellonParam.num_inference_steps(28), - MellonParam.guidance_scale(2.5), - MellonParam.image_latents(display="input"), + PipelineParam.embeddings(display="input"), + PipelineParam.seed(), + PipelineParam.num_inference_steps(28), + PipelineParam.guidance_scale(2.5), + PipelineParam.image_latents(display="input"), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.guider(), - MellonParam.scheduler(), + PipelineParam.unet(), + PipelineParam.guider(), + PipelineParam.scheduler(), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["embeddings", "image_latents"], "required_model_inputs": ["unet", "scheduler"], @@ -587,14 +588,14 @@ }, "vae_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.image_latents(display="output"), - MellonParam.doc(), + PipelineParam.image_latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["vae"], @@ -602,15 +603,15 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), + PipelineParam.prompt(), # No negative_prompt ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt"], "required_model_inputs": ["text_encoders"], @@ -618,14 +619,14 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), + PipelineParam.latents(display="input"), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.images(), - MellonParam.doc(), + PipelineParam.images(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -633,7 +634,7 @@ }, } -FLUX_KONTEXT_PIPELINE_CONFIG = MellonPipelineConfig( +FLUX_KONTEXT_PIPELINE_CONFIG = PipelineConfig( node_specs=FLUX_KONTEXT_NODE_SPECS, label="Flux Kontext", default_repo="black-forest-labs/FLUX.1-Kontext-dev", @@ -648,22 +649,22 @@ "controlnet": None, "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.width(), - MellonParam.height(), - MellonParam.seed(), - MellonParam.num_inference_steps(4), - MellonParam.guidance_scale(1.0), - MellonParam.image_latents(display="input"), + PipelineParam.embeddings(display="input"), + PipelineParam.width(), + PipelineParam.height(), + PipelineParam.seed(), + PipelineParam.num_inference_steps(4), + PipelineParam.guidance_scale(1.0), + PipelineParam.image_latents(display="input"), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.guider(), - MellonParam.scheduler(), + PipelineParam.unet(), + PipelineParam.guider(), + PipelineParam.scheduler(), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["embeddings"], "required_model_inputs": ["unet", "scheduler"], @@ -671,14 +672,14 @@ }, "vae_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.image_latents(display="output"), - MellonParam.doc(), + PipelineParam.image_latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["vae"], @@ -686,14 +687,14 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), + PipelineParam.prompt(), ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt"], "required_model_inputs": ["text_encoders"], @@ -701,14 +702,14 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), + PipelineParam.latents(display="input"), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.images(), - MellonParam.doc(), + PipelineParam.images(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -716,7 +717,7 @@ }, } -FLUX_2_KLEIN_DISTILLED_PIPELINE_CONFIG = MellonPipelineConfig( +FLUX_2_KLEIN_DISTILLED_PIPELINE_CONFIG = PipelineConfig( node_specs=FLUX_2_KLEIN_DISTILLED_NODE_SPECS, label="Flux 2 Klein Distilled", default_repo="black-forest-labs/FLUX.2-klein-4B", @@ -732,23 +733,23 @@ "controlnet": None, "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.width(), - MellonParam.height(), - MellonParam.seed(), - MellonParam.num_inference_steps(9), - MellonParam.guidance_scale(1.0), - MellonParam.image_latents_with_strength(), - MellonParam.strength(), + PipelineParam.embeddings(display="input"), + PipelineParam.width(), + PipelineParam.height(), + PipelineParam.seed(), + PipelineParam.num_inference_steps(9), + PipelineParam.guidance_scale(1.0), + PipelineParam.image_latents_with_strength(), + PipelineParam.strength(), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.guider(), - MellonParam.scheduler(), + PipelineParam.unet(), + PipelineParam.guider(), + PipelineParam.scheduler(), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["embeddings"], "required_model_inputs": ["unet", "scheduler"], @@ -756,14 +757,14 @@ }, "vae_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.image_latents(display="output"), - MellonParam.doc(), + PipelineParam.image_latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["vae"], @@ -771,15 +772,15 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), + PipelineParam.prompt(), # No negative_prompt - pipeline does not support this ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt"], "required_model_inputs": ["text_encoders"], @@ -787,14 +788,14 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), + PipelineParam.latents(display="input"), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.images(), - MellonParam.doc(), + PipelineParam.images(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -802,7 +803,7 @@ }, } -Z_IMAGE_PIPELINE_CONFIG = MellonPipelineConfig( +Z_IMAGE_PIPELINE_CONFIG = PipelineConfig( node_specs=Z_IMAGE_NODE_SPECS, label="Z-Image", default_repo="Tongyi-MAI/Z-Image-Turbo", @@ -817,21 +818,21 @@ "controlnet": None, "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.width(832), - MellonParam.height(480), - MellonParam.seed(), - MellonParam.num_inference_steps(50), - MellonParam.guidance_scale(5.0), - MellonParam.num_frames(81), + PipelineParam.embeddings(display="input"), + PipelineParam.width(832), + PipelineParam.height(480), + PipelineParam.seed(), + PipelineParam.num_inference_steps(50), + PipelineParam.guidance_scale(5.0), + PipelineParam.num_frames(81), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.scheduler(), + PipelineParam.unet(), + PipelineParam.scheduler(), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["embeddings"], "required_model_inputs": ["unet", "scheduler"], @@ -839,15 +840,15 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), - MellonParam.negative_prompt(), + PipelineParam.prompt(), + PipelineParam.negative_prompt(), ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt"], "required_model_inputs": ["text_encoders"], @@ -855,15 +856,15 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), - MellonParam.output_type(default="pil"), + PipelineParam.latents(display="input"), + PipelineParam.output_type(default="pil"), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.videos(), - MellonParam.doc(), + PipelineParam.videos(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -871,7 +872,7 @@ }, } -WAN_T2V_PIPELINE_CONFIG = MellonPipelineConfig( +WAN_T2V_PIPELINE_CONFIG = PipelineConfig( node_specs=WAN_T2V_NODE_SPECS, label="WAN2 T2V", default_repo="Wan-AI/Wan2.1-T2V-1.3B-Diffusers", @@ -882,23 +883,23 @@ "controlnet": None, "denoise": { "inputs": [ - MellonParam.embeddings(display="input"), - MellonParam.width(832), - MellonParam.height(480), - MellonParam.seed(), - MellonParam.num_inference_steps(50), - MellonParam.guidance_scale(5.0), - MellonParam.num_frames(81), - MellonParam.image_embeds(display="input"), - MellonParam(name="image_condition_latents", label="Image Latents", type="latents", display="input"), + PipelineParam.embeddings(display="input"), + PipelineParam.width(832), + PipelineParam.height(480), + PipelineParam.seed(), + PipelineParam.num_inference_steps(50), + PipelineParam.guidance_scale(5.0), + PipelineParam.num_frames(81), + PipelineParam.image_embeds(display="input"), + PipelineParam(name="image_condition_latents", label="Image Latents", type="latents", display="input"), ], "model_inputs": [ - MellonParam.unet(), - MellonParam.scheduler(), + PipelineParam.unet(), + PipelineParam.scheduler(), ], "outputs": [ - MellonParam.latents(display="output"), - MellonParam.doc(), + PipelineParam.latents(display="output"), + PipelineParam.doc(), ], "required_inputs": ["embeddings", "image_embeds", "image_condition_latents"], "required_model_inputs": ["unet", "scheduler"], @@ -906,14 +907,14 @@ }, "vae_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam(name="image_condition_latents", label="Image Latents", type="latents", display="output"), - MellonParam.doc(), + PipelineParam(name="image_condition_latents", label="Image Latents", type="latents", display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["vae"], @@ -921,14 +922,14 @@ }, "image_encoder": { "inputs": [ - MellonParam.image(), + PipelineParam.image(), ], "model_inputs": [ - MellonParam.image_encoder(), + PipelineParam.image_encoder(), ], "outputs": [ - MellonParam.image_embeds(display="output"), - MellonParam.doc(), + PipelineParam.image_embeds(display="output"), + PipelineParam.doc(), ], "required_inputs": ["image"], "required_model_inputs": ["image_encoder"], @@ -936,15 +937,15 @@ }, "text_encoder": { "inputs": [ - MellonParam.prompt(), - MellonParam.negative_prompt(), + PipelineParam.prompt(), + PipelineParam.negative_prompt(), ], "model_inputs": [ - MellonParam.text_encoders(), + PipelineParam.text_encoders(), ], "outputs": [ - MellonParam.embeddings(display="output"), - MellonParam.doc(), + PipelineParam.embeddings(display="output"), + PipelineParam.doc(), ], "required_inputs": ["prompt"], "required_model_inputs": ["text_encoders"], @@ -952,17 +953,17 @@ }, "decoder": { "inputs": [ - MellonParam.latents(display="input"), - MellonParam( + PipelineParam.latents(display="input"), + PipelineParam( name="output_type", label="Output Type", type="dropdown", options=["np", "pil"], default="pil" ), ], "model_inputs": [ - MellonParam.vae(), + PipelineParam.vae(), ], "outputs": [ - MellonParam.videos(), - MellonParam.doc(), + PipelineParam.videos(), + PipelineParam.doc(), ], "required_inputs": ["latents"], "required_model_inputs": ["vae"], @@ -970,7 +971,7 @@ }, } -WAN_I2V_PIPELINE_CONFIG = MellonPipelineConfig( +WAN_I2V_PIPELINE_CONFIG = PipelineConfig( node_specs=WAN_I2V_NODE_SPECS, label="WAN2 I2V", default_repo="Wan-AI/Wan2.1-I2V-14B-480P-Diffusers", @@ -989,7 +990,7 @@ def __new__(cls): return ModularPipeline.from_pretrained(cls.repo_id, trust_remote_code=True) -DUMMY_CUSTOM_PIPELINE_CONFIG = MellonPipelineConfig( +DUMMY_CUSTOM_PIPELINE_CONFIG = PipelineConfig( node_specs={}, label="Custom", default_repo="", default_dtype="bfloat16" ) @@ -999,23 +1000,23 @@ class ModiffPipelineRegistry: """Registry mapping pipeline class to its config, including label, default_repo, default_dtype, and node_params.""" def __init__(self): - self._registry: Dict[type, MellonPipelineConfig] = {} + self._registry: Dict[type, PipelineConfig] = {} self._initialized = False # Lock to prevent concurrent initialization races self._init_lock = threading.Lock() - def register(self, pipeline_cls: type, config: MellonPipelineConfig): + def register(self, pipeline_cls: type, config: PipelineConfig): """Register a pipeline class with its config.""" self._registry[pipeline_cls] = config - def get(self, pipeline_cls: type) -> Optional[MellonPipelineConfig]: + def get(self, pipeline_cls: type) -> Optional[PipelineConfig]: # Ensure only one thread/coroutine initializes the registry with self._init_lock: if not self._initialized: _initialize_registry(self) return self._registry.get(pipeline_cls, None) - def get_all(self) -> Dict[type, MellonPipelineConfig]: + def get_all(self) -> Dict[type, PipelineConfig]: # Ensure only one thread/coroutine initializes the registry with self._init_lock: if not self._initialized: diff --git a/modules/ModularDiffusers/pipeline_schema.py b/modules/ModularDiffusers/pipeline_schema.py new file mode 100644 index 0000000..e872fc0 --- /dev/null +++ b/modules/ModularDiffusers/pipeline_schema.py @@ -0,0 +1,1086 @@ +"""MoDiff-owned schema helpers for Modular Diffusers node metadata. + +Adapted from Hugging Face Diffusers modular pipeline utilities under Apache-2.0. +The local copy gives MoDiff a stable, product-owned schema and Hub config format. +""" + +import copy +import json +import logging +import os + +# Simple typed wrapper for parameter overrides +from dataclasses import asdict, dataclass +from typing import Any + +from huggingface_hub import create_repo, hf_hub_download, upload_file +from huggingface_hub.utils import ( + EntryNotFoundError, + HfHubHTTPError, + RepositoryNotFoundError, + RevisionNotFoundError, +) + +from diffusers.utils import HUGGINGFACE_CO_RESOLVE_ENDPOINT +from diffusers.modular_pipelines.modular_pipeline_utils import InputParam, OutputParam + + +logger = logging.getLogger(__name__) + + +def _name_to_label(name: str) -> str: + """Convert snake_case name to Title Case label.""" + return name.replace("_", " ").title() + + +# Template definitions for standard diffuser pipeline parameters +MODIFF_PARAM_TEMPLATES = { + # Image I/O + "image": {"label": "Image", "type": "image", "display": "input", "required_block_params": ["image"]}, + "images": {"label": "Images", "type": "image", "display": "output", "required_block_params": ["images"]}, + "control_image": { + "label": "Control Image", + "type": "image", + "display": "input", + "required_block_params": ["control_image"], + }, + # Latents + "latents": {"label": "Latents", "type": "latents", "display": "input", "required_block_params": ["latents"]}, + "image_latents": { + "label": "Image Latents", + "type": "latents", + "display": "input", + "required_block_params": ["image_latents"], + }, + "first_frame_latents": { + "label": "First Frame Latents", + "type": "latents", + "display": "input", + "required_block_params": ["first_frame_latents"], + }, + "latents_preview": {"label": "Latents Preview", "type": "latent", "display": "output"}, + # Image Latents with Strength + "image_latents_with_strength": { + "name": "image_latents", # name is not same as template key + "label": "Image Latents", + "type": "latents", + "display": "input", + "onChange": {"false": ["height", "width"], "true": ["strength"]}, + "required_block_params": ["image_latents", "strength"], + }, + # Embeddings + "embeddings": {"label": "Text Embeddings", "type": "embeddings", "display": "output"}, + "image_embeds": { + "label": "Image Embeddings", + "type": "image_embeds", + "display": "output", + "required_block_params": ["image_embeds"], + }, + # Text inputs + "prompt": { + "label": "Prompt", + "type": "string", + "display": "textarea", + "default": "", + "required_block_params": ["prompt"], + }, + "negative_prompt": { + "label": "Negative Prompt", + "type": "string", + "display": "textarea", + "default": "", + "required_block_params": ["negative_prompt"], + }, + # Numeric params + "guidance_scale": { + "label": "Guidance Scale", + "type": "float", + "display": "slider", + "default": 5.0, + "min": 1.0, + "max": 30.0, + "step": 0.1, + }, + "strength": { + "label": "Strength", + "type": "float", + "default": 0.5, + "min": 0.0, + "max": 1.0, + "step": 0.01, + "required_block_params": ["strength"], + }, + "height": { + "label": "Height", + "type": "int", + "default": 1024, + "min": 64, + "step": 8, + "required_block_params": ["height"], + }, + "width": { + "label": "Width", + "type": "int", + "default": 1024, + "min": 64, + "step": 8, + "required_block_params": ["width"], + }, + "seed": { + "label": "Seed", + "type": "int", + "default": 0, + "min": 0, + "max": 4294967295, + "display": "random", + "required_block_params": ["generator"], + }, + "num_inference_steps": { + "label": "Steps", + "type": "int", + "default": 25, + "min": 1, + "max": 100, + "display": "slider", + "required_block_params": ["num_inference_steps"], + }, + "num_frames": { + "label": "Frames", + "type": "int", + "default": 81, + "min": 1, + "max": 480, + "display": "slider", + "required_block_params": ["num_frames"], + }, + "layers": { + "label": "Layers", + "type": "int", + "default": 4, + "min": 1, + "max": 10, + "display": "slider", + "required_block_params": ["layers"], + }, + "output_type": { + "label": "Output Type", + "type": "dropdown", + "default": "np", + "options": ["np", "pil", "pt"], + }, + # ControlNet + "controlnet_conditioning_scale": { + "label": "Controlnet Conditioning Scale", + "type": "float", + "default": 0.5, + "min": 0.0, + "max": 1.0, + "step": 0.01, + "required_block_params": ["controlnet_conditioning_scale"], + }, + "control_guidance_start": { + "label": "Control Guidance Start", + "type": "float", + "default": 0.0, + "min": 0.0, + "max": 1.0, + "step": 0.01, + "required_block_params": ["control_guidance_start"], + }, + "control_guidance_end": { + "label": "Control Guidance End", + "type": "float", + "default": 1.0, + "min": 0.0, + "max": 1.0, + "step": 0.01, + "required_block_params": ["control_guidance_end"], + }, + # Video + "videos": {"label": "Videos", "type": "video", "display": "output", "required_block_params": ["videos"]}, + # Models + "vae": {"label": "VAE", "type": "diffusers_auto_model", "display": "input", "required_block_params": ["vae"]}, + "image_encoder": { + "label": "Image Encoder", + "type": "diffusers_auto_model", + "display": "input", + "required_block_params": ["image_encoder"], + }, + "unet": {"label": "Denoise Model", "type": "diffusers_auto_model", "display": "input"}, + "scheduler": {"label": "Scheduler", "type": "diffusers_auto_model", "display": "input"}, + "controlnet": { + "label": "ControlNet Model", + "type": "diffusers_auto_model", + "display": "input", + "required_block_params": ["controlnet"], + }, + "text_encoders": { + "label": "Text Encoders", + "type": "diffusers_auto_models", + "display": "input", + "required_block_params": ["text_encoder"], + }, + # Bundles/Custom + "controlnet_bundle": { + "label": "ControlNet", + "type": "custom_controlnet", + "display": "input", + "required_block_params": "controlnet_image", + }, + "ip_adapter": {"label": "IP Adapter", "type": "custom_ip_adapter", "display": "input"}, + "guider": { + "label": "Guider", + "type": "custom_guider", + "display": "input", + "onChange": {False: ["guidance_scale"], True: []}, + }, + "doc": {"label": "Doc", "type": "string", "display": "output"}, +} + + +class MoDiffParamMeta(type): + """Metaclass that enables MoDiffParam.template_name(**overrides) syntax.""" + + def __getattr__(cls, name: str): + if name in MODIFF_PARAM_TEMPLATES: + + def factory(default=None, **overrides): + template = MODIFF_PARAM_TEMPLATES[name] + # Use template's name if specified, otherwise use the key + params = {"name": template.get("name", name), **template, **overrides} + if default is not None: + params["default"] = default + return cls(**params) + + return factory + + raise AttributeError(f"type object 'MoDiffParam' has no attribute '{name}'") + + +@dataclass(frozen=True) +class MoDiffParam(metaclass=MoDiffParamMeta): + """ + Parameter definition for MoDiff nodes. + + Usage: + ```python + # From template (standard diffuser params) + MoDiffParam.seed() + MoDiffParam.prompt(default="a cat") + MoDiffParam.latents(display="output") + + # Generic inputs (for custom blocks) + MoDiffParam.Input.slider("my_scale", default=1.0, min=0.0, max=2.0) + MoDiffParam.Input.dropdown("mode", options=["fast", "slow"]) + + # Generic outputs + MoDiffParam.Output.image("result_images") + + # Fully custom + MoDiffParam(name="custom", label="Custom", type="float", default=0.5) + ``` + """ + + name: str + label: str + type: str + display: str | None = None + default: Any = None + min: float | None = None + max: float | None = None + step: float | None = None + options: Any = None + value: Any = None + fieldOptions: dict[str, Any] | None = None + onChange: Any = None + onSignal: Any = None + required_block_params: str | list[str] | None = None + + def to_dict(self) -> dict[str, Any]: + """Convert to dict for MoDiff schema, excluding None values and internal fields.""" + data = asdict(self) + return {k: v for k, v in data.items() if v is not None and k not in ("name", "required_block_params")} + + # ========================================================================= + # Input: Generic input parameter factories (for custom blocks) + # ========================================================================= + class Input: + """input UI elements for custom blocks.""" + + @classmethod + def image(cls, name: str) -> "MoDiffParam": + """image input.""" + return MoDiffParam(name=name, label=_name_to_label(name), type="image", display="input") + + @classmethod + def textbox(cls, name: str, default: str = "") -> "MoDiffParam": + """text input as textarea.""" + return MoDiffParam( + name=name, label=_name_to_label(name), type="string", display="textarea", default=default + ) + + @classmethod + def dropdown(cls, name: str, options: list[str] = None, default: str = None) -> "MoDiffParam": + """dropdown selection.""" + if options and not default: + default = options[0] + if not default: + default = "" + if not options: + options = [default] + return MoDiffParam(name=name, label=_name_to_label(name), type="string", options=options, value=default) + + @classmethod + def slider( + cls, name: str, default: float = 0, min: float = None, max: float = None, step: float = None + ) -> "MoDiffParam": + """slider input.""" + is_float = isinstance(default, float) or (step is not None and isinstance(step, float)) + param_type = "float" if is_float else "int" + if min is None: + min = default + if max is None: + max = default + if step is None: + step = 0.01 if is_float else 1 + return MoDiffParam( + name=name, + label=_name_to_label(name), + type=param_type, + display="slider", + default=default, + min=min, + max=max, + step=step, + ) + + @classmethod + def number( + cls, name: str, default: float = 0, min: float = None, max: float = None, step: float = None + ) -> "MoDiffParam": + """number input (no slider).""" + is_float = isinstance(default, float) or (step is not None and isinstance(step, float)) + param_type = "float" if is_float else "int" + return MoDiffParam( + name=name, label=_name_to_label(name), type=param_type, default=default, min=min, max=max, step=step + ) + + @classmethod + def seed(cls, name: str = "seed", default: int = 0) -> "MoDiffParam": + """seed input with randomize button.""" + return MoDiffParam( + name=name, + label=_name_to_label(name), + type="int", + display="random", + default=default, + min=0, + max=4294967295, + ) + + @classmethod + def checkbox(cls, name: str, default: bool = False) -> "MoDiffParam": + """boolean checkbox.""" + return MoDiffParam(name=name, label=_name_to_label(name), type="boolean", value=default) + + @classmethod + def custom_type(cls, name: str, type: str) -> "MoDiffParam": + """custom type input for node connections.""" + return MoDiffParam(name=name, label=_name_to_label(name), type=type, display="input") + + @classmethod + def model(cls, name: str) -> "MoDiffParam": + """model input for diffusers components.""" + return MoDiffParam(name=name, label=_name_to_label(name), type="diffusers_auto_model", display="input") + + # ========================================================================= + # Output: Generic output parameter factories (for custom blocks) + # ========================================================================= + class Output: + """output UI elements for custom blocks.""" + + @classmethod + def image(cls, name: str) -> "MoDiffParam": + """image output.""" + return MoDiffParam(name=name, label=_name_to_label(name), type="image", display="output") + + @classmethod + def video(cls, name: str) -> "MoDiffParam": + """video output.""" + return MoDiffParam(name=name, label=_name_to_label(name), type="video", display="output") + + @classmethod + def text(cls, name: str) -> "MoDiffParam": + """text output.""" + return MoDiffParam(name=name, label=_name_to_label(name), type="string", display="output") + + @classmethod + def custom_type(cls, name: str, type: str) -> "MoDiffParam": + """custom type output for node connections.""" + return MoDiffParam(name=name, label=_name_to_label(name), type=type, display="output") + + @classmethod + def model(cls, name: str) -> "MoDiffParam": + """model output for diffusers components.""" + return MoDiffParam(name=name, label=_name_to_label(name), type="diffusers_auto_model", display="output") + + +def input_param_to_modiff_param(input_param: "InputParam") -> MoDiffParam: + """ + Convert an InputParam to a MoDiffParam using metadata. + + Args: + input_param: An InputParam with optional metadata containing either: + - {"modiff": ""} for simple types (image, textbox, slider, etc.) + - {"modiff": MoDiffParam(...)} for full control over UI configuration + + Returns: + MoDiffParam instance + """ + name = input_param.name + metadata = input_param.metadata + modiff_value = metadata.get("modiff") if metadata else None + default = input_param.default + + # If it's already a MoDiffParam, return it directly + if isinstance(modiff_value, MoDiffParam): + return modiff_value + + modiff_type = modiff_value + + if modiff_type == "image": + return MoDiffParam.Input.image(name) + elif modiff_type == "textbox": + return MoDiffParam.Input.textbox(name, default=default or "") + elif modiff_type == "dropdown": + return MoDiffParam.Input.dropdown(name, default=default or "") + elif modiff_type == "slider": + return MoDiffParam.Input.slider(name, default=default or 0) + elif modiff_type == "number": + return MoDiffParam.Input.number(name, default=default or 0) + elif modiff_type == "seed": + return MoDiffParam.Input.seed(name, default=default or 0) + elif modiff_type == "checkbox": + return MoDiffParam.Input.checkbox(name, default=default or False) + elif modiff_type == "model": + return MoDiffParam.Input.model(name) + else: + # None or unknown -> custom + return MoDiffParam.Input.custom_type(name, type="custom") + + +def output_param_to_modiff_param(output_param: "OutputParam") -> MoDiffParam: + """ + Convert an OutputParam to a MoDiffParam using metadata. + + Args: + output_param: An OutputParam with optional metadata={"modiff": ""} where type is one of: + image, video, text, model. If metadata is None or unknown, maps to "custom". + + Returns: + MoDiffParam instance + """ + name = output_param.name + metadata = output_param.metadata + modiff_type = metadata.get("modiff") if metadata else None + + if modiff_type == "image": + return MoDiffParam.Output.image(name) + elif modiff_type == "video": + return MoDiffParam.Output.video(name) + elif modiff_type == "text": + return MoDiffParam.Output.text(name) + elif modiff_type == "model": + return MoDiffParam.Output.model(name) + else: + # None or unknown -> custom + return MoDiffParam.Output.custom_type(name, type="custom") + + +DEFAULT_NODE_SPECS = { + "controlnet": None, + "denoise": { + "inputs": [ + MoDiffParam.embeddings(display="input"), + MoDiffParam.width(), + MoDiffParam.height(), + MoDiffParam.seed(), + MoDiffParam.num_inference_steps(), + MoDiffParam.num_frames(), + MoDiffParam.guidance_scale(), + MoDiffParam.strength(), + MoDiffParam.image_latents_with_strength(), + MoDiffParam.image_latents(), + MoDiffParam.first_frame_latents(), + MoDiffParam.controlnet_bundle(display="input"), + ], + "model_inputs": [ + MoDiffParam.unet(), + MoDiffParam.guider(), + MoDiffParam.scheduler(), + ], + "outputs": [ + MoDiffParam.latents(display="output"), + MoDiffParam.latents_preview(), + MoDiffParam.doc(), + ], + "required_inputs": ["embeddings"], + "required_model_inputs": ["unet", "scheduler"], + "block_name": "denoise", + }, + "vae_encoder": { + "inputs": [ + MoDiffParam.image(), + ], + "model_inputs": [ + MoDiffParam.vae(), + ], + "outputs": [ + MoDiffParam.image_latents(display="output"), + MoDiffParam.doc(), + ], + "required_inputs": ["image"], + "required_model_inputs": ["vae"], + "block_name": "vae_encoder", + }, + "text_encoder": { + "inputs": [ + MoDiffParam.prompt(), + MoDiffParam.negative_prompt(), + ], + "model_inputs": [ + MoDiffParam.text_encoders(), + ], + "outputs": [ + MoDiffParam.embeddings(display="output"), + MoDiffParam.doc(), + ], + "required_inputs": ["prompt"], + "required_model_inputs": ["text_encoders"], + "block_name": "text_encoder", + }, + "decoder": { + "inputs": [ + MoDiffParam.latents(display="input"), + ], + "model_inputs": [ + MoDiffParam.vae(), + ], + "outputs": [ + MoDiffParam.images(), + MoDiffParam.videos(), + MoDiffParam.doc(), + ], + "required_inputs": ["latents"], + "required_model_inputs": ["vae"], + "block_name": "decode", + }, +} + + +def mark_required(label: str, marker: str = " *") -> str: + """Add required marker to label if not already present.""" + if label.endswith(marker): + return label + return f"{label}{marker}" + + +def node_spec_to_modiff_dict(node_spec: dict[str, Any], node_type: str) -> dict[str, Any]: + """ + Convert a node spec dict into MoDiff format. + + A node spec is how we define a MoDiff diffusers node in code. This function converts it into the `params` map + format that MoDiff UI expects. + + The `params` map is a dict where keys are parameter names and values are UI configuration: + ```python + {"seed": {"label": "Seed", "type": "int", "default": 0}} + ``` + + For Modular MoDiff nodes, we need to distinguish: + - `inputs`: Pipeline inputs (e.g., seed, prompt, image) + - `mode…266 tokens truncated… - `block_name`: The backend block name + - `node_type`: The node type + + Example: + ```python + node_spec = { + "inputs": [MoDiffParam.seed(), MoDiffParam.prompt()], + "model_inputs": [MoDiffParam.unet()], + "outputs": [MoDiffParam.latents(display="output")], + "required_inputs": ["prompt"], + "required_model_inputs": ["unet"], + "block_name": "denoise", + } + + result = node_spec_to_modiff_dict(node_spec, "denoise") + # Returns: + # { + # "params": { + # "seed": {"label": "Seed", "type": "int", "default": 0}, + # "prompt": {"label": "Prompt *", "type": "string", "default": ""}, # * marks required + # "unet": {"label": "Denoise Model *", "type": "diffusers_auto_model", "display": "input"}, + # "latents": {"label": "Latents", "type": "latents", "display": "output"}, + # }, + # "input_names": ["seed", "prompt"], + # "model_input_names": ["unet"], + # "output_names": ["latents"], + # "block_name": "denoise", + # "node_type": "denoise", + # } + ``` + """ + params = {} + input_names = [] + model_input_names = [] + output_names = [] + + required_inputs = node_spec.get("required_inputs", []) + required_model_inputs = node_spec.get("required_model_inputs", []) + + # Process inputs + for p in node_spec.get("inputs", []): + param_dict = p.to_dict() + if p.name in required_inputs: + param_dict["label"] = mark_required(param_dict["label"]) + params[p.name] = param_dict + input_names.append(p.name) + + # Process model_inputs + for p in node_spec.get("model_inputs", []): + param_dict = p.to_dict() + if p.name in required_model_inputs: + param_dict["label"] = mark_required(param_dict["label"]) + params[p.name] = param_dict + model_input_names.append(p.name) + + # Process outputs: add a prefix to the output name if it already exists as an input + for p in node_spec.get("outputs", []): + if p.name in input_names: + # rename to out_ + output_name = f"out_{p.name}" + else: + output_name = p.name + params[output_name] = p.to_dict() + output_names.append(output_name) + + return { + "params": params, + "input_names": input_names, + "model_input_names": model_input_names, + "output_names": output_names, + "block_name": node_spec.get("block_name"), + "node_type": node_type, + } + + +class MoDiffPipelineConfig: + """ + Configuration for an entire MoDiff pipeline containing multiple nodes. + + Accepts node specs as dicts with inputs/model_inputs/outputs lists of MoDiffParam, converts them to MoDiff-ready + format, and handles save/load to Hub. + + Example: + ```python + config = MoDiffPipelineConfig( + node_specs={ + "denoise": { + "inputs": [MoDiffParam.seed(), MoDiffParam.prompt()], + "model_inputs": [MoDiffParam.unet()], + "outputs": [MoDiffParam.latents(display="output")], + "required_inputs": ["prompt"], + "required_model_inputs": ["unet"], + "block_name": "denoise", + }, + "decoder": { + "inputs": [MoDiffParam.latents(display="input")], + "outputs": [MoDiffParam.images()], + "block_name": "decoder", + }, + }, + label="My Pipeline", + default_repo="user/my-pipeline", + default_dtype="float16", + ) + + # Access MoDiff format dict + denoise = config.node_params["denoise"] + input_names = denoise["input_names"] + params = denoise["params"] + + # Save to Hub + config.save("./my_config", push_to_hub=True, repo_id="user/my-pipeline") + + # Load from Hub + loaded = MoDiffPipelineConfig.load("user/my-pipeline") + ``` + """ + + config_name = "modiff_pipeline_config.json" + + def __init__( + self, + node_specs: dict[str, dict[str, Any] | None], + label: str = "", + default_repo: str = "", + default_dtype: str = "", + ): + """ + Args: + node_specs: Dict mapping node_type to node spec or None. + Node spec has: inputs, model_inputs, outputs, required_inputs, required_model_inputs, + block_name (all optional) + label: Human-readable label for the pipeline + default_repo: Default HuggingFace repo for this pipeline + default_dtype: Default dtype (e.g., "float16", "bfloat16") + """ + # Convert all node specs to MoDiff format immediately + self.node_specs = node_specs + + self.label = label + self.default_repo = default_repo + self.default_dtype = default_dtype + + @property + def node_params(self) -> dict[str, Any]: + """Lazily compute node_params from node_specs.""" + if self.node_specs is None: + return self._node_params + + params = {} + for node_type, spec in self.node_specs.items(): + if spec is None: + params[node_type] = None + else: + params[node_type] = node_spec_to_modiff_dict(spec, node_type) + return params + + def __repr__(self) -> str: + lines = [ + f"MoDiffPipelineConfig(label={self.label!r}, default_repo={self.default_repo!r}, default_dtype={self.default_dtype!r})" + ] + for node_type, spec in self.node_specs.items(): + if spec is None: + lines.append(f" {node_type}: None") + else: + inputs = [p.name for p in spec.get("inputs", [])] + model_inputs = [p.name for p in spec.get("model_inputs", [])] + outputs = [p.name for p in spec.get("outputs", [])] + lines.append(f" {node_type}:") + lines.append(f" inputs: {inputs}") + lines.append(f" model_inputs: {model_inputs}") + lines.append(f" outputs: {outputs}") + return "\n".join(lines) + + def to_dict(self) -> dict[str, Any]: + """Convert to a JSON-serializable dictionary.""" + return { + "label": self.label, + "default_repo": self.default_repo, + "default_dtype": self.default_dtype, + "node_params": self.node_params, + } + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> "MoDiffPipelineConfig": + """ + Create from a dictionary (loaded from JSON). + + Note: The modiff_params are already in MoDiff format when loading from JSON. + """ + instance = cls.__new__(cls) + instance.node_specs = None + instance._node_params = data.get("node_params", {}) + instance.label = data.get("label", "") + instance.default_repo = data.get("default_repo", "") + instance.default_dtype = data.get("default_dtype", "") + return instance + + def to_json_string(self) -> str: + """Serialize to JSON string.""" + return json.dumps(self.to_dict(), indent=2, sort_keys=False) + "\n" + + def to_json_file(self, json_file_path: str | os.PathLike): + """Save to a JSON file.""" + with open(json_file_path, "w", encoding="utf-8") as writer: + writer.write(self.to_json_string()) + + @classmethod + def from_json_file(cls, json_file_path: str | os.PathLike) -> "MoDiffPipelineConfig": + """Load from a JSON file.""" + with open(json_file_path, "r", encoding="utf-8") as reader: + data = json.load(reader) + return cls.from_dict(data) + + def save(self, save_directory: str | os.PathLike, push_to_hub: bool = False, **kwargs): + """Save the modiff pipeline config to a directory.""" + if os.path.isfile(save_directory): + raise AssertionError(f"Provided path ({save_directory}) should be a directory, not a file") + + os.makedirs(save_directory, exist_ok=True) + output_path = os.path.join(save_directory, self.config_name) + self.to_json_file(output_path) + logger.info(f"Pipeline config saved to {output_path}") + + if push_to_hub: + commit_message = kwargs.pop("commit_message", None) + private = kwargs.pop("private", None) + create_pr = kwargs.pop("create_pr", False) + token = kwargs.pop("token", None) + repo_id = kwargs.pop("repo_id", save_directory.split(os.path.sep)[-1]) + repo_id = create_repo(repo_id, exist_ok=True, private=private, token=token).repo_id + + upload_file( + path_or_fileobj=output_path, + path_in_repo=self.config_name, + repo_id=repo_id, + token=token, + commit_message=commit_message or "Upload MoDiffPipelineConfig", + create_pr=create_pr, + ) + logger.info(f"Pipeline config pushed to hub: {repo_id}") + + @classmethod + def load( + cls, + pretrained_model_name_or_path: str | os.PathLike, + **kwargs, + ) -> "MoDiffPipelineConfig": + """Load a pipeline config from a local path or Hugging Face Hub.""" + cache_dir = kwargs.pop("cache_dir", None) + local_dir = kwargs.pop("local_dir", None) + local_dir_use_symlinks = kwargs.pop("local_dir_use_symlinks", "auto") + force_download = kwargs.pop("force_download", False) + proxies = kwargs.pop("proxies", None) + token = kwargs.pop("token", None) + local_files_only = kwargs.pop("local_files_only", False) + revision = kwargs.pop("revision", None) + subfolder = kwargs.pop("subfolder", None) + + pretrained_model_name_or_path = str(pretrained_model_name_or_path) + + if os.path.isfile(pretrained_model_name_or_path): + config_file = pretrained_model_name_or_path + elif os.path.isdir(pretrained_model_name_or_path): + config_file = os.path.join(pretrained_model_name_or_path, cls.config_name) + if not os.path.isfile(config_file): + raise EnvironmentError(f"No file named {cls.config_name} found in {pretrained_model_name_or_path}") + else: + try: + config_file = hf_hub_download( + pretrained_model_name_or_path, + filename=cls.config_name, + cache_dir=cache_dir, + force_download=force_download, + proxies=proxies, + local_files_only=local_files_only, + token=token, + revision=revision, + subfolder=subfolder, + local_dir=local_dir, + local_dir_use_symlinks=local_dir_use_symlinks, + ) + except RepositoryNotFoundError: + raise EnvironmentError( + f"{pretrained_model_name_or_path} is not a local folder and is not a valid model identifier" + " listed on 'https://huggingface.co/models'\nIf this is a private repository, make sure to pass a" + " token having permission to this repo with `token` or log in with `hf auth login`." + ) + except RevisionNotFoundError: + raise EnvironmentError( + f"{revision} is not a valid git identifier (branch name, tag name or commit id) that exists for" + " this model name. Check the model page at" + f" 'https://huggingface.co/{pretrained_model_name_or_path}' for available revisions." + ) + except EntryNotFoundError: + raise EnvironmentError( + f"{pretrained_model_name_or_path} does not appear to have a file named {cls.config_name}." + ) + except HfHubHTTPError as err: + raise EnvironmentError( + "There was a specific connection error when trying to load" + f" {pretrained_model_name_or_path}:\n{err}" + ) + except ValueError: + raise EnvironmentError( + f"We couldn't connect to '{HUGGINGFACE_CO_RESOLVE_ENDPOINT}' to load this model, couldn't find it" + f" in the cached files and it looks like {pretrained_model_name_or_path} is not the path to a" + f" directory containing a {cls.config_name} file.\nCheckout your internet connection or see how to" + " run the library in offline mode at" + " 'https://huggingface.co/docs/diffusers/installation#offline-mode'." + ) + except EnvironmentError: + raise EnvironmentError( + f"Can't load config for '{pretrained_model_name_or_path}'. If you were trying to load it from " + "'https://huggingface.co/models', make sure you don't have a local directory with the same name. " + f"Otherwise, make sure '{pretrained_model_name_or_path}' is the correct path to a directory " + f"containing a {cls.config_name} file" + ) + + try: + return cls.from_json_file(config_file) + except (json.JSONDecodeError, UnicodeDecodeError): + raise EnvironmentError(f"The config file at '{config_file}' is not a valid JSON file.") + + @classmethod + def from_blocks( + cls, + blocks, + template: dict[str, dict[str, Any]] | None = None, + label: str = "", + default_repo: str = "", + default_dtype: str = "bfloat16", + ) -> "MoDiffPipelineConfig": + """ + Create MoDiffPipelineConfig by matching template against actual pipeline blocks. + """ + if template is None: + template = DEFAULT_NODE_SPECS + + sub_block_map = dict(blocks.sub_blocks) + + def filter_spec_for_block(template_spec: dict[str, Any], block) -> dict[str, Any] | None: + """Filter template spec params based on what the block actually supports.""" + block_input_names = set(block.input_names) + block_output_names = set(block.intermediate_output_names) + block_component_names = set(block.component_names) + + filtered_inputs = [ + p + for p in template_spec.get("inputs", []) + if p.required_block_params is None + or all(name in block_input_names for name in p.required_block_params) + ] + filtered_model_inputs = [ + p + for p in template_spec.get("model_inputs", []) + if p.required_block_params is None + or all(name in block_component_names for name in p.required_block_params) + ] + filtered_outputs = [ + p + for p in template_spec.get("outputs", []) + if p.required_block_params is None + or all(name in block_output_names for name in p.required_block_params) + ] + + filtered_input_names = {p.name for p in filtered_inputs} + filtered_model_input_names = {p.name for p in filtered_model_inputs} + + filtered_required_inputs = [ + r for r in template_spec.get("required_inputs", []) if r in filtered_input_names + ] + filtered_required_model_inputs = [ + r for r in template_spec.get("required_model_inputs", []) if r in filtered_model_input_names + ] + + return { + "inputs": filtered_inputs, + "model_inputs": filtered_model_inputs, + "outputs": filtered_outputs, + "required_inputs": filtered_required_inputs, + "required_model_inputs": filtered_required_model_inputs, + "block_name": template_spec.get("block_name"), + } + + # Build node specs + node_specs = {} + for node_type, template_spec in template.items(): + if template_spec is None: + node_specs[node_type] = None + continue + + block_name = template_spec.get("block_name") + if block_name is None or block_name not in sub_block_map: + node_specs[node_type] = None + continue + + node_specs[node_type] = filter_spec_for_block(template_spec, sub_block_map[block_name]) + + return cls( + node_specs=node_specs, + label=label or getattr(blocks, "model_name", ""), + default_repo=default_repo, + default_dtype=default_dtype, + ) + + @classmethod + def from_custom_block( + cls, + block, + node_label: str = None, + input_types: dict[str, Any] | None = None, + output_types: dict[str, Any] | None = None, + ) -> "MoDiffPipelineConfig": + """ + Create a MoDiffPipelineConfig from a custom block. + + Args: + block: A block instance with `inputs`, `outputs`, and `expected_components`/`component_names` properties. + Each InputParam/OutputParam should have metadata={"modiff": ""} where type is one of: image, + video, text, checkbox, number, slider, dropdown, model. If metadata is None, maps to "custom". + node_label: The display label for the node. Defaults to block class name with spaces. + input_types: + Optional dict mapping input param names to modiff types. Overrides the block's metadata if provided. + Example: {"prompt": "textbox", "image": "image"} + output_types: + Optional dict mapping output param names to modiff types. Overrides the block's metadata if provided. + Example: {"prompt": "text", "images": "image"} + + Returns: + MoDiffPipelineConfig instance + """ + if node_label is None: + class_name = block.__class__.__name__ + node_label = "".join([" " + c if c.isupper() else c for c in class_name]).strip() + + if input_types is None: + input_types = {} + if output_types is None: + output_types = {} + + inputs = [] + model_inputs = [] + outputs = [] + + # Process block inputs + for input_param in block.inputs: + if input_param.name is None: + continue + if input_param.name in input_types: + input_param = copy.copy(input_param) + input_param.metadata = {"modiff": input_types[input_param.name]} + print(f" processing input: {input_param.name}, metadata: {input_param.metadata}") + inputs.append(input_param_to_modiff_param(input_param)) + + # Process block outputs + for output_param in block.outputs: + if output_param.name is None: + continue + if output_param.name in output_types: + output_param = copy.copy(output_param) + output_param.metadata = {"modiff": output_types[output_param.name]} + outputs.append(output_param_to_modiff_param(output_param)) + + # Process expected components (all map to model inputs) + component_names = block.component_names + for component_name in component_names: + model_inputs.append(MoDiffParam.Input.model(component_name)) + + # Always add doc output + outputs.append(MoDiffParam.doc()) + + node_spec = { + "inputs": inputs, + "model_inputs": model_inputs, + "outputs": outputs, + "required_inputs": [], + "required_model_inputs": [], + "block_name": "custom", + } + + return cls( + node_specs={"custom": node_spec}, + label=node_label, + ) diff --git a/tests/test_node_base.py b/tests/test_node_base.py index 83a23f9..33254c5 100644 --- a/tests/test_node_base.py +++ b/tests/test_node_base.py @@ -24,42 +24,25 @@ def test_nested_audio_arrays_compare_without_image_attributes(self): self.assertFalse(deep_equal(left, right)) def test_direct_node_base_imports_preserve_complete_module_registry(self): - import_orders = { - "canonical_first": ( - "from modiff.NodeBase import NodeBase\n" - "from mellon.NodeBase import NodeBase as LegacyNodeBase" - ), - "legacy_first": ( - "from mellon.NodeBase import NodeBase as LegacyNodeBase\n" - "from modiff.NodeBase import NodeBase" - ), - } - - for name, imports in import_orders.items(): - with self.subTest(order=name): - script = f""" + script = """ import json -{imports} +from modiff.NodeBase import NodeBase import modules -print(json.dumps({{ - "same_class": NodeBase is LegacyNodeBase, +print(json.dumps({ "module_count": len(modules.MODULE_MAP), "node_count": modules.total_nodes, -}})) +})) """ - result = subprocess.run( - [sys.executable, "-c", script], - cwd=Path(__file__).resolve().parents[1], - capture_output=True, - text=True, - check=False, - ) - self.assertEqual(result.returncode, 0, result.stderr) - payload = json.loads(result.stdout.strip().splitlines()[-1]) - self.assertEqual( - payload, - {"same_class": True, "module_count": 19, "node_count": 83}, - ) + result = subprocess.run( + [sys.executable, "-c", script], + cwd=Path(__file__).resolve().parents[1], + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(result.returncode, 0, result.stderr) + payload = json.loads(result.stdout.strip().splitlines()[-1]) + self.assertEqual(payload, {"module_count": 19, "node_count": 83}) if __name__ == "__main__": diff --git a/tests/test_pipeline_schema.py b/tests/test_pipeline_schema.py new file mode 100644 index 0000000..8ebdfd3 --- /dev/null +++ b/tests/test_pipeline_schema.py @@ -0,0 +1,69 @@ +import tempfile +import unittest +from pathlib import Path +from types import SimpleNamespace + +from modules.ModularDiffusers.pipeline_schema import MoDiffParam, MoDiffPipelineConfig, input_param_to_modiff_param + + +class PipelineSchemaTests(unittest.TestCase): + def test_parameter_templates_serialize_without_internal_fields(self): + prompt = MoDiffParam.prompt(default="a lighthouse") + + self.assertEqual(prompt.name, "prompt") + self.assertEqual( + prompt.to_dict(), + { + "label": "Prompt", + "type": "string", + "display": "textarea", + "default": "a lighthouse", + }, + ) + + def test_output_names_are_disambiguated_from_inputs(self): + config = MoDiffPipelineConfig( + node_specs={ + "denoise": { + "inputs": [MoDiffParam.latents(display="input")], + "model_inputs": [], + "outputs": [MoDiffParam.latents(display="output")], + "required_inputs": ["latents"], + "block_name": "denoise", + } + } + ) + + denoise = config.node_params["denoise"] + self.assertEqual(denoise["input_names"], ["latents"]) + self.assertEqual(denoise["output_names"], ["out_latents"]) + self.assertEqual(denoise["params"]["latents"]["label"], "Latents *") + self.assertEqual(denoise["params"]["out_latents"]["display"], "output") + + def test_custom_block_metadata_uses_modiff_schema(self): + input_param = SimpleNamespace(name="prompt", default="hello", metadata={"modiff": "textbox"}) + + converted = input_param_to_modiff_param(input_param) + + self.assertEqual(converted.name, "prompt") + self.assertEqual(converted.to_dict()["display"], "textarea") + self.assertEqual(converted.to_dict()["default"], "hello") + + def test_config_round_trip_uses_modiff_owned_filename(self): + config = MoDiffPipelineConfig( + node_specs={"custom": None}, + label="Custom Pipeline", + default_repo="example/pipeline", + default_dtype="float16", + ) + + with tempfile.TemporaryDirectory() as directory: + config.save(directory) + config_path = Path(directory, "modiff_pipeline_config.json") + + self.assertTrue(config_path.is_file()) + self.assertEqual(MoDiffPipelineConfig.load(directory).to_dict(), config.to_dict()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_runtime_status.py b/tests/test_runtime_status.py index 0e69f13..4f2e24c 100644 --- a/tests/test_runtime_status.py +++ b/tests/test_runtime_status.py @@ -205,11 +205,20 @@ def package_status(module_name, distribution_name, import_check=True): self.assertEqual(report["hardware"], snapshot) self.assertEqual(torch_status["cuda_device_name"], "Mock CUDA") self.assertTrue(torch_status["cuda_available"]) + self.assertEqual( + report["namespace"], + { + "productName": "MoDiff", + "canonicalPackage": "modiff", + "canonicalPreflightCommand": "python -m modiff.preflight", + }, + ) output = io.StringIO() with redirect_stdout(output): preflight.print_human(report) self.assertIn("Torch: unit-test CUDA available (Mock CUDA); MPS not available", output.getvalue()) + self.assertIn("Namespace: use python -m modiff.preflight", output.getvalue()) if __name__ == "__main__": From 76bbafe8b01c35985913a3949fa20de511636b6e Mon Sep 17 00:00:00 2001 From: Sourav Das Date: Wed, 5 Aug 2026 04:04:49 +0530 Subject: [PATCH 3/7] feat: Completed basic templates and app is in stable state --- .github/ISSUE_TEMPLATE/bug-report.yml | 47 + .github/ISSUE_TEMPLATE/feature-request.yml | 36 + .github/pull_request_template.md | 22 +- .github/workflows/ci.yml | 44 +- .gitignore | 10 + AGENTS.md | 36 + CONTRIBUTING.md | 41 +- README.md | 333 +- SECURITY.md | 6 +- THIRD_PARTY_NOTICES.md | 133 + config.example.ini | 6 +- .../modular_diffusers/dynamic_node.json | 2 +- .../modular_diffusers/image_to_image.json | 2 +- .../multiple_image_edit.json | 2 +- .../modular_diffusers/quantization.json | 2 +- .../modular_diffusers/text_to_image.json | 2 +- .../audio-continuation.json | 2041 +++ .../audio-repaint.json | 1760 +++ .../audio-variation.json | 1760 +++ ...audio--ace-step-chinese-new-year-lora.json | 1776 +++ .../text-to-audio--ace-step-custom-lora.json | 1775 +++ .../text-to-audio.json | 1657 +++ .../flux-canny-pipeline/control-image.json | 1915 +++ .../flux-depth-pipeline/control-image.json | 1915 +++ ...-image--flux-lora-cinematic-octane-3d.json | 2016 +++ .../text-to-image--flux-lora-film-noir.json | 1895 +++ ...text-to-image--flux-lora-ghibli-story.json | 1895 +++ ...text-to-image--flux-lora-oil-painting.json | 1895 +++ ...text-to-image--flux-lora-paper-cutout.json | 1895 +++ ...mage--flux-lora-photoreal-documentary.json | 1895 +++ .../text-to-image--flux-lora-retro-comic.json | 1895 +++ .../text-to-image--flux-lora-watercolor.json | 1895 +++ .../flux-dev-pipeline/text-to-image.json | 1774 +++ .../studio/flux-fill-pipeline/inpaint.json | 2051 +++ .../studio/flux-fill-pipeline/outpaint.json | 2051 +++ .../flux-kontext-pipeline/edit-image.json | 1928 +++ .../multi-image-reference-edit.json | 1929 +++ .../flux-krea-pipeline/text-to-image.json | 1776 +++ .../flux-redux-pipeline/edit-image.json | 1928 +++ .../flux-schnell-pipeline/text-to-image.json | 1774 +++ .../flux2-klein-pipeline/edit-image.json | 1926 +++ .../multi-image-reference-edit.json | 1927 +++ .../flux2-klein-pipeline/text-to-image.json | 1774 +++ .../ltxvideo-pipeline/image-to-video.json | 2142 ++++ .../ltxvideo-pipeline/reference-to-video.json | 2143 ++++ .../ltxvideo-pipeline/text-to-video.json | 2008 +++ .../ltxvideo-pipeline/video-to-video.json | 2247 ++++ .../edit-image.json | 1132 ++ .../inpaint.json | 2049 +++ .../outpaint.json | 2103 ++++ .../edit-image.json | 1255 ++ .../multi-image-reference-edit.json | 1256 ++ .../layer-decomposition.json | 1141 ++ .../control-image.json | 1518 +++ .../text-to-image.json | 1774 +++ .../image-to-video.json | 1976 +++ .../wan-ti2-vpipeline/text-to-video.json | 3516 ++++++ .../wan-vacepipeline/control-to-video.json | 2247 ++++ .../wan-vacepipeline/text-to-video.json | 2008 +++ .../wan-vacepipeline/video-inpaint.json | 2473 ++++ .../wan-vacepipeline/video-outpaint.json | 2473 ++++ .../wan-video-pipeline/text-to-video.json | 2008 +++ .../wan-video-pipeline/video-color-edit.json | 2247 ++++ .../wan-video-pipeline/video-to-video.json | 2247 ++++ .../text-to-image--fast-lora.json | 1894 +++ .../text-to-image--z-image-lora-style.json | 1894 +++ .../text-to-image.json | 1774 +++ data/model-artifact-catalog.json | 217 + data/workflow-library-manifest.json | 1479 +++ docs/README.md | 8 +- docs/accelerator-installation.md | 36 +- docs/api-reference.md | 141 +- docs/hugging-face-standards.md | 81 + docs/optional-runtime-optimizations.md | 103 + docs/runtime-support-matrix.md | 5 +- docs/source-provenance.md | 38 + docs/troubleshooting.md | 94 +- install.ps1 | 2 +- install.sh | 0 main.py | 101 +- modiff/NodeBase.py | 451 +- modiff/auto_resource.py | 1208 +- modiff/client.py | 1 + modiff/compatibility/accelerators.v1.json | 23 +- modiff/config.py | 6 +- modiff/diffusers_offload.py | 298 +- modiff/diffusers_offload_modes.py | 17 + modiff/diffusers_profiles.py | 291 +- modiff/disk_activity.py | 201 + modiff/hardware.py | 375 +- modiff/install.py | 376 +- modiff/media_assets.py | 269 + modiff/media_import.py | 347 + modiff/media_io.py | 571 + modiff/model_artifact_catalog.py | 213 + modiff/modelstore.py | 1 + modiff/optimization_packages.py | 1089 ++ modiff/path_identifiers.py | 154 + modiff/preflight.py | 72 +- modiff/runtime_profile.py | 314 +- modiff/server.py | 10515 ++++++++++++---- modiff/supervisor_control.py | 298 + modiff/workflow_store.py | 91 + modules/Audio/__init__.py | 2 +- modules/Audio/main.py | 533 +- modules/Color/main.py | 3 +- modules/DiffusersAdapters/__init__.py | 1 + modules/DiffusersAdapters/main.py | 384 + modules/DiffusersAudio/__init__.py | 2 +- modules/DiffusersAudio/main.py | 523 +- modules/DiffusersImage/__init__.py | 4 +- modules/DiffusersImage/main.py | 953 +- modules/DiffusersRuntime/__init__.py | 1 + modules/DiffusersRuntime/main.py | 2226 ++++ modules/DiffusersVideo/__init__.py | 33 + modules/DiffusersVideo/main.py | 1843 +++ .../main.py => DiffusersVideo/wan_vace.py} | 217 +- modules/Experiments/FLUXKontext.py | 516 - modules/Experiments/StableDiffusion3.py | 476 - modules/Experiments/StableDiffusionXL.py | 48 - modules/Experiments/VAE.py | 65 - modules/Experiments/__init__.py | 347 - modules/Experiments/flux_layers.py | 1279 -- modules/Experiments/main.py | 4 - modules/Experiments/sd3_layers.py | 1456 --- modules/Experiments/t5_layers.py | 393 - modules/Experiments/utils.py | 200 - modules/Image/main.py | 162 +- modules/ImageFilters/__init__.py | 5 +- modules/ImageFilters/main.py | 4 +- modules/MediaSource/__init__.py | 1 + modules/MediaSource/main.py | 73 + modules/ModelArtifact/__init__.py | 2 +- modules/ModelArtifact/main.py | 263 +- modules/ModularDiffusers/README.md | 21 +- modules/ModularDiffusers/__init__.py | 153 +- modules/ModularDiffusers/adapters.py | 78 +- modules/ModularDiffusers/controlnet.py | 12 +- modules/ModularDiffusers/denoise.py | 74 +- modules/ModularDiffusers/dynamic_node.py | 84 +- modules/ModularDiffusers/embeddings.py | 39 +- modules/ModularDiffusers/guiders.py | 151 +- modules/ModularDiffusers/latents.py | 126 +- modules/ModularDiffusers/loaders.py | 586 +- modules/ModularDiffusers/modular_utils.py | 167 +- modules/ModularDiffusers/pipeline_schema.py | 35 +- modules/ModularDiffusers/schedulers.py | 1 + modules/ModularDiffusers/utils.py | 1 + modules/Primitive/main.py | 1 + modules/QwenImage/__init__.py | 1 - modules/QwenImage/main.py | 661 - modules/Segmentation/__init__.py | 3 - modules/Segmentation/main.py | 157 - modules/Spandrel/__init__.py | 14 +- modules/Spandrel/main.py | 103 +- modules/Tensor/main.py | 3 +- modules/Text/main.py | 3 +- modules/Video/main.py | 1164 +- modules/VideoColor/__init__.py | 2 +- modules/VideoConditioning/__init__.py | 2 +- modules/VideoConditioning/main.py | 134 +- modules/WanVACE/__init__.py | 1 - modules/WorkflowControl/__init__.py | 2 + modules/WorkflowControl/main.py | 419 + modules/__init__.py | 20 +- pyproject.toml | 40 +- requirements.txt | 15 - requirements/profiles/cpu.txt | 9 +- requirements/profiles/intel-xpu.txt | 6 + requirements/profiles/nvidia-cuda.txt | 9 +- requirements/test.txt | 2 + requirements_extras.txt | 3 - requirements_macos.txt | 21 - requirements_quant.txt | 6 - run.ps1 | 3 +- run.sh | 19 +- scripts/lock_accelerator_wheels.py | 72 +- scripts/with-runtime-env.sh | 47 + tests/test_accelerator_manifest.py | 7 +- tests/test_accelerator_requirements.py | 35 + tests/test_app_managed_auxiliary_models.py | 122 + tests/test_audio_operations.py | 207 + tests/test_auto_resource.py | 743 +- tests/test_deterministic_mode.py | 129 + tests/test_diffusers_adapters.py | 125 + tests/test_diffusers_audio.py | 347 +- tests/test_diffusers_image_registry.py | 940 +- tests/test_diffusers_offload.py | 677 +- tests/test_diffusers_profiles.py | 51 + tests/test_diffusers_runtime.py | 790 ++ tests/test_diffusers_video_registry.py | 1160 ++ tests/test_disk_activity.py | 48 + tests/test_graph_catalog_integrity.py | 193 + tests/test_graph_queue_ack.py | 100 + tests/test_hardware.py | 109 +- tests/test_hf_download_concurrency.py | 115 + tests/test_hf_download_errors.py | 61 + tests/test_hf_download_progress.py | 583 +- tests/test_install_guidance.py | 408 +- tests/test_lock_accelerator_wheels.py | 51 + tests/test_main_supervisor.py | 88 + tests/test_media_assets.py | 56 + tests/test_media_import.py | 159 + tests/test_media_io.py | 222 + tests/test_media_preview_urls.py | 274 + tests/test_memory_manager.py | 28 + tests/test_model_artifact_catalog.py | 100 + tests/test_model_artifact_options.py | 16 + tests/test_model_capabilities.py | 106 + ...est_modular_diffusers_upstream_contract.py | 310 + tests/test_modular_image_outputs.py | 111 + tests/test_modular_pipeline_recovery.py | 186 + tests/test_node_base.py | 372 +- tests/test_optimization_packages.py | 193 + tests/test_path_identifiers.py | 140 + tests/test_qwen_inpaint_contract.py | 69 + tests/test_runtime_option_catalog.py | 51 + tests/test_runtime_profile.py | 261 + tests/test_runtime_status.py | 1189 +- tests/test_server_security.py | 330 + tests/test_studio_blocks.py | 27 + tests/test_supervisor_control.py | 189 + tests/test_torch_utils.py | 16 +- tests/test_video_composition.py | 59 + tests/test_video_conditioning.py | 44 + .../test_video_conditioning_preprocessors.py | 36 + tests/test_video_operations.py | 228 + tests/test_wan_vace.py | 79 +- tests/test_workflow_control_nodes.py | 131 + tests/test_workflow_loops.py | 235 + tests/test_workflow_store.py | 588 + utils/huggingface.py | 589 +- utils/memory_menager.py | 203 +- utils/paths.py | 1 + utils/quantization.py | 148 - utils/spline.py | 74 - utils/torch_utils.py | 1 + uv.lock | 3330 ----- web/THIRD_PARTY_LICENSES.txt | 6920 ++++++++++ web/assets/graph-vendor.css | 1 + web/assets/graph-vendor.js | 19 + web/assets/index.css | 2 +- web/assets/index.js | 149 +- web/assets/rolldown-runtime.js | 1 + web/assets/template-asset-source.v1.json | 1 + web/index.html | 2 + web/template-gallery/high_quality.webp | Bin 451678 -> 0 bytes .../inputs/qwen-carton-layout-control.png | Bin 11137 -> 0 bytes .../qwen_edit_strength_sweep.before.webp | Bin 262892 -> 0 bytes .../inputs/wan-industrial-outpaint-mask.mp4 | Bin 4292 -> 0 bytes .../inputs/wan-industrial-outpaint-source.mp4 | Bin 365525 -> 0 bytes web/template-gallery/low_vram.webp | Bin 48944 -> 0 bytes web/template-gallery/manifest.json | 382 - .../qwen_edit_strength_sweep.webp | Bin 211152 -> 0 bytes .../qwen_low_vram_poster_layout.webp | Bin 125204 -> 0 bytes .../qwen_low_vram_product_concept.webp | Bin 113248 -> 0 bytes .../qwen_low_vram_text_rendering.webp | Bin 242606 -> 0 bytes .../qwen_poster_logo_text.webp | Bin 221998 -> 0 bytes web/template-gallery/qwen_product_mockup.webp | Bin 196402 -> 0 bytes web/template-gallery/qwen_text_rendering.webp | Bin 229832 -> 0 bytes .../high_quality.duplicate-provenance.json | 1877 --- .../reviews/high_quality.quality-review.json | 29 - .../low_vram.duplicate-provenance.json | 1903 --- .../reviews/low_vram.quality-review.json | 29 - ...en_edit_strength_sweep.quality-review.json | 80 - ...it_strength_sweep.reviewed-provenance.json | 1056 -- ...am_poster_layout.duplicate-provenance.json | 1357 -- ...low_vram_poster_layout.quality-review.json | 27 - ..._product_concept.duplicate-provenance.json | 1357 -- ...w_vram_product_concept.quality-review.json | 28 - ...m_text_rendering.duplicate-provenance.json | 1357 -- ...ow_vram_text_rendering.quality-review.json | 27 - ...poster_logo_text.duplicate-provenance.json | 1877 --- .../qwen_poster_logo_text.quality-review.json | 30 - ...n_product_mockup.duplicate-provenance.json | 1877 --- .../qwen_product_mockup.quality-review.json | 30 - ...n_text_rendering.duplicate-provenance.json | 1877 --- .../qwen_text_rendering.quality-review.json | 30 - ...ic_contact_sheet.duplicate-provenance.json | 1903 --- ...inematic_contact_sheet.quality-review.json | 29 - .../z_image_poster.duplicate-provenance.json | 1903 --- .../z_image_poster.quality-review.json | 29 - ...e_product_mockup.duplicate-provenance.json | 1903 --- ...z_image_product_mockup.quality-review.json | 29 - ...ge_quick_concept.duplicate-provenance.json | 1885 --- .../z_image_quick_concept.quality-review.json | 30 - .../wan_vace_cinematic_text_to_video.mp4 | Bin 191339 -> 0 bytes ...n_vace_cinematic_text_to_video.poster.webp | Bin 41922 -> 0 bytes .../z_image_cinematic_contact_sheet.webp | Bin 223184 -> 0 bytes web/template-gallery/z_image_poster.webp | Bin 140378 -> 0 bytes .../z_image_product_mockup.webp | Bin 133580 -> 0 bytes .../z_image_quick_concept.webp | Bin 228938 -> 0 bytes 292 files changed, 146088 insertions(+), 36130 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml create mode 100644 AGENTS.md create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 data/graphs/studio/ace-step-audio-pipeline/audio-continuation.json create mode 100644 data/graphs/studio/ace-step-audio-pipeline/audio-repaint.json create mode 100644 data/graphs/studio/ace-step-audio-pipeline/audio-variation.json create mode 100644 data/graphs/studio/ace-step-audio-pipeline/text-to-audio--ace-step-chinese-new-year-lora.json create mode 100644 data/graphs/studio/ace-step-audio-pipeline/text-to-audio--ace-step-custom-lora.json create mode 100644 data/graphs/studio/ace-step-audio-pipeline/text-to-audio.json create mode 100644 data/graphs/studio/flux-canny-pipeline/control-image.json create mode 100644 data/graphs/studio/flux-depth-pipeline/control-image.json create mode 100644 data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-cinematic-octane-3d.json create mode 100644 data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-film-noir.json create mode 100644 data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-ghibli-story.json create mode 100644 data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-oil-painting.json create mode 100644 data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-paper-cutout.json create mode 100644 data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-photoreal-documentary.json create mode 100644 data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-retro-comic.json create mode 100644 data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-watercolor.json create mode 100644 data/graphs/studio/flux-dev-pipeline/text-to-image.json create mode 100644 data/graphs/studio/flux-fill-pipeline/inpaint.json create mode 100644 data/graphs/studio/flux-fill-pipeline/outpaint.json create mode 100644 data/graphs/studio/flux-kontext-pipeline/edit-image.json create mode 100644 data/graphs/studio/flux-kontext-pipeline/multi-image-reference-edit.json create mode 100644 data/graphs/studio/flux-krea-pipeline/text-to-image.json create mode 100644 data/graphs/studio/flux-redux-pipeline/edit-image.json create mode 100644 data/graphs/studio/flux-schnell-pipeline/text-to-image.json create mode 100644 data/graphs/studio/flux2-klein-pipeline/edit-image.json create mode 100644 data/graphs/studio/flux2-klein-pipeline/multi-image-reference-edit.json create mode 100644 data/graphs/studio/flux2-klein-pipeline/text-to-image.json create mode 100644 data/graphs/studio/ltxvideo-pipeline/image-to-video.json create mode 100644 data/graphs/studio/ltxvideo-pipeline/reference-to-video.json create mode 100644 data/graphs/studio/ltxvideo-pipeline/text-to-video.json create mode 100644 data/graphs/studio/ltxvideo-pipeline/video-to-video.json create mode 100644 data/graphs/studio/qwen-image-edit-modular-pipeline/edit-image.json create mode 100644 data/graphs/studio/qwen-image-edit-modular-pipeline/inpaint.json create mode 100644 data/graphs/studio/qwen-image-edit-modular-pipeline/outpaint.json create mode 100644 data/graphs/studio/qwen-image-edit-plus-modular-pipeline/edit-image.json create mode 100644 data/graphs/studio/qwen-image-edit-plus-modular-pipeline/multi-image-reference-edit.json create mode 100644 data/graphs/studio/qwen-image-layered-modular-pipeline/layer-decomposition.json create mode 100644 data/graphs/studio/qwen-image-modular-pipeline/control-image.json create mode 100644 data/graphs/studio/qwen-image-modular-pipeline/text-to-image.json create mode 100644 data/graphs/studio/wan-image-to-video-pipeline/image-to-video.json create mode 100644 data/graphs/studio/wan-ti2-vpipeline/text-to-video.json create mode 100644 data/graphs/studio/wan-vacepipeline/control-to-video.json create mode 100644 data/graphs/studio/wan-vacepipeline/text-to-video.json create mode 100644 data/graphs/studio/wan-vacepipeline/video-inpaint.json create mode 100644 data/graphs/studio/wan-vacepipeline/video-outpaint.json create mode 100644 data/graphs/studio/wan-video-pipeline/text-to-video.json create mode 100644 data/graphs/studio/wan-video-pipeline/video-color-edit.json create mode 100644 data/graphs/studio/wan-video-pipeline/video-to-video.json create mode 100644 data/graphs/studio/zimage-modular-pipeline/text-to-image--fast-lora.json create mode 100644 data/graphs/studio/zimage-modular-pipeline/text-to-image--z-image-lora-style.json create mode 100644 data/graphs/studio/zimage-modular-pipeline/text-to-image.json create mode 100644 data/model-artifact-catalog.json create mode 100644 data/workflow-library-manifest.json create mode 100644 docs/hugging-face-standards.md create mode 100644 docs/optional-runtime-optimizations.md create mode 100644 docs/source-provenance.md mode change 100755 => 100644 install.sh create mode 100644 modiff/diffusers_offload_modes.py create mode 100644 modiff/disk_activity.py create mode 100644 modiff/media_assets.py create mode 100644 modiff/media_import.py create mode 100644 modiff/media_io.py create mode 100644 modiff/model_artifact_catalog.py create mode 100644 modiff/optimization_packages.py create mode 100644 modiff/path_identifiers.py create mode 100644 modiff/supervisor_control.py create mode 100644 modiff/workflow_store.py create mode 100644 modules/DiffusersAdapters/__init__.py create mode 100644 modules/DiffusersAdapters/main.py create mode 100644 modules/DiffusersRuntime/__init__.py create mode 100644 modules/DiffusersRuntime/main.py create mode 100644 modules/DiffusersVideo/__init__.py create mode 100644 modules/DiffusersVideo/main.py rename modules/{WanVACE/main.py => DiffusersVideo/wan_vace.py} (56%) delete mode 100644 modules/Experiments/FLUXKontext.py delete mode 100644 modules/Experiments/StableDiffusion3.py delete mode 100644 modules/Experiments/StableDiffusionXL.py delete mode 100644 modules/Experiments/VAE.py delete mode 100644 modules/Experiments/__init__.py delete mode 100644 modules/Experiments/flux_layers.py delete mode 100644 modules/Experiments/main.py delete mode 100644 modules/Experiments/sd3_layers.py delete mode 100644 modules/Experiments/t5_layers.py delete mode 100644 modules/Experiments/utils.py create mode 100644 modules/MediaSource/__init__.py create mode 100644 modules/MediaSource/main.py delete mode 100644 modules/QwenImage/__init__.py delete mode 100644 modules/QwenImage/main.py delete mode 100644 modules/Segmentation/__init__.py delete mode 100644 modules/Segmentation/main.py delete mode 100644 modules/WanVACE/__init__.py create mode 100644 modules/WorkflowControl/__init__.py create mode 100644 modules/WorkflowControl/main.py delete mode 100644 requirements.txt create mode 100644 requirements/profiles/intel-xpu.txt create mode 100644 requirements/test.txt delete mode 100644 requirements_extras.txt delete mode 100644 requirements_macos.txt delete mode 100644 requirements_quant.txt mode change 100755 => 100644 run.sh create mode 100644 scripts/with-runtime-env.sh create mode 100644 tests/test_accelerator_requirements.py create mode 100644 tests/test_app_managed_auxiliary_models.py create mode 100644 tests/test_audio_operations.py create mode 100644 tests/test_deterministic_mode.py create mode 100644 tests/test_diffusers_adapters.py create mode 100644 tests/test_diffusers_profiles.py create mode 100644 tests/test_diffusers_runtime.py create mode 100644 tests/test_diffusers_video_registry.py create mode 100644 tests/test_disk_activity.py create mode 100644 tests/test_graph_catalog_integrity.py create mode 100644 tests/test_graph_queue_ack.py create mode 100644 tests/test_hf_download_concurrency.py create mode 100644 tests/test_hf_download_errors.py create mode 100644 tests/test_lock_accelerator_wheels.py create mode 100644 tests/test_main_supervisor.py create mode 100644 tests/test_media_assets.py create mode 100644 tests/test_media_import.py create mode 100644 tests/test_media_io.py create mode 100644 tests/test_media_preview_urls.py create mode 100644 tests/test_memory_manager.py create mode 100644 tests/test_model_artifact_catalog.py create mode 100644 tests/test_model_artifact_options.py create mode 100644 tests/test_model_capabilities.py create mode 100644 tests/test_modular_diffusers_upstream_contract.py create mode 100644 tests/test_modular_image_outputs.py create mode 100644 tests/test_modular_pipeline_recovery.py create mode 100644 tests/test_optimization_packages.py create mode 100644 tests/test_path_identifiers.py create mode 100644 tests/test_qwen_inpaint_contract.py create mode 100644 tests/test_runtime_option_catalog.py create mode 100644 tests/test_runtime_profile.py create mode 100644 tests/test_server_security.py create mode 100644 tests/test_supervisor_control.py create mode 100644 tests/test_video_composition.py create mode 100644 tests/test_video_conditioning.py create mode 100644 tests/test_video_conditioning_preprocessors.py create mode 100644 tests/test_video_operations.py create mode 100644 tests/test_workflow_control_nodes.py create mode 100644 tests/test_workflow_loops.py create mode 100644 tests/test_workflow_store.py delete mode 100644 utils/quantization.py delete mode 100644 utils/spline.py delete mode 100644 uv.lock create mode 100644 web/THIRD_PARTY_LICENSES.txt create mode 100644 web/assets/graph-vendor.css create mode 100644 web/assets/graph-vendor.js create mode 100644 web/assets/rolldown-runtime.js create mode 100644 web/assets/template-asset-source.v1.json delete mode 100644 web/template-gallery/high_quality.webp delete mode 100644 web/template-gallery/inputs/qwen-carton-layout-control.png delete mode 100644 web/template-gallery/inputs/qwen_edit_strength_sweep.before.webp delete mode 100644 web/template-gallery/inputs/wan-industrial-outpaint-mask.mp4 delete mode 100644 web/template-gallery/inputs/wan-industrial-outpaint-source.mp4 delete mode 100644 web/template-gallery/low_vram.webp delete mode 100644 web/template-gallery/manifest.json delete mode 100644 web/template-gallery/qwen_edit_strength_sweep.webp delete mode 100644 web/template-gallery/qwen_low_vram_poster_layout.webp delete mode 100644 web/template-gallery/qwen_low_vram_product_concept.webp delete mode 100644 web/template-gallery/qwen_low_vram_text_rendering.webp delete mode 100644 web/template-gallery/qwen_poster_logo_text.webp delete mode 100644 web/template-gallery/qwen_product_mockup.webp delete mode 100644 web/template-gallery/qwen_text_rendering.webp delete mode 100644 web/template-gallery/reviews/high_quality.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/high_quality.quality-review.json delete mode 100644 web/template-gallery/reviews/low_vram.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/low_vram.quality-review.json delete mode 100644 web/template-gallery/reviews/qwen_edit_strength_sweep.quality-review.json delete mode 100644 web/template-gallery/reviews/qwen_edit_strength_sweep.reviewed-provenance.json delete mode 100644 web/template-gallery/reviews/qwen_low_vram_poster_layout.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/qwen_low_vram_poster_layout.quality-review.json delete mode 100644 web/template-gallery/reviews/qwen_low_vram_product_concept.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/qwen_low_vram_product_concept.quality-review.json delete mode 100644 web/template-gallery/reviews/qwen_low_vram_text_rendering.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/qwen_low_vram_text_rendering.quality-review.json delete mode 100644 web/template-gallery/reviews/qwen_poster_logo_text.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/qwen_poster_logo_text.quality-review.json delete mode 100644 web/template-gallery/reviews/qwen_product_mockup.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/qwen_product_mockup.quality-review.json delete mode 100644 web/template-gallery/reviews/qwen_text_rendering.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/qwen_text_rendering.quality-review.json delete mode 100644 web/template-gallery/reviews/z_image_cinematic_contact_sheet.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/z_image_cinematic_contact_sheet.quality-review.json delete mode 100644 web/template-gallery/reviews/z_image_poster.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/z_image_poster.quality-review.json delete mode 100644 web/template-gallery/reviews/z_image_product_mockup.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/z_image_product_mockup.quality-review.json delete mode 100644 web/template-gallery/reviews/z_image_quick_concept.duplicate-provenance.json delete mode 100644 web/template-gallery/reviews/z_image_quick_concept.quality-review.json delete mode 100644 web/template-gallery/wan_vace_cinematic_text_to_video.mp4 delete mode 100644 web/template-gallery/wan_vace_cinematic_text_to_video.poster.webp delete mode 100644 web/template-gallery/z_image_cinematic_contact_sheet.webp delete mode 100644 web/template-gallery/z_image_poster.webp delete mode 100644 web/template-gallery/z_image_product_mockup.webp delete mode 100644 web/template-gallery/z_image_quick_concept.webp diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..2e4c2d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,47 @@ +name: Bug report +description: Report a reproducible MoDiff backend or runtime defect. +title: "[Bug] " +labels: + - bug +body: + - type: markdown + attributes: + value: | + Do not include Hugging Face tokens, private media, personal paths, or exploit details. Report security issues through the private process in `SECURITY.md`. + - type: checkboxes + attributes: + label: Before filing + options: + - label: I searched existing issues and tested the current supported branch. + required: true + - label: This is not a security vulnerability or a request for help with an unsupported third-party runtime. + required: true + - type: textarea + attributes: + label: Problem + description: Explain what is wrong and why it matters. + validations: + required: true + - type: textarea + attributes: + label: Minimal reproduction + description: Provide the smallest graph, command, or code sample that reproduces the issue without private data. + render: shell + validations: + required: true + - type: textarea + attributes: + label: Expected and actual behavior + validations: + required: true + - type: textarea + attributes: + label: Environment + description: Include the MoDiff commit, OS, Python version, accelerator profile, and sanitized preflight output. + validations: + required: true + - type: textarea + attributes: + label: Sanitized logs + description: Include the shortest relevant traceback. Remove tokens, URLs with credentials, media, prompts, and local paths. + render: text diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000..f4c2adf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,36 @@ +name: Feature request +description: Propose a focused change within MoDiff's Hugging Face Diffusers runtime boundary. +title: "[Feature] " +labels: + - enhancement +body: + - type: textarea + attributes: + label: Motivation + description: Describe the user problem before describing an implementation. + validations: + required: true + - type: textarea + attributes: + label: Proposed user experience + description: Show how a user would invoke and understand the feature. + validations: + required: true + - type: textarea + attributes: + label: Diffusers support + description: Link the relevant official Diffusers or Modular Diffusers API/model documentation and explain how the proposal stays inside that execution boundary. + validations: + required: true + - type: textarea + attributes: + label: Alternatives and compatibility + description: Note existing MoDiff patterns, graph/API compatibility, dependencies, hardware needs, and migration concerns. + validations: + required: true + - type: textarea + attributes: + label: Validation plan + description: List focused unit, contract, integration, documentation, and live-model checks as applicable. + validations: + required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 08b721b..fd9d3c6 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,13 +2,27 @@ - +## Scope And Human Review + +- Agreed scope / linked issue or discussion: +- Human self-review completed; every changed line and generated artifact is understood: yes / no +- AI assistance used (tool and role), or `none`: +- Follow-up work deliberately left out of this change: + ## Checks -- [ ] `uv lock --check` -- [ ] `uv pip check` -- [ ] `uv run python -m modiff.preflight --json --check-port 8088 --fail-on-error` -- [ ] `uv run python -m unittest discover -s tests -v` +- [ ] `uvx --from ruff==0.12.7 ruff check . --select E9,F` +- [ ] `uv pip check --python .venv/bin/python` (use `.venv/Scripts/python.exe` on Windows) +- [ ] `./.venv/bin/python -m modiff.preflight --json --check-port 8088 --fail-on-error` +- [ ] `./.venv/bin/python -m pytest -q` - [ ] `bash -n run.sh` when a POSIX shell is available +- [ ] Exact command results and any skipped checks are recorded below. + +## Validation Results + +- Commands and results: +- Checks skipped, with reason: +- Live accelerator/model evidence, or `not claimed`: ## Compatibility, Security, And Proof diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16f56cb..3577fda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,15 +1,47 @@ name: CI on: [push, pull_request] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + with: { version: '0.11.26' } + - run: uvx --from ruff==0.12.7 ruff check . --select E9,F + - run: bash -n install.sh run.sh scripts/with-runtime-env.sh + backend: strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-14] - python: ['3.12'] + include: + - os: ubuntu-latest + python: '3.12' + managed-python: ./.venv/bin/python + - os: windows-latest + python: '3.12' + managed-python: ./.venv/Scripts/python.exe + - os: macos-14 + python: '3.12' + managed-python: ./.venv/bin/python runs-on: ${{ matrix.os }} + timeout-minutes: 35 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: { python-version: '${{ matrix.python }}' } - - run: python -m unittest tests.test_accelerator_manifest tests.test_install_detection tests.test_install_guidance tests.test_hardware - - run: python -m modiff.install --accelerator cpu --dry-run --json + - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 + with: { version: '0.11.26' } + - run: python -m modiff.install --accelerator cpu --backend-only --non-interactive --json + - run: uv pip install --python ${{ matrix.managed-python }} -r requirements/test.txt + - run: uv pip check --python ${{ matrix.managed-python }} + - run: ${{ matrix.managed-python }} -m modiff.preflight --json --check-port 8088 --fail-on-error + - run: ${{ matrix.managed-python }} -m pytest -q diff --git a/.gitignore b/.gitignore index 9424656..b8ddc03 100644 --- a/.gitignore +++ b/.gitignore @@ -22,17 +22,27 @@ custom/* #!custom/Example/** data/* +!data/model-artifact-catalog.json !data/graphs/ +!data/workflow-library-manifest.json data/graphs/* !data/graphs/modiff/ !data/graphs/modiff/** !data/graphs/modular_diffusers/ !data/graphs/modular_diffusers/** +!data/graphs/studio/ +!data/graphs/studio/** +!data/graphs/experimental/ +!data/graphs/experimental/** #!data/.gitkeep web/user/* !web/user/ExampleField.js +# The client resolves Gallery bytes from a pinned public Hugging Face Dataset. +# Offline snapshots are local caches and must not be mirrored into source Git. +web/template-gallery/ + .venv .venv.next/ .venv.previous/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..5552556 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# MoDiff Backend Agent Instructions + +These rules apply to AI-assisted work in this repository. `CONTRIBUTING.md` is the complete contributor guide; read it together with `SECURITY.md` and the relevant document under `docs/` before editing. + +## Scope And Runtime Boundary + +- Keep each change focused on a diagnosed problem. Trace the existing call path and tests before editing, and remove incidental generated files from the diff. +- Hugging Face Diffusers and Modular Diffusers are MoDiff's only supported model-execution layer. Do not add an alternate graph executor, hosted inference provider, independent Transformers application, or another model driver. +- Supporting libraries used by Diffusers and ordinary deterministic media processing are not alternate drivers. They must remain narrowly scoped, documented, and covered by tests. +- Never enable arbitrary remote Python code, mutable model revisions, or custom model execution implicitly. Trust-sensitive behavior requires an explicit operator choice and an immutable revision. +- Prefer existing module, graph, configuration, error, and test patterns. Do not create a parallel workflow representation or model-loading path. + +## Diffusers And Modular Diffusers + +- Keep the reviewed Diffusers revision pinned in the executable installation contract and update its compatibility test when changing it. +- Use Diffusers loaders, pipelines, components, schedulers, adapters, and offload hooks instead of reimplementing upstream behavior. +- Modular blocks should declare inputs, outputs, and dependencies clearly, avoid hidden cross-block state, and remain composable through `init_pipeline`. +- Keep model-specific differences explicit and small. Put reusable behavior in the existing shared Diffusers modules rather than copying it into another pipeline. +- Prefer `safetensors`; document and test any unavoidable unsafe deserialization or remote-code boundary. + +## Security And Data + +- Treat every HTTP, WebSocket, filesystem, archive, URL, model-repository, and workflow boundary as untrusted input. +- Mutations must not use `GET`. Resolve filesystem targets before access, reject traversal and symlink escapes, and keep request sizes finite. +- The supported server boundary is a trusted single user on `127.0.0.1`; do not weaken that default or imply that CORS is authentication. +- Never commit `config.ini`, tokens, local paths, generated outputs, model caches, qualification workspaces, virtual environments, logs, or template media. +- Public template media belongs in the configured public Hugging Face Dataset repository. Keep only its versioned source descriptor, hashes, and documentation in Git. + +## Quality And Evidence + +- Add a regression test that fails for the original defect and covers related instances of the same pattern. +- Keep public interfaces, errors, configuration, and non-obvious invariants documented. Examples must be runnable and must not require private files. +- Distinguish static inspection, unit/contract tests, HTTP smoke tests, and live model output. Never present one proof level as another. +- Report the exact commands and results you ran. A human maintainer remains responsible for understanding every changed line and reviewing generated content. + +Run the focused tests while iterating, then use the complete backend gate documented in `CONTRIBUTING.md`. For changes that affect the client contract, also run the compatible MoDiff Client gate and its relevant browser tests. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f59c442..18412cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,19 +6,24 @@ Before starting, read [SECURITY.md](SECURITY.md) and the relevant guide in [docs ## Development setup -Use Python 3.12 and the committed lockfile: +Use Python 3.12 and create the same managed CPU profile used by baseline CI: ```bash -uv sync --frozen -uv run python -m modiff.preflight --json --check-port 8088 --fail-on-error +./install.sh --accelerator cpu --backend-only +uv pip install --python .venv/bin/python -r requirements/test.txt +./scripts/with-runtime-env.sh ./.venv/bin/python -m modiff.preflight --json --check-port 8088 --fail-on-error ``` -Install only the extras required for the code path being changed. For example: +On Windows PowerShell, use the corresponding managed commands: -```bash -uv sync --frozen --extra quantization --extra spandrel +```powershell +.\install.ps1 -Accelerator cpu -BackendOnly +uv pip install --python .venv/Scripts/python.exe -r requirements/test.txt +.\.venv\Scripts\python.exe -m modiff.preflight --json --check-port 8088 --fail-on-error ``` +Choose the qualified accelerator profile relevant to a hardware-specific change and report that validation separately. Do not use `uv sync` or `uv run`: the project is intentionally `uv`-unmanaged because the installer, not the generic resolver, owns the executable Torch profile. + Do not commit `config.ini`, `.env` files, model caches, generated outputs, local logs, virtual environments, or test caches. ## Backend conventions @@ -51,32 +56,32 @@ Add focused tests for registry visibility, constructor safety, field contracts, ## Dependency changes -`pyproject.toml` and `uv.lock` are one change surface. After intentional metadata edits: +The selected file under `requirements/profiles/`, `pyproject.toml`, and `modiff/compatibility/accelerators.v1.json` jointly define the executable runtime contract. Keep direct wheel URLs hash-verified, keep remote source dependencies pinned to immutable revisions, and retain the exact reviewed Diffusers commit. After an intentional dependency or profile edit, rebuild the relevant managed profile: ```bash -uv lock -uv lock --check -uv sync --frozen -uv pip check +./install.sh --accelerator cpu --backend-only --repair +uv pip check --python .venv/bin/python ``` -Commit the updated lockfile. Keep platform markers and optional extras explicit, and update the README/configuration guidance when an install profile changes. If the manual pip fallback is affected, update the matching requirements file too. +Use the corresponding accelerator instead of `cpu` when the change affects CUDA, ROCm, or MPS. Update the compatibility manifest and public installation guidance only when the evidence supports the claim. MoDiff deliberately has no `uv.lock`; do not generate one or describe the top-level requirements files as a cross-platform lock. ## Validation The baseline backend checks are: ```bash -uv lock --check -uv pip check -uv run python -m modiff.preflight --json --check-port 8088 --fail-on-error -uv run python -m unittest discover -s tests -v +uvx --from ruff==0.12.7 ruff check . --select E9,F +uv pip check --python .venv/bin/python +./scripts/with-runtime-env.sh ./.venv/bin/python -m modiff.preflight --json --check-port 8088 --fail-on-error +./scripts/with-runtime-env.sh ./.venv/bin/python -m pytest -q ``` +The wrapper applies the installed accelerator profile's process environment before Python imports Torch. On Windows, run `.venv/Scripts/python.exe` directly in the equivalent commands. + On a host with Git Bash or a POSIX shell: ```bash -bash -n run.sh +bash -n run.sh scripts/with-runtime-env.sh ``` Before reporting a live smoke test, confirm port `8088` is free or intentionally reuse the running process. A file-level inspection is not evidence that a model workflow completed; distinguish unit/contract tests, registry import checks, live backend HTTP checks, and real model-generation proof. @@ -90,7 +95,7 @@ When a change affects the client/backend contract: 1. Update and validate the client source. 2. Run `npm ci` and `npm run check` in MoDiff-client. 3. Mirror the contents of its generated `dist/` directory into this repository's `web/` directory, deleting stale generated bundle files while preserving backend-owned `web/user/` custom fields. -4. Verify that `/`, `/assets/index.js`, and `/template-gallery/manifest.json` are served by a fresh backend. +4. Verify that `/` and `/assets/index.js` are served by a fresh backend. Verify `/template-gallery/manifest.json` only for an explicit offline/local Gallery build; a remote-asset build intentionally omits that directory and route. 5. Include the matching backend and client commit identifiers in the change description when the repositories are published separately. See [README.md](README.md#updating-the-bundled-client) for exact-mirror examples. diff --git a/README.md b/README.md index b95de72..25f5efe 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ + + # MoDiff MoDiff is a local client/server application for building and running node-based machine-learning workflows with a focus on [Hugging Face Diffusers](https://github.com/huggingface/diffusers). The backend discovers Python node modules, executes graphs, manages models and generated media, and serves a bundled web client from `web/`. @@ -5,21 +7,165 @@ MoDiff is a local client/server application for building and running node-based > [!CAUTION] > MoDiff is early-stage software. It is not a production service, a multi-user platform, or a security sandbox. The server has no authentication and can execute model workflows, import custom Python modules, and access files inside its configured working directory. Keep it bound to `127.0.0.1`, install only code you trust, and read [SECURITY.md](SECURITY.md) before changing its network exposure. +## Before you install + +MoDiff is distributed as paired backend and client source checkouts. For normal +use you need: + +- A supported 64-bit Windows, Linux, or Apple Silicon macOS host. +- Git and an internet connection for the first installation and model downloads. +- FFmpeg on `PATH` for workflows that read or export video or audio. +- Enough free disk space for the application, model cache, temporary files, and + outputs. Individual model repositories can require many gigabytes. +- A supported accelerator for practical use of larger models. CPU fallback is + available, but many image workflows will be slow and most large video/audio + workflows will be impractical. + +The guided installer provisions its pinned `uv`, Python 3.12, and Node.js +toolchains when the selected platform supports bootstrapping them. The managed +NVIDIA profile uses the reviewed PyTorch CUDA 12.8 wheels. Qualified Linux AMD +hosts use the pinned ROCm profile. AMD's selected Windows stack is represented +as a conditional target but remains blocked until MoDiff pins and validates its +complete SDK wheel set. Intel Arc and supported Intel integrated graphics use +the preview XPU profile on x86-64 Linux/Windows. Apple Silicon uses reviewed +PyPI PyTorch wheels and MPS. Review the [runtime support matrix](docs/runtime-support-matrix.md) +and [accelerator installation guide](docs/accelerator-installation.md) before +choosing a non-default profile. + +## Install and run + +Clone both repositories into the same parent directory. The directory names and +sibling layout below let the backend installer find, build, and bundle the +client automatically. + +Linux or macOS: + +```bash +git clone https://github.com/sdevil7th/MoDiff.git MoDiff +git clone https://github.com/sdevil7th/MoDiff-client.git MoDiff-client +cd MoDiff +./install.sh --accelerator auto --system-check +./install.sh --accelerator auto +./run.sh +``` + +Windows PowerShell: + +```powershell +git clone https://github.com/sdevil7th/MoDiff.git MoDiff +git clone https://github.com/sdevil7th/MoDiff-client.git MoDiff-client +cd MoDiff +.\install.ps1 -Accelerator auto -SystemCheck +.\install.ps1 -Accelerator auto +.\run.ps1 +``` + +The system-check step reports the proposed accelerator profile and blockers +without installing packages. Review it, then continue with the normal installer +shown on the next line. + +Open . The first installation downloads Python, Node.js, +packages, and the verified Template Gallery assets, so it can take time and use +substantial disk space. Normal Gallery browsing then uses the local bundled +files instead of downloading media during use. + +`run.sh` and `run.ps1` start the backend in the foreground. The backend also +serves the installed frontend bundle, so this starts the complete application; +no separate frontend process is required. Press `Ctrl+C` in that terminal to +stop it. + +The client's `run-dev.sh` and `run-dev.ps1` launch a separate Vite frontend for +hot reload and are intended only for frontend development. Their matching +shutdown commands are `stop-dev.sh` and `stop-dev.ps1`. + +### Verify the installation + +With `run.sh` or `run.ps1` still running, verify the backend from another +terminal: + +```bash +curl --fail http://127.0.0.1:8088/health +curl --fail http://127.0.0.1:8088/runtime/status +``` + +```powershell +Invoke-RestMethod http://127.0.0.1:8088/health +Invoke-RestMethod http://127.0.0.1:8088/runtime/status +``` + +Then open and check these items: + +1. The top-bar connection indicator reports that the backend is connected. +2. **Setup** shows the selected runtime profile and no unresolved environment + repair blocker. +3. **Nodes** loads the live registry and **Models** can inspect the configured + model locations. + +A successful health check proves that the service is responding. It does not +prove that a particular model is installed, licensed, compatible with the +machine, or fast enough for practical use. + +### Run your first workflow + +1. Open **Setup** and resolve any runtime or model-cache blocker first. +2. If a selected Hugging Face repository is gated, accept its terms on Hugging + Face and save a least-privilege read token through **Models**. The token is + stored as plaintext in ignored `config.ini`. +3. Choose **Text to image** or a small image recipe from **Browse recipes**. + Prefer a recipe that Auto marks ready for the detected device; do not use a + large video model as the first installation test. +4. Keep the resource-mode **Auto** switch enabled, enter the required prompt and + inputs, and review the readiness result. +5. Use the attached **Install** or **Repair** action when the selected artifact + is missing or incomplete. Wait for its terminal status rather than assuming + an unchanged download percentage is a failure. +6. Choose one-shot **Run**. Follow model preparation, node, and step progress in + the session shelf or **Queue**. +7. Open **Gallery** after completion to inspect, download, restore, or reuse the + output. Export important workflows instead of relying only on browser state. + +The client [Studio user guide](https://github.com/sdevil7th/MoDiff-client/blob/main/docs/studio-user-flow.md) +explains the complete guided workflow, Auto/Expert controls, Queue, Gallery, +and recovery behavior. + +### What Auto does—and does not do + +Auto chooses an eligible runnable resource recipe using the current backend, +artifact, device, memory, and available qualification evidence. It may select a +dtype, pre-quantized artifact, offload strategy, attention backend, or other +known-safe runtime option. Unqualified recipes remain labeled as such. Auto does not promise the fastest recipe, benchmark +the model before submission, or make every catalog entry runnable. + +In particular, a shared-memory or integrated GPU reporting a large addressable +memory pool is not equivalent to a discrete GPU with the same amount of local +VRAM. A workflow can be technically valid while model placement and every +denoising step remain very slow. The first run can also spend substantial time +downloading, validating, loading, and placing weights before generation starts. + +The same prompt and generation parameters can sometimes run faster with a +different qualified runtime recipe—for example, a compatible pre-quantized +artifact, supported attention kernel, compilation/cache path, or improved +device placement. These choices normally require a new pipeline load and a new +run; they cannot safely accelerate work already in progress. Do not enable an +unqualified runtime quantizer or optional kernel merely because it is visible. + ## What is included - A graph execution backend with queue, progress, interruption, cache, and structured runtime diagnostics. - Diffusers-oriented image, audio, and video nodes, including Qwen Image, Wan VACE, Modular Diffusers, and reusable media/conditioning utilities. -- Hardware-aware resource planning for CUDA, Apple MPS, and CPU fallback. +- Hardware-aware resource planning for NVIDIA CUDA, AMD ROCm, Apple MPS, and CPU fallback. - Hugging Face model discovery, download progress, cache diagnostics, and gated-model token setup. - Local Studio output history, reusable blocks, workflow sharing, and a proof-backed template gallery. - A prebuilt MoDiff client served by the backend at `http://127.0.0.1:8088`. The registry currently spans these module groups: -`Audio`, `Color`, `DiffusersAudio`, `DiffusersImage`, `Experiments`, `Image`, `ImageFilters`, `ModelArtifact`, `ModularDiffusers`, `Primitive`, `QwenImage`, `Segmentation`, `Spandrel`, `Tensor`, `Text`, `Video`, `VideoColor`, `VideoConditioning`, and `WanVACE`. +`Audio`, `Color`, `DiffusersAdapters`, `DiffusersAudio`, `DiffusersImage`, `DiffusersRuntime`, `DiffusersVideo`, `Image`, `ImageFilters`, `MediaSource`, `ModelArtifact`, `ModularDiffusers`, `Primitive`, `Spandrel`, `Tensor`, `Text`, `Video`, `VideoColor`, `VideoConditioning`, and `WorkflowControl`. Some nodes require optional packages, specific model repositories, substantial accelerator memory, or upstream experimental Diffusers APIs. Registry visibility does not by itself guarantee that every node is runnable on every machine. +The deterministic edge, sketch, and optical-flow actions in `VideoConditioning` use the optional `gallery-media` OpenCV extra. Resize, mask alignment, frame selection, and authored shot-list controls remain available in the core install and do not load a separate model runtime. + ## Repository layout | Path | Purpose | @@ -32,44 +178,11 @@ Some nodes require optional packages, specific model repositories, substantial a | `web/` | Generated client bundle served by the backend. The editable frontend lives in the separate MoDiff-client repository. | | `tests/` | Backend contract and regression tests. | -## Requirements - -- Python 3.12. -- [`uv`](https://docs.astral.sh/uv/) for the recommended reproducible installation. -- Git for source checkout and optional custom-module installation. -- FFmpeg for video/audio workflows that use ImageIO or external codecs. -- A supported accelerator for practical use of larger models. CPU fallback exists, but many current workflows will be slow or impractical without CUDA. - -Linux/Windows GPU resolution currently targets PyTorch CUDA 12.8 wheels. Install a compatible NVIDIA driver before expecting CUDA workflows to run. Apple Silicon uses normal PyPI PyTorch wheels and MPS when the installed PyTorch build and host support it. - -## Quick start +## Configuration -From a fresh checkout: - -```bash -uv sync --frozen -uv run python -m modiff.preflight --json --check-port 8088 --fail-on-error -uv run python main.py -``` - -Open after the server starts. - -On Linux or macOS, `run.sh` selects the local virtual environment, then `uv`, then a system Python fallback: - -```bash -chmod +x run.sh -./run.sh -``` - -On Windows, use PowerShell or Command Prompt: - -```powershell -uv sync --frozen -uv run python -m modiff.preflight --json --check-port 8088 --fail-on-error -uv run python main.py -``` - -The defaults work without `config.ini`. To customize them, copy the example first: +MoDiff is distributed as paired backend and client source checkouts, not as a +standalone Python wheel. The defaults work without `config.ini`. To customize +them, copy the example first: ```powershell Copy-Item config.example.ini config.ini @@ -81,73 +194,57 @@ cp config.example.ini config.ini `config.ini` is intentionally ignored because it may contain a Hugging Face token and machine-local paths. -## Installation profiles +## Managed installation profiles -The lockfile is committed. Use `--frozen` for a reproducible checkout install, and name every optional extra needed by the workflows you intend to run. +MoDiff's installer owns the executable Python/Torch environment. The project is intentionally marked `uv`-unmanaged, so `uv sync` and `uv run` are not supported setup or launch commands. The installer stages a fresh environment, checks its package policy and a real device tensor, then atomically promotes it to `.venv/` while retaining the previous environment for rollback. When the sibling client is installed, setup also downloads and SHA-256 verifies the pinned rights-approved Template Gallery snapshot and bundles it under `web/template-gallery` so normal use does not wait on Hub media requests. Four permission-dependent preview files are currently unavailable; their templates remain usable and do not request those files. -| Extra | Purpose | -| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `apple-silicon` | Explicit Apple Silicon/MPS installation profile using PyPI PyTorch packages. | -| `cuda` | Adds CUDA-oriented optional acceleration packages such as `xformers`; base Linux/Windows resolution already selects CUDA PyTorch wheels. | -| `nunchaku` | Installs the platform-specific Nunchaku wheel pinned by `pyproject.toml`. | -| `spandrel` | Enables Spandrel upscaler nodes. | -| `background-removal` | Enables `transparent-background` workflows. | -| `quantization` | Adds DFloat11, GGUF, Quanto, TorchAO, kernels, and related quantization support where platform markers allow it. | -| `non-commercial` | Adds `rembg[gpu]`; review its package/model licenses before redistribution or commercial use. | +| Installer choice | Managed profile | Current scope | +| --- | --- | --- | +| `auto` | Host-dependent | Selects a qualified profile or a safe CPU fallback. | +| `nvidia` | `nvidia-cuda` | Linux/Windows NVIDIA with the reviewed CUDA 12.8 PyTorch profile. | +| `amd` | OS-dependent AMD profile | Qualified Linux AMD/ROCm hosts. Windows is a conditional official platform, but MoDiff blocks installation until the complete SDK wheel set and physical proof are pinned. | +| `intel` | `intel-xpu` | Preview PyTorch XPU profile for supported Intel Arc and integrated graphics on x86-64 Linux/Windows. | +| `mps` | `apple-mps` | Apple Silicon using the reviewed MPS-capable PyTorch profile. | +| `cpu` | `cpu` | Portable CPU environment for development and fallback. | -Examples: +If an installation was interrupted, resume its external journal instead of +starting unrelated setup work: ```bash -# Apple Silicon -uv sync --frozen --extra apple-silicon - -# Linux/Windows with optional CUDA acceleration and quantization -uv sync --frozen --extra cuda --extra quantization - -# Add Nunchaku and Spandrel support -uv sync --frozen --extra nunchaku --extra spandrel +./install.sh --accelerator auto --resume ``` -Avoid `--all-extras` unless you have reviewed the platform, build-tool, CUDA, and license requirements of every optional dependency. - -### Manual pip fallback - -`uv` is the maintained installation path. The requirements files are provided for manual environments, but they are not a cross-platform replacement for the lockfile. +```powershell +.\install.ps1 -Accelerator auto -Resume +``` -On Linux, install the appropriate PyTorch build for the host first, then: +Rebuild and validate the selected managed profile when preflight reports +runtime-contract drift or a repair requirement: ```bash -python -m venv .venv -source .venv/bin/activate -python -m pip install -U pip wheel setuptools -python -m pip install -U -r requirements.txt -python -m modiff.preflight --json --check-port 8088 --fail-on-error -python main.py +./install.sh --accelerator auto --repair ``` -On Windows, create and activate the environment with PowerShell, then run the same install and startup commands: - ```powershell -py -3.12 -m venv .venv -.\.venv\Scripts\Activate.ps1 -python -m pip install -U pip wheel setuptools -python -m pip install -U -r requirements.txt -python -m modiff.preflight --json --check-port 8088 --fail-on-error -python main.py +.\install.ps1 -Accelerator auto -Repair ``` -Apple Silicon should use `requirements_macos.txt` instead of the CUDA-oriented base requirements: +Backend contributors who intentionally do not want to build the sibling client +can install a CPU development environment with: ```bash -python3.12 -m venv .venv -source .venv/bin/activate -python -m pip install -U pip wheel setuptools -python -m pip install -U -r requirements_macos.txt -python -m modiff.preflight --json --check-port 8088 --fail-on-error -python main.py +./install.sh --accelerator cpu --backend-only +``` + +```powershell +.\install.ps1 -Accelerator cpu -BackendOnly ``` -The optional pip groups are `requirements_extras.txt` and `requirements_quant.txt`. Native extensions such as FlashAttention, SageAttention, Nunchaku, and some quantization backends may require their own supported PyTorch/CUDA combination and compiler toolchain. +See [the accelerator installation guide](docs/accelerator-installation.md) for +dry-run, non-interactive, resume, repair, and experimental-host behavior. The +selected file under `requirements/profiles/`, `pyproject.toml`, and the +accelerator compatibility manifest jointly define the reviewed runtime +contract. ## Configuration and local data @@ -172,10 +269,12 @@ Generated outputs, prompts, workflow packages, and shares are also local plainte Run preflight before investigating model-specific failures: ```bash -uv run python -m modiff.preflight --json --check-port 8088 --fail-on-error +./.venv/bin/python -m modiff.preflight --json --check-port 8088 --fail-on-error ``` -The report checks Python, required imports, CUDA/MPS/CPU discovery, cache and data paths, and port state without importing the full node registry. +On Windows, use `.\.venv\Scripts\python.exe` in place of `./.venv/bin/python`. + +The report checks Python, required imports, CUDA/ROCm/MPS/CPU discovery, cache and data paths, and port state without importing the full node registry. Useful local endpoints include: @@ -191,21 +290,42 @@ See [docs/api-reference.md](docs/api-reference.md) for route groups and trust im The Modular Diffusers integration is documented in [modules/ModularDiffusers/README.md](modules/ModularDiffusers/README.md). MoDiff owns the pipeline configuration schema used by its dynamic node contracts while relying on upstream Diffusers for model and pipeline execution. -## Updating +## Updating and recovery + +Stop the foreground application with `Ctrl+C` and update both sibling +repositories so the frontend and backend contracts remain aligned. -For a normal backend update: +Linux or macOS, from their parent directory: ```bash -git pull --ff-only -uv lock --check -uv sync --frozen -uv run python -m modiff.preflight --json --check-port 8088 --fail-on-error -uv run python -m unittest discover -s tests -v +git -C MoDiff-client pull --ff-only +git -C MoDiff pull --ff-only +cd MoDiff +./install.sh --accelerator auto --repair +./run.sh +``` + +Windows PowerShell, from their parent directory: + +```powershell +git -C .\MoDiff-client pull --ff-only +git -C .\MoDiff pull --ff-only +Set-Location .\MoDiff +.\install.ps1 -Accelerator auto -Repair +.\run.ps1 ``` -If `pyproject.toml` is intentionally changed, regenerate and commit `uv.lock` with `uv lock`; do not bypass a stale-lock failure. +Use the accelerator you deliberately selected instead of `auto` when retaining +an explicit profile. The installer detects changes across profile +requirements, `pyproject.toml`, and the accelerator manifest, then rebuilds the +sibling client bundle during a normal paired installation. There is no +repository `uv.lock` to regenerate. A `--backend-only`/`-BackendOnly` repair +does not update the installed client. -The bundled client does not update as part of `uv sync`. Client releases must be built in the separate MoDiff-client checkout and mirrored into this repository's `web/` directory. +For failures, begin with [troubleshooting](docs/troubleshooting.md) rather than +deleting caches or data. The safe-cleanup section identifies disposable files +and explains why `data/`, `config.ini`, and shared Hugging Face caches require +individual review and backups. ## Updating the bundled client @@ -214,8 +334,12 @@ Do not edit `web/assets/index.js` or `web/assets/index.css` by hand. They are ge 1. Finish and validate changes in the MoDiff-client repository. 2. Run `npm ci` and `npm run check` there. The check command produces `dist/` after frontend tests and validation. 3. Mirror the **contents** of `dist/` into this backend's `web/` directory, deleting stale generated bundle files while preserving backend-owned `web/user/` custom fields. Do not create `web/dist/`. -4. Confirm that `web/index.html`, `web/assets/`, `web/favicon.ico`, and `web/template-gallery/` came from the same build, and that any existing `web/user/` directory survived the sync. -5. Start the backend and verify `/`, `/assets/index.js`, and `/template-gallery/manifest.json` before committing both repositories. +4. Confirm that `web/index.html`, `web/assets/`, and `web/favicon.ico` came from the same build, and that any existing `web/user/` directory survived the sync. A source-release build intentionally has no `web/template-gallery/` directory; the normal installer adds the verified local payload. +5. Start the backend and verify `/`, `/assets/index.js`, and `/template-gallery/manifest.json`. Lightweight source-release builds without an installer pass retain immutable Hub URL resolution. + +The normal installer materializes `web/template-gallery/` for that +installation. Treat it as downloaded runtime data: do not add it to Git or a +normal remote-asset release package. For adjacent checkouts, an exact mirror can be performed with a platform tool after confirming both paths: @@ -237,10 +361,15 @@ Backend-only contributors who do not have the matching client checkout should le - [docs/README.md](docs/README.md) is the backend documentation index and recommended reading order. - [docs/troubleshooting.md](docs/troubleshooting.md) covers startup, port, accelerator, model-download, FFmpeg, and stale-client problems. -- [CONTRIBUTING.md](CONTRIBUTING.md) describes backend validation, module conventions, lockfile updates, and client-bundle changes. +- [CONTRIBUTING.md](CONTRIBUTING.md) describes backend validation, module conventions, managed-runtime updates, and client-bundle changes. - [SECURITY.md](SECURITY.md) documents the supported local-only trust boundary and vulnerability reporting. - [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) defines the expected behavior in project spaces. ## License -MoDiff is distributed under the [Apache License 2.0](LICENSE). Individual models, datasets, custom modules, and optional dependencies may have separate licenses and usage restrictions; review them before downloading, redistributing, or using their outputs commercially. +MoDiff source code is distributed under the [Apache License 2.0](LICENSE) and retains the copyright notice from the +upstream Mellon project from which it was derived. Model weights, adapters, and Gallery media are not relicensed by +MoDiff and remain subject to their respective upstream terms. Datasets, custom modules, and optional dependencies may +also have separate licenses and usage restrictions; review them before downloading, redistributing, or using their +outputs commercially. Attribution for the adapted Diffusers helper and licenses for font software +redistributed with the bundled client are in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). diff --git a/SECURITY.md b/SECURITY.md index 423565c..2850e8f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,7 +13,7 @@ Do not expose MoDiff directly to an untrusted LAN, the public internet, a shared MoDiff is designed to execute Python and model code: - Custom-module installation can clone a Git repository or copy a local directory into `custom/`, then import it into the live registry. -- Enabling `trust_remote_code`, and custom Modular Diffusers block paths that require remote code, can execute Python supplied by a model repository. +- Enabling `trust_remote_code`, and custom Modular Diffusers block paths that require remote code, can execute Python supplied by a model repository. MoDiff requires an exact 40-character commit revision for these remote custom paths; do not weaken that check to accept moving branches or tags. - Model deserialization and optional native/CUDA packages have their own supply-chain and memory-safety risks. - Workflows can allocate substantial CPU, RAM, accelerator memory, disk, and network bandwidth. @@ -36,7 +36,9 @@ Generated media, prompts, graph snapshots, Studio output history, reusable block ## Network behavior -Depending on the workflow, MoDiff can connect to Hugging Face, Git hosts, model-defined URLs, and other user-supplied sources. Use host firewall and egress controls when running untrusted or sensitive workloads. Offline mode reduces Hugging Face resolution but does not turn arbitrary installed code into a sandbox. +Depending on the workflow, MoDiff can connect to Hugging Face, Git hosts, model-defined URLs, and other user-supplied sources. Backend-managed web-media import rejects credentials and non-public address resolutions, validates every redirect, disables environment proxies for graph-controlled URLs, and connects to the validated numeric address to resist DNS rebinding. Installed Python/model code and specialized downloaders still have process-level network access. Use host firewall and egress controls when running untrusted or sensitive workloads. Offline mode reduces Hugging Face resolution but does not turn arbitrary installed code into a sandbox. + +The main HTTP server defaults to a 1 GiB request cap, workflow-share preview copies are capped at 256 MiB, and every HTTP request requires a loopback request Host (`localhost` or a literal loopback address) and loopback peer. Browser requests that supply an Origin must use a loopback HTTP(S) Origin; originless native clients and ordinary browser navigations are accepted only from loopback. The supervisor control plane is loopback-only. WebSocket upgrades also require a loopback destination and peer. Browser WebSockets must provide a loopback `http` or `https` Origin; native clients without an `Origin` header are accepted only over a loopback connection. Initial WebSocket history contains compact task receipts, while completed workflow snapshots remain available lazily through `/runs/{task_id}`. These controls resist ordinary cross-site requests, read-side data exposure, and DNS-rebinding hostnames, but they do not add authentication or make a non-loopback deployment supported. ## Reporting a vulnerability diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..b30fd50 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,133 @@ +# Third-party notices + +## Mellon and Mellon Client + +Substantial portions of the backend source are adapted from +[`cubiq/Mellon`](https://github.com/cubiq/Mellon) at comparison baseline +[`5fd242921d13bff9fb03f4de405fdd39c2335e1f`](https://github.com/cubiq/Mellon/tree/5fd242921d13bff9fb03f4de405fdd39c2335e1f), +Copyright 2024 Matteo Spinelli. Historical generated client files under `web/` +are adapted from +[`cubiq/Mellon-client`](https://github.com/cubiq/Mellon-client) at comparison +baseline +[`af0c5801f843453a1700733596e99fe6589b2e86`](https://github.com/cubiq/Mellon-client/tree/af0c5801f843453a1700733596e99fe6589b2e86), +Copyright 2024 Matteo Spinelli. Both upstream repositories are licensed under +the Apache License 2.0; the project `LICENSE` contains that license text. + +These revisions are evidence-based pre-import comparison baselines selected +from repository history and file similarity. They are not asserted to be +proven Git ancestors or necessarily the exact snapshots used for MoDiff's +original import. MoDiff has modified the adapted files. See the +[source-provenance map](docs/source-provenance.md) for the affected path +families and the treatment of formats that cannot carry comments. + +## Hugging Face Diffusers + +Portions of `modules/ModularDiffusers/pipeline_schema.py` are derived from +Hugging Face Diffusers' `src/diffusers/modular_pipelines/mellon_node_utils.py` +at commit `13a7bee4878d62fccc8d25f97e480e68de96fa03`. Diffusers is licensed under +the Apache License 2.0; the project `LICENSE` contains that license text. + +Source: + +## Font software + +MoDiff redistributes the following font software in source builds and/or the +bundled web client. These fonts are not licensed under MoDiff's Apache-2.0 +license. + +- **IBM Plex Mono**, supplied by `@fontsource/ibm-plex-mono` 5.2.7. + The package notice says: Copyright 2017 IBM Corp. All rights reserved. + The authoritative IBM Plex notice is: Copyright © 2017 IBM Corp. with + Reserved Font Name "Plex". +- **Source Sans Pro**, supplied by `@fontsource/source-sans-pro` 5.2.5. + The package notice says: Google Inc. The authoritative Source Sans notice + is: Copyright 2010-2024 Adobe (), with Reserved Font + Name "Source". All Rights Reserved. Source is a trademark of Adobe in the + United States and/or other countries. + +Both published packages declare the SIL Open Font License, Version 1.1. + +## SIL Open Font License, Version 1.1 + +Version 1.1 - 26 February 2007 + +### Preamble + +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The fonts, +including any derivative works, can be bundled, embedded, redistributed +and/or sold with any software provided that any reserved names are not used +by derivative works. The fonts and derivatives, however, cannot be released +under any other type of license. The requirement for fonts to remain under +this license does not apply to any document created using the fonts or their +derivatives. + +### Definitions + +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may include +source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or +substituting -- in part or in whole -- any of the components of the Original +Version, by changing formats or by porting the Font Software to a new +environment. + +"Author" refers to any designer, engineer, programmer, technical writer or +other person who contributed to the Font Software. + +### Permission and conditions + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the Font Software, to use, study, copy, merge, embed, modify, redistribute, +and sell modified and unmodified copies of the Font Software, subject to the +following conditions: + +1. Neither the Font Software nor any of its individual components, in + Original or Modified Versions, may be sold by itself. +2. Original or Modified Versions of the Font Software may be bundled, + redistributed and/or sold with any software, provided that each copy + contains the above copyright notice and this license. These can be + included either as stand-alone text files, human-readable headers or in + the appropriate machine-readable metadata fields within text or binary + files as long as those fields can be easily viewed by the user. +3. No Modified Version of the Font Software may use the Reserved Font Name(s) + unless explicit written permission is granted by the corresponding + Copyright Holder. This restriction only applies to the primary font name + as presented to the users. +4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font + Software shall not be used to promote, endorse or advertise any Modified + Version, except to acknowledge the contribution(s) of the Copyright + Holder(s) and the Author(s) or with their explicit written permission. +5. The Font Software, modified or unmodified, in part or in whole, must be + distributed entirely under this license, and must not be distributed + under any other license. The requirement for fonts to remain under this + license does not apply to any document created using the Font Software. + +### Termination + +This license becomes null and void if any of the above conditions are not +met. + +### Disclaimer + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, +INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE +THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/config.example.ini b/config.example.ini index a36a237..f497785 100644 --- a/config.example.ini +++ b/config.example.ini @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. [server] # MoDiff has no authentication or multi-user security boundary. Keep the # server on loopback unless you have added an authenticated, access-controlled @@ -14,9 +15,8 @@ port = 8088 # ssl_cert = certs/server.crt # ssl_key = certs/server.key -# Maximum request body size in bytes. The application fallback is currently -# 1099511627776 bytes (1 TiB); this example deliberately suggests a safer -# 1 GiB cap. Lower it further unless large local image/video uploads need it. +# Maximum request body size in bytes. The application fallback is 1 GiB. +# Lower it further unless large local image/video uploads need it. # client_max_size = 1073741824 [logging] diff --git a/data/graphs/modular_diffusers/dynamic_node.json b/data/graphs/modular_diffusers/dynamic_node.json index a3bfcfd..62c7e65 100644 --- a/data/graphs/modular_diffusers/dynamic_node.json +++ b/data/graphs/modular_diffusers/dynamic_node.json @@ -1 +1 @@ -{"nodes":[{"id":"GSg8R1oTURacVw6FG8hAg","type":"custom","position":{"x":904.1697196938652,"y":-741.8042050564726},"data":{"module":"modules.ModularDiffusers","action":"DynamicBlockNode","type":"custom","label":"Dynamic Block Node","category":"default","description":"","resizable":true,"skipParamsCheck":true,"style":{"minWidth":300},"params":{"repo_id":{"label":"Custom Block","display":"autocomplete","type":"string","default":"","value":"diffusers/FLUX.2-klein-4B-modular","options":{"":"","diffusers/FLUX.2-klein-4B-modular":"FLUX.2-klein-4B"},"fieldOptions":{"noValidation":true}},"load_block_button":{"label":"Load Custom Block","display":"ui_button","value":false,"onChange":"update_node","disabled":false},"device":{"label":"Device","type":"string","value":"cuda:0","options":{"cuda:0":{"arch":"cuda","name":"NVIDIA GeForce RTX 5090 31.34GB (0)","label":["cuda:0"],"total_memory":33647820800,"index":0},"cpu:0":{"arch":"cpu","name":"CPU (0)","label":["cpu:0"],"total_memory":0,"index":0}}},"auto_offload":{"label":"Enable Auto Offload","type":"boolean","value":false},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false},"prompt":{"label":"Prompt","type":"string","display":"textarea","default":"","value":"put the subjects of each images in a epic fight at a tropical beach"},"image":{"label":"Image","type":"image","display":"input","isConnected":true},"num_inference_steps":{"label":"Num Inference Steps","type":"int","default":4,"value":4},"images":{"label":"Images","type":"image","display":"output","isConnected":true}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":12687806976,"min":12687806976,"max":12687806976},"executionTime":{"last":8.350792169570923,"min":8.350792169570923,"max":8.350792169570923}},"measured":{"width":425,"height":455},"selected":false,"dragging":false},{"id":"W4-hq-bIt37wvW3PkM_XZ","type":"custom","position":{"x":-56.520515531998626,"y":-726.0033788192052},"data":{"module":"modules.Image","action":"Load","type":"custom","label":"Load Image","category":"image","description":"Load an image from a file","resizable":true,"skipParamsCheck":false,"style":{},"params":{"image":{"label":"Image","display":"output","type":"image","isConnected":true},"label":{"display":"ui_label","value":"Load Image"},"file":{"label":false,"display":"filebrowser","type":"str","fieldOptions":{"fileTypes":["image"],"multiple":true},"value":["https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/main/resources/kangaroo.png","https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/main/resources/turtle.png"]},"alpha_channel":{"label":"Alpha Channel","type":"string","options":["ignore","add alpha","remove alpha"],"default":"ignore"},"width":{"display":"output","type":"int","isConnected":false},"height":{"display":"output","type":"int","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":0,"min":0,"max":0},"executionTime":{"last":0.5995538234710693,"min":0.5995538234710693,"max":0.5995538234710693}},"measured":{"width":762,"height":622},"selected":false,"dragging":false},{"id":"5ZX02g5AALT6sN-EOOpCw","type":"custom","position":{"x":1454.038472750774,"y":-898.2323848054208},"data":{"module":"modules.Image","action":"Preview","type":"custom","label":"Preview Image","category":"image","description":"Preview an image","resizable":true,"skipParamsCheck":false,"style":{},"params":{"vae":{"type":"pipeline","display":"input","label":"VAE","isConnected":false,"hidden":true},"device":{"type":"string","default":"cuda:0","options":{"cuda:0":{"arch":"cuda","name":"NVIDIA GeForce RTX 5090 31.34GB (0)","label":["cuda:0"],"total_memory":33647820800,"index":0},"cpu:0":{"arch":"cpu","name":"CPU (0)","label":["cpu:0"],"total_memory":0,"index":0}},"hidden":true},"image":{"type":["image","latent"],"display":"input","onChange":{"action":"show","data":{"true":["vae","device"],"false":[]},"condition":{"type":"latent"}},"isConnected":true},"preview":{"display":"ui_image","type":"url","dataSource":"output","value":["/cache/5ZX02g5AALT6sN-EOOpCw/output/0?format=WEBP&quality=100&t=1770199754.6593027"]},"output":{"type":"image","display":"output","label":"All images","isConnected":false},"export":{"type":"str","default":"0","description":"Export the image at the given index. Leave empty to export all images."},"filtered":{"type":"image","display":"output","label":"Selected image","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":10759950336,"min":10759950336,"max":10759950336},"executionTime":{"last":0.0004911422729492188,"min":0.0004911422729492188,"max":0.0004911422729492188}},"measured":{"width":1044,"height":985},"selected":true,"dragging":false}],"edges":[{"source":"W4-hq-bIt37wvW3PkM_XZ","sourceHandle":"image","target":"GSg8R1oTURacVw6FG8hAg","targetHandle":"image","edgeType":"default","id":"wCNLFaRh3znbDMgHlTvo0","type":"default","className":"category-image"},{"source":"GSg8R1oTURacVw6FG8hAg","sourceHandle":"images","target":"5ZX02g5AALT6sN-EOOpCw","targetHandle":"image","edgeType":"default","id":"r3hHEoxXuHJWVP4hR2cDs","type":"default","className":"category-image"}],"viewport":{"x":195.77060761461348,"y":686.4717819925573,"zoom":0.6328782969851419}} \ No newline at end of file +{"nodes":[{"id":"GSg8R1oTURacVw6FG8hAg","type":"custom","position":{"x":904.1697196938652,"y":-741.8042050564726},"data":{"module":"modules.ModularDiffusers","action":"DynamicBlockNode","type":"custom","label":"Dynamic Block Node","category":"default","description":"","resizable":true,"skipParamsCheck":true,"style":{"minWidth":300},"params":{"repo_id":{"label":"Custom Block","display":"autocomplete","type":"string","default":"","value":"diffusers/FLUX.2-klein-4B-modular","options":{"":"","diffusers/FLUX.2-klein-4B-modular":"FLUX.2-klein-4B"},"fieldOptions":{"noValidation":true}},"load_block_button":{"label":"Load Custom Block","display":"ui_button","value":false,"onChange":"update_node","disabled":false},"device":{"label":"Device","type":"string","value":"cuda:0"},"auto_offload":{"label":"Enable Auto Offload","type":"boolean","value":false},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false},"prompt":{"label":"Prompt","type":"string","display":"textarea","default":"","value":"put the subjects of each images in a epic fight at a tropical beach"},"image":{"label":"Image","type":"image","display":"input","isConnected":true},"num_inference_steps":{"label":"Num Inference Steps","type":"int","default":4,"value":4},"images":{"label":"Images","type":"image","display":"output","isConnected":true},"revision":{"label":"Revision","type":"string","default":"","value":"62ac375aa5308588f111fcd12115f5c54a8b1f4f"},"trust_remote_code":{"label":"Trust Remote Code","type":"boolean","default":false,"value":false}}}},{"id":"W4-hq-bIt37wvW3PkM_XZ","type":"custom","position":{"x":-56.520515531998626,"y":-726.0033788192052},"data":{"module":"modules.Image","action":"Load","type":"custom","label":"Load Image","category":"image","description":"Load an image from a file","resizable":true,"skipParamsCheck":false,"style":{},"params":{"image":{"label":"Image","display":"output","type":"image","isConnected":true},"label":{"display":"ui_label","value":"Load Image"},"file":{"label":false,"display":"filebrowser","type":"str","fieldOptions":{"fileTypes":["image"],"multiple":true},"value":["https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/42ef98a1ee9295d29009dcc3716f0d5a56f47d48/resources/kangaroo.png","https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/42ef98a1ee9295d29009dcc3716f0d5a56f47d48/resources/turtle.png"]},"alpha_channel":{"label":"Alpha Channel","type":"string","options":["ignore","add alpha","remove alpha"],"default":"ignore"},"width":{"display":"output","type":"int","isConnected":false},"height":{"display":"output","type":"int","isConnected":false}}}},{"id":"5ZX02g5AALT6sN-EOOpCw","type":"custom","position":{"x":1454.038472750774,"y":-898.2323848054208},"data":{"module":"modules.Image","action":"Preview","type":"custom","label":"Preview Image","category":"image","description":"Preview an image","resizable":true,"skipParamsCheck":false,"style":{},"params":{"vae":{"type":"pipeline","display":"input","label":"VAE","isConnected":false,"hidden":true},"device":{"type":"string","default":"cuda:0","hidden":true},"image":{"type":["image","latent"],"display":"input","onChange":{"action":"show","data":{"true":["vae","device"],"false":[]},"condition":{"type":"latent"}},"isConnected":true},"preview":{"display":"ui_image","type":"url","dataSource":"output","value":[]},"output":{"type":"image","display":"output","label":"All images","isConnected":false},"export":{"type":"str","default":"0","description":"Export the image at the given index. Leave empty to export all images."},"filtered":{"type":"image","display":"output","label":"Selected image","isConnected":false}}}}],"edges":[{"source":"W4-hq-bIt37wvW3PkM_XZ","sourceHandle":"image","target":"GSg8R1oTURacVw6FG8hAg","targetHandle":"image","edgeType":"default","id":"wCNLFaRh3znbDMgHlTvo0","type":"default","className":"category-image"},{"source":"GSg8R1oTURacVw6FG8hAg","sourceHandle":"images","target":"5ZX02g5AALT6sN-EOOpCw","targetHandle":"image","edgeType":"default","id":"r3hHEoxXuHJWVP4hR2cDs","type":"default","className":"category-image"}],"viewport":{"x":195.77060761461348,"y":686.4717819925573,"zoom":0.6328782969851419}} diff --git a/data/graphs/modular_diffusers/image_to_image.json b/data/graphs/modular_diffusers/image_to_image.json index 7bf38c0..8072af5 100644 --- a/data/graphs/modular_diffusers/image_to_image.json +++ b/data/graphs/modular_diffusers/image_to_image.json @@ -1 +1 @@ -{"nodes":[{"id":"xoO5v5A46aiDzRV8CdwQf","type":"custom","position":{"x":727.9860859499502,"y":-52.21875009781645},"data":{"module":"modules.ModularDiffusers","action":"EncodePrompt","type":"custom","label":"Encode Prompt","category":"embedding","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"text_encoders":{"label":"Text Encoders *","type":"diffusers_auto_models","display":"input","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"prompt":{"label":"Prompt *","type":"string","display":"textarea","default":"","value":"a rabbit"},"embeddings":{"label":"Text Embeddings","type":"embeddings","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":8316807680,"min":8316807680,"max":8316807680},"executionTime":{"last":3.8661952018737793,"min":3.8661952018737793,"max":3.8661952018737793}},"measured":{"width":236,"height":266},"selected":false,"dragging":false},{"id":"fNoX-2jsWixE29bRBWCPz","type":"custom","position":{"x":1566.1547042087227,"y":-70.96344239594912},"data":{"module":"modules.ModularDiffusers","action":"DecodeLatents","type":"custom","label":"Decode Latents","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"latents":{"label":"Latents *","type":"latents","display":"input","isConnected":true},"images":{"label":"Images","type":"image","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":22607324160,"min":22607324160,"max":22607324160},"executionTime":{"last":0.546771764755249,"min":0.546771764755249,"max":0.546771764755249}},"measured":{"width":236,"height":195},"selected":false,"dragging":false},{"id":"awx_oWVfLRXTd58gvvQs-","type":"custom","position":{"x":726.071757968142,"y":397.76239548733645},"data":{"module":"modules.ModularDiffusers","action":"ImageEncode","type":"custom","label":"Encode Image","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"image":{"label":"Image *","type":"image","display":"input","isConnected":true},"image_latents":{"label":"Image Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":9506501632,"min":9506501632,"max":9506501632},"executionTime":{"last":0.705026388168335,"min":0.705026388168335,"max":0.705026388168335}},"measured":{"width":228,"height":195},"selected":false,"dragging":false},{"id":"hzu0l03gW40i0hdxHA2uR","type":"custom","position":{"x":233.3150439945601,"y":-113.75885553427531},"data":{"module":"modules.ModularDiffusers","action":"ModelsLoader","type":"custom","label":"Load Models","category":"loader","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"model_type":{"label":"Model Type","type":"string","options":{"":"","DummyCustomPipeline":"Custom","StableDiffusionXLModularPipeline":"Stable Diffusion XL","QwenImageModularPipeline":"Qwen Image","QwenImageEditModularPipeline":"Qwen Image Edit","QwenImageEditPlusModularPipeline":"Qwen Image Edit Plus","QwenImageLayeredModularPipeline":"Qwen Image Layered","FluxModularPipeline":"Flux","FluxKontextModularPipeline":"Flux Kontext","Flux2KleinModularPipeline":"Flux 2 Klein Distilled","ZImageModularPipeline":"Z-Image","WanModularPipeline":"WAN"},"onChange":["set_filters",{"action":"signal","target":"unet_out"},{"action":"signal","target":"text_encoders"},{"action":"signal","target":"vae_out"},{"action":"signal","target":"image_encoder"}],"disabled":false,"value":"ZImageModularPipeline"},"repo_id":{"label":"Repository ID","display":"modelselect","type":"string","value":{"source":"hub","value":"Tongyi-MAI/Z-Image-Turbo"},"fieldOptions":{"noValidation":true,"sources":["hub","local"],"filter":{"hub":{"className":["ZImageModularPipeline"]}}},"disabled":false,"default":{"source":"hub","value":"Tongyi-MAI/Z-Image-Turbo"}},"dtype":{"label":"dtype","options":["float32","float16","bfloat16"],"value":"bfloat16","disabled":false},"device":{"label":"Device","type":"string","value":"cuda:0","options":{"cuda:0":{"arch":"cuda","name":"NVIDIA GeForce RTX 5090 31.34GB (0)","label":["cuda:0"],"total_memory":33647820800,"index":0},"cpu:0":{"arch":"cpu","name":"CPU (0)","label":["cpu:0"],"total_memory":0,"index":0}}},"trust_remote_code":{"label":"Trust Remote Code","type":"boolean","value":false},"auto_offload":{"label":"Enable Auto Offload","type":"boolean","value":true},"unet":{"label":"Denoise Model","display":"input","type":"diffusers_auto_model","isConnected":false},"vae":{"label":"VAE","display":"input","type":"diffusers_auto_model","isConnected":false},"lora_list":{"label":"Lora","display":"input","type":"custom_lora","isConnected":false},"text_encoders":{"label":"Text Encoders","display":"output","type":"diffusers_auto_models","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"unet_out":{"label":"Denoise Model","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"vae_out":{"label":"VAE","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"scheduler":{"label":"Scheduler","display":"output","type":"diffusers_auto_model","isConnected":true},"image_encoder":{"label":"Image Encoder","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":false},"quant_config":{"label":"Quant Config","display":"input","type":"quant_config","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":0,"min":0,"max":0},"executionTime":{"last":8.357560873031616,"min":8.357560873031616,"max":8.357560873031616}},"measured":{"width":319,"height":563},"selected":true,"dragging":false},{"id":"_djQOIxcQQg8_HzMF2jJE","type":"custom","position":{"x":243.28339536373954,"y":588.2978908950809},"data":{"module":"modules.Image","action":"Load","type":"custom","label":"Load Image","category":"image","description":"Load an image from a file","resizable":true,"skipParamsCheck":false,"style":{},"params":{"image":{"label":"Image","display":"output","type":"image","isConnected":true},"label":{"display":"ui_label","value":"Load Image"},"file":{"label":false,"display":"filebrowser","type":"str","fieldOptions":{"fileTypes":["image"],"multiple":true},"value":["https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/main/resources/turtle.png"]},"alpha_channel":{"label":"Alpha Channel","type":"string","options":["ignore","add alpha","remove alpha"],"default":"ignore"},"width":{"display":"output","type":"int","isConnected":false},"height":{"display":"output","type":"int","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":8110280704,"min":8110280704,"max":8110280704},"executionTime":{"last":0.3018989562988281,"min":0.3018989562988281,"max":0.3018989562988281}},"measured":{"width":394,"height":590},"selected":false,"dragging":false},{"id":"xbAbVqpJ1lkfp0_Z6L4CV","type":"custom","position":{"x":1104.833763699968,"y":-83.85380142673681},"data":{"module":"modules.ModularDiffusers","action":"Denoise","type":"custom","label":"Denoise","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"unet":{"label":"Denoise Model *","display":"input","type":"diffusers_auto_model","onSignal":["update_node",{"action":"signal","target":"guider"},{"action":"signal","target":"controlnet_bundle"}],"disabled":false,"isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"}},"embeddings":{"label":"Text Embeddings *","type":"embeddings","display":"input","isConnected":true},"width":{"label":"Width","type":"int","default":1024,"min":64,"step":8,"value":1024,"hidden":true},"height":{"label":"Height","type":"int","default":1024,"min":64,"step":8,"value":1024,"hidden":true},"seed":{"label":"Seed","type":"int","display":"random","default":0,"min":0,"max":4294967295,"value":0},"num_inference_steps":{"label":"Steps","type":"int","display":"slider","default":9,"min":1,"max":100,"value":9},"guidance_scale":{"label":"Guidance Scale","type":"float","display":"slider","default":1,"min":1,"max":30,"step":0.1,"value":1,"hidden":false},"image_latents":{"label":"Image Latents","type":"latents","display":"input","onChange":{"false":["height","width"],"true":["strength"]},"isConnected":true},"strength":{"label":"Strength","type":"float","default":0.5,"min":0,"max":1,"step":0.01,"value":0.5,"hidden":false},"guider":{"label":"Guider","type":"custom_guider","display":"input","onChange":{"false":["guidance_scale"],"true":[]},"isConnected":false},"scheduler":{"label":"Scheduler *","type":"diffusers_auto_model","display":"input","isConnected":true},"latents":{"label":"Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":21071249920,"min":21071249920,"max":21071249920},"executionTime":{"last":2.6162049770355225,"min":2.6162049770355225,"max":2.6162049770355225}},"measured":{"width":386,"height":420},"selected":false,"dragging":false},{"id":"ktsJ1brnEY_PTMFv3yqU7","type":"custom","position":{"x":1663.0614403740196,"y":226.58914121342468},"data":{"module":"modules.Image","action":"Preview","type":"custom","label":"Preview Image","category":"image","description":"Preview an image","resizable":true,"skipParamsCheck":false,"style":{},"params":{"vae":{"type":"pipeline","display":"input","label":"VAE","isConnected":false,"hidden":true},"device":{"type":"string","default":"cuda:0","options":{"cuda:0":{"arch":"cuda","name":"NVIDIA GeForce RTX 5090 31.34GB (0)","label":["cuda:0"],"total_memory":33647820800,"index":0},"cpu:0":{"arch":"cpu","name":"CPU (0)","label":["cpu:0"],"total_memory":0,"index":0}},"hidden":true},"image":{"type":["image","latent"],"display":"input","onChange":{"action":"show","data":{"true":["vae","device"],"false":[]},"condition":{"type":"latent"}},"isConnected":true},"preview":{"display":"ui_image","type":"url","dataSource":"output","value":["/cache/ktsJ1brnEY_PTMFv3yqU7/output/0?format=WEBP&quality=100&t=1770218044.8301468"]},"output":{"type":"image","display":"output","label":"All images","isConnected":false},"export":{"type":"str","default":"0","description":"Export the image at the given index. Leave empty to export all images."},"filtered":{"type":"image","display":"output","label":"Selected image","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":20692231168,"min":20692231168,"max":20692231168},"executionTime":{"last":0.00005030632019042969,"min":0.00005030632019042969,"max":0.00005030632019042969}},"measured":{"width":1044,"height":985},"selected":false,"dragging":false}],"edges":[{"source":"hzu0l03gW40i0hdxHA2uR","target":"xoO5v5A46aiDzRV8CdwQf","sourceHandle":"text_encoders","targetHandle":"text_encoders","edgeType":"default","id":"opkJ4Qfle6cliTjjM1vVC","type":"default","className":"category-diffusers_auto_models"},{"source":"hzu0l03gW40i0hdxHA2uR","target":"awx_oWVfLRXTd58gvvQs-","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"VjEk3gqjxQglsAjLx42xZ","type":"default","className":"category-diffusers_auto_model"},{"source":"_djQOIxcQQg8_HzMF2jJE","sourceHandle":"image","target":"awx_oWVfLRXTd58gvvQs-","targetHandle":"image","edgeType":"default","id":"n99Ul20K5liiziJnQ6FiR","type":"default","className":"category-image"},{"source":"hzu0l03gW40i0hdxHA2uR","target":"xbAbVqpJ1lkfp0_Z6L4CV","sourceHandle":"unet_out","targetHandle":"unet","edgeType":"default","id":"T1NIO2mDP2xc8km3Jpxrt","type":"default","className":"category-diffusers_auto_model"},{"source":"awx_oWVfLRXTd58gvvQs-","sourceHandle":"image_latents","target":"xbAbVqpJ1lkfp0_Z6L4CV","targetHandle":"image_latents","edgeType":"default","id":"HFM7OCm3Pp2kHBKZ6psH5","type":"default","className":"category-latents"},{"source":"xoO5v5A46aiDzRV8CdwQf","sourceHandle":"embeddings","target":"xbAbVqpJ1lkfp0_Z6L4CV","targetHandle":"embeddings","edgeType":"default","id":"DcMdTGnP4K4YDU7QXCbt-","type":"default","className":"category-embeddings"},{"source":"hzu0l03gW40i0hdxHA2uR","sourceHandle":"scheduler","target":"xbAbVqpJ1lkfp0_Z6L4CV","targetHandle":"scheduler","edgeType":"default","id":"gb51IZAdmE1W22hXEr5xl","type":"default","className":"category-diffusers_auto_model"},{"source":"hzu0l03gW40i0hdxHA2uR","target":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"ubuEO25jIvbOsL74vvor5","type":"default","className":"category-diffusers_auto_model"},{"source":"xbAbVqpJ1lkfp0_Z6L4CV","sourceHandle":"latents","target":"fNoX-2jsWixE29bRBWCPz","targetHandle":"latents","edgeType":"default","id":"LgtCOHWN-qlrcWomFbkHC","type":"default","className":"category-latents"},{"source":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"images","target":"ktsJ1brnEY_PTMFv3yqU7","targetHandle":"image","edgeType":"default","id":"eTcb2n5rx0xJ8EgPvTXJP","type":"default","className":"category-image"}],"viewport":{"x":-3.8390589853727306,"y":151.88402086246606,"zoom":0.7022224378689988}} \ No newline at end of file +{"nodes":[{"id":"xoO5v5A46aiDzRV8CdwQf","type":"custom","position":{"x":727.9860859499502,"y":-52.21875009781645},"data":{"module":"modules.ModularDiffusers","action":"EncodePrompt","type":"custom","label":"Encode Prompt","category":"embedding","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"text_encoders":{"label":"Text Encoders *","type":"diffusers_auto_models","display":"input","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"prompt":{"label":"Prompt *","type":"string","display":"textarea","default":"","value":"a rabbit"},"embeddings":{"label":"Text Embeddings","type":"embeddings","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"fNoX-2jsWixE29bRBWCPz","type":"custom","position":{"x":1566.1547042087227,"y":-70.96344239594912},"data":{"module":"modules.ModularDiffusers","action":"DecodeLatents","type":"custom","label":"Decode Latents","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"latents":{"label":"Latents *","type":"latents","display":"input","isConnected":true},"images":{"label":"Images","type":"image","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"awx_oWVfLRXTd58gvvQs-","type":"custom","position":{"x":726.071757968142,"y":397.76239548733645},"data":{"module":"modules.ModularDiffusers","action":"ImageEncode","type":"custom","label":"Encode Image","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"image":{"label":"Image *","type":"image","display":"input","isConnected":true},"image_latents":{"label":"Image Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"hzu0l03gW40i0hdxHA2uR","type":"custom","position":{"x":233.3150439945601,"y":-113.75885553427531},"data":{"module":"modules.ModularDiffusers","action":"ModelsLoader","type":"custom","label":"Load Models","category":"loader","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"model_type":{"label":"Model Type","type":"string","options":{"":"","DummyCustomPipeline":"Custom","StableDiffusionXLModularPipeline":"Stable Diffusion XL","QwenImageModularPipeline":"Qwen Image","QwenImageEditModularPipeline":"Qwen Image Edit","QwenImageEditPlusModularPipeline":"Qwen Image Edit Plus","QwenImageLayeredModularPipeline":"Qwen Image Layered","FluxModularPipeline":"Flux","FluxKontextModularPipeline":"Flux Kontext","Flux2KleinModularPipeline":"Flux 2 Klein Distilled","ZImageModularPipeline":"Z-Image","WanModularPipeline":"WAN"},"onChange":["set_filters",{"action":"signal","target":"unet_out"},{"action":"signal","target":"text_encoders"},{"action":"signal","target":"vae_out"},{"action":"signal","target":"image_encoder"}],"disabled":false,"value":"ZImageModularPipeline"},"repo_id":{"label":"Repository ID","display":"modelselect","type":"string","value":{"source":"hub","value":"Tongyi-MAI/Z-Image-Turbo"},"fieldOptions":{"noValidation":true,"sources":["hub","local"],"filter":{"hub":{"className":["ZImageModularPipeline"]}}},"disabled":false,"default":{"source":"hub","value":"Tongyi-MAI/Z-Image-Turbo"}},"dtype":{"label":"dtype","options":["float32","float16","bfloat16"],"value":"bfloat16","disabled":false},"device":{"label":"Device","type":"string","value":"cuda:0"},"trust_remote_code":{"label":"Trust Remote Code","type":"boolean","value":false},"auto_offload":{"label":"Enable Auto Offload","type":"boolean","value":true},"unet":{"label":"Denoise Model","display":"input","type":"diffusers_auto_model","isConnected":false},"vae":{"label":"VAE","display":"input","type":"diffusers_auto_model","isConnected":false},"lora_list":{"label":"Lora","display":"input","type":"custom_lora","isConnected":false},"text_encoders":{"label":"Text Encoders","display":"output","type":"diffusers_auto_models","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"unet_out":{"label":"Denoise Model","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"vae_out":{"label":"VAE","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"scheduler":{"label":"Scheduler","display":"output","type":"diffusers_auto_model","isConnected":true},"image_encoder":{"label":"Image Encoder","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":false},"quant_config":{"label":"Quant Config","display":"input","type":"quant_config","isConnected":false},"revision":{"label":"Revision","type":"string","default":"","value":"f332072aa78be7aecdf3ee76d5c247082da564a6"}}}},{"id":"_djQOIxcQQg8_HzMF2jJE","type":"custom","position":{"x":243.28339536373954,"y":588.2978908950809},"data":{"module":"modules.Image","action":"Load","type":"custom","label":"Load Image","category":"image","description":"Load an image from a file","resizable":true,"skipParamsCheck":false,"style":{},"params":{"image":{"label":"Image","display":"output","type":"image","isConnected":true},"label":{"display":"ui_label","value":"Load Image"},"file":{"label":false,"display":"filebrowser","type":"str","fieldOptions":{"fileTypes":["image"],"multiple":true},"value":["https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/42ef98a1ee9295d29009dcc3716f0d5a56f47d48/resources/turtle.png"]},"alpha_channel":{"label":"Alpha Channel","type":"string","options":["ignore","add alpha","remove alpha"],"default":"ignore"},"width":{"display":"output","type":"int","isConnected":false},"height":{"display":"output","type":"int","isConnected":false}}}},{"id":"xbAbVqpJ1lkfp0_Z6L4CV","type":"custom","position":{"x":1104.833763699968,"y":-83.85380142673681},"data":{"module":"modules.ModularDiffusers","action":"Denoise","type":"custom","label":"Denoise","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"unet":{"label":"Denoise Model *","display":"input","type":"diffusers_auto_model","onSignal":["update_node",{"action":"signal","target":"guider"},{"action":"signal","target":"controlnet_bundle"}],"disabled":false,"isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"}},"embeddings":{"label":"Text Embeddings *","type":"embeddings","display":"input","isConnected":true},"width":{"label":"Width","type":"int","default":1024,"min":64,"step":8,"value":1024,"hidden":true},"height":{"label":"Height","type":"int","default":1024,"min":64,"step":8,"value":1024,"hidden":true},"seed":{"label":"Seed","type":"int","display":"random","default":0,"min":0,"max":4294967295,"value":0},"num_inference_steps":{"label":"Steps","type":"int","display":"slider","default":9,"min":1,"max":100,"value":9},"guidance_scale":{"label":"Guidance Scale","type":"float","display":"slider","default":1,"min":1,"max":30,"step":0.1,"value":1,"hidden":false},"image_latents":{"label":"Image Latents","type":"latents","display":"input","onChange":{"false":["height","width"],"true":["strength"]},"isConnected":true},"strength":{"label":"Strength","type":"float","default":0.5,"min":0,"max":1,"step":0.01,"value":0.5,"hidden":false},"guider":{"label":"Guider","type":"custom_guider","display":"input","onChange":{"false":["guidance_scale"],"true":[]},"isConnected":false},"scheduler":{"label":"Scheduler *","type":"diffusers_auto_model","display":"input","isConnected":true},"latents":{"label":"Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"ktsJ1brnEY_PTMFv3yqU7","type":"custom","position":{"x":1663.0614403740196,"y":226.58914121342468},"data":{"module":"modules.Image","action":"Preview","type":"custom","label":"Preview Image","category":"image","description":"Preview an image","resizable":true,"skipParamsCheck":false,"style":{},"params":{"vae":{"type":"pipeline","display":"input","label":"VAE","isConnected":false,"hidden":true},"device":{"type":"string","default":"cuda:0","hidden":true},"image":{"type":["image","latent"],"display":"input","onChange":{"action":"show","data":{"true":["vae","device"],"false":[]},"condition":{"type":"latent"}},"isConnected":true},"preview":{"display":"ui_image","type":"url","dataSource":"output","value":[]},"output":{"type":"image","display":"output","label":"All images","isConnected":false},"export":{"type":"str","default":"0","description":"Export the image at the given index. Leave empty to export all images."},"filtered":{"type":"image","display":"output","label":"Selected image","isConnected":false}}}}],"edges":[{"source":"hzu0l03gW40i0hdxHA2uR","target":"xoO5v5A46aiDzRV8CdwQf","sourceHandle":"text_encoders","targetHandle":"text_encoders","edgeType":"default","id":"opkJ4Qfle6cliTjjM1vVC","type":"default","className":"category-diffusers_auto_models"},{"source":"hzu0l03gW40i0hdxHA2uR","target":"awx_oWVfLRXTd58gvvQs-","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"VjEk3gqjxQglsAjLx42xZ","type":"default","className":"category-diffusers_auto_model"},{"source":"_djQOIxcQQg8_HzMF2jJE","sourceHandle":"image","target":"awx_oWVfLRXTd58gvvQs-","targetHandle":"image","edgeType":"default","id":"n99Ul20K5liiziJnQ6FiR","type":"default","className":"category-image"},{"source":"hzu0l03gW40i0hdxHA2uR","target":"xbAbVqpJ1lkfp0_Z6L4CV","sourceHandle":"unet_out","targetHandle":"unet","edgeType":"default","id":"T1NIO2mDP2xc8km3Jpxrt","type":"default","className":"category-diffusers_auto_model"},{"source":"awx_oWVfLRXTd58gvvQs-","sourceHandle":"image_latents","target":"xbAbVqpJ1lkfp0_Z6L4CV","targetHandle":"image_latents","edgeType":"default","id":"HFM7OCm3Pp2kHBKZ6psH5","type":"default","className":"category-latents"},{"source":"xoO5v5A46aiDzRV8CdwQf","sourceHandle":"embeddings","target":"xbAbVqpJ1lkfp0_Z6L4CV","targetHandle":"embeddings","edgeType":"default","id":"DcMdTGnP4K4YDU7QXCbt-","type":"default","className":"category-embeddings"},{"source":"hzu0l03gW40i0hdxHA2uR","sourceHandle":"scheduler","target":"xbAbVqpJ1lkfp0_Z6L4CV","targetHandle":"scheduler","edgeType":"default","id":"gb51IZAdmE1W22hXEr5xl","type":"default","className":"category-diffusers_auto_model"},{"source":"hzu0l03gW40i0hdxHA2uR","target":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"ubuEO25jIvbOsL74vvor5","type":"default","className":"category-diffusers_auto_model"},{"source":"xbAbVqpJ1lkfp0_Z6L4CV","sourceHandle":"latents","target":"fNoX-2jsWixE29bRBWCPz","targetHandle":"latents","edgeType":"default","id":"LgtCOHWN-qlrcWomFbkHC","type":"default","className":"category-latents"},{"source":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"images","target":"ktsJ1brnEY_PTMFv3yqU7","targetHandle":"image","edgeType":"default","id":"eTcb2n5rx0xJ8EgPvTXJP","type":"default","className":"category-image"}],"viewport":{"x":-3.8390589853727306,"y":151.88402086246606,"zoom":0.7022224378689988}} diff --git a/data/graphs/modular_diffusers/multiple_image_edit.json b/data/graphs/modular_diffusers/multiple_image_edit.json index ddfa8b6..9846ce8 100644 --- a/data/graphs/modular_diffusers/multiple_image_edit.json +++ b/data/graphs/modular_diffusers/multiple_image_edit.json @@ -1 +1 @@ -{"nodes":[{"id":"1nWjS_jWwtCQQwxTEQwC8","type":"custom","position":{"x":1112.0754378533063,"y":-53.64280029341349},"data":{"module":"modules.ModularDiffusers","action":"Denoise","type":"custom","label":"Denoise","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"unet":{"label":"Denoise Model *","display":"input","type":"diffusers_auto_model","onSignal":["update_node",{"action":"signal","target":"guider"},{"action":"signal","target":"controlnet_bundle"}],"isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"embeddings":{"label":"Text Embeddings *","type":"embeddings","display":"input","isConnected":true},"width":{"label":"Width","type":"int","default":1024,"min":64,"step":8,"value":1024},"height":{"label":"Height","type":"int","default":1024,"min":64,"step":8,"value":1024},"seed":{"label":"Seed","type":"int","display":"random","default":0,"min":0,"max":4294967295,"value":0},"num_inference_steps":{"label":"Steps","type":"int","display":"slider","default":4,"min":1,"max":100,"value":4},"guidance_scale":{"label":"Guidance Scale","type":"float","display":"slider","default":1,"min":1,"max":30,"step":0.1,"value":1,"hidden":false},"image_latents":{"label":"Image Latents","type":"latents","display":"input","isConnected":true},"guider":{"label":"Guider","type":"custom_guider","display":"input","onChange":{"false":["guidance_scale"],"true":[]},"isConnected":false},"scheduler":{"label":"Scheduler *","type":"diffusers_auto_model","display":"input","isConnected":true},"latents":{"label":"Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":17484062208,"min":8298126336,"max":17484062208},"executionTime":{"last":3.434556484222412,"min":0.4415295124053955,"max":3.434556484222412}},"measured":{"width":386,"height":456},"selected":false,"dragging":false},{"id":"fNoX-2jsWixE29bRBWCPz","type":"custom","position":{"x":1614.1427900291944,"y":-44.67221638113184},"data":{"module":"modules.ModularDiffusers","action":"DecodeLatents","type":"custom","label":"Decode Latents","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"latents":{"label":"Latents *","type":"latents","display":"input","isConnected":true},"images":{"label":"Images","type":"image","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":18603694592,"min":18603694592,"max":18603694592},"executionTime":{"last":0.5802440643310547,"min":0.5802440643310547,"max":0.5802440643310547}},"measured":{"width":236,"height":195},"selected":false,"dragging":false},{"id":"awx_oWVfLRXTd58gvvQs-","type":"custom","position":{"x":707.5591054253803,"y":342.2244378590507},"data":{"module":"modules.ModularDiffusers","action":"ImageEncode","type":"custom","label":"Encode Image","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"image":{"label":"Image *","type":"image","display":"input","isConnected":true},"image_latents":{"label":"Image Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":1397593600,"min":1397593600,"max":1397593600},"executionTime":{"last":0.6720435619354248,"min":0.6720435619354248,"max":0.6720435619354248}},"measured":{"width":228,"height":195},"selected":false,"dragging":false},{"id":"Vf07pkxSCp8HUxgTLJQ6Z","type":"custom","position":{"x":1969.447042842304,"y":-32.70234521736318},"data":{"module":"modules.Image","action":"Preview","type":"custom","label":"Preview Image","category":"image","description":"Preview an image","resizable":true,"skipParamsCheck":false,"style":{},"params":{"vae":{"type":"pipeline","display":"input","label":"VAE","isConnected":false,"hidden":true},"device":{"type":"string","default":"cuda:0","options":{"cuda:0":{"arch":"cuda","name":"NVIDIA GeForce RTX 5090 31.34GB (0)","label":["cuda:0"],"total_memory":33647820800,"index":0},"cpu:0":{"arch":"cpu","name":"CPU (0)","label":["cpu:0"],"total_memory":0,"index":0}},"hidden":true},"image":{"type":["image","latent"],"display":"input","onChange":{"action":"show","data":{"true":["vae","device"],"false":[]},"condition":{"type":"latent"}},"isConnected":true},"preview":{"display":"ui_image","type":"url","dataSource":"output","value":["/cache/Vf07pkxSCp8HUxgTLJQ6Z/output/0?format=WEBP&quality=100&t=1770208816.021967"]},"output":{"type":"image","display":"output","label":"All images","isConnected":false},"export":{"type":"str","default":"0","description":"Export the image at the given index. Leave empty to export all images."},"filtered":{"type":"image","display":"output","label":"Selected image","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":16049362944,"min":16049362944,"max":16049362944},"executionTime":{"last":0.00004887580871582031,"min":0.00004887580871582031,"max":0.00004887580871582031}},"measured":{"width":1044,"height":1241},"selected":true,"dragging":false},{"id":"WUjHpGhV8lGBGeJvaU010","type":"custom","position":{"x":122.23912873798855,"y":-66.76519907957203},"data":{"module":"modules.ModularDiffusers","action":"ModelsLoader","type":"custom","label":"Load Models","category":"loader","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"model_type":{"label":"Model Type","type":"string","options":{"":"","DummyCustomPipeline":"Custom","StableDiffusionXLModularPipeline":"Stable Diffusion XL","QwenImageModularPipeline":"Qwen Image","QwenImageEditModularPipeline":"Qwen Image Edit","QwenImageEditPlusModularPipeline":"Qwen Image Edit Plus","QwenImageLayeredModularPipeline":"Qwen Image Layered","FluxModularPipeline":"Flux","FluxKontextModularPipeline":"Flux Kontext","Flux2KleinModularPipeline":"Flux 2 Klein Distilled","ZImageModularPipeline":"Z-Image","WanModularPipeline":"WAN"},"onChange":["set_filters",{"action":"signal","target":"unet_out"},{"action":"signal","target":"text_encoders"},{"action":"signal","target":"vae_out"},{"action":"signal","target":"image_encoder"}],"disabled":false,"value":"Flux2KleinModularPipeline"},"repo_id":{"label":"Repository ID","display":"modelselect","type":"string","value":{"source":"hub","value":"black-forest-labs/FLUX.2-klein-4B"},"fieldOptions":{"noValidation":true,"sources":["hub","local"],"filter":{"hub":{"className":["Flux2KleinModularPipeline"]}}},"disabled":false,"default":{"source":"hub","value":"black-forest-labs/FLUX.2-klein-4B"}},"dtype":{"label":"dtype","options":["float32","float16","bfloat16"],"value":"bfloat16","disabled":false},"device":{"label":"Device","type":"string","value":"cuda:0","options":{"cuda:0":{"arch":"cuda","name":"NVIDIA GeForce RTX 5090 31.34GB (0)","label":["cuda:0"],"total_memory":33647820800,"index":0},"cpu:0":{"arch":"cpu","name":"CPU (0)","label":["cpu:0"],"total_memory":0,"index":0}}},"trust_remote_code":{"label":"Trust Remote Code","type":"boolean","value":false},"auto_offload":{"label":"Enable Auto Offload","type":"boolean","value":true},"unet":{"label":"Denoise Model","display":"input","type":"diffusers_auto_model","isConnected":false},"vae":{"label":"VAE","display":"input","type":"diffusers_auto_model","isConnected":false},"lora_list":{"label":"Lora","display":"input","type":"custom_lora","isConnected":false},"text_encoders":{"label":"Text Encoders","display":"output","type":"diffusers_auto_models","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"unet_out":{"label":"Denoise Model","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"vae_out":{"label":"VAE","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"scheduler":{"label":"Scheduler","display":"output","type":"diffusers_auto_model","isConnected":true},"image_encoder":{"label":"Image Encoder","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":false},"quant_config":{"label":"Quant Config","display":"input","type":"quant_config","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":9961472,"min":9961472,"max":9961472},"executionTime":{"last":5.63805627822876,"min":5.63805627822876,"max":5.63805627822876}},"measured":{"width":467,"height":566},"selected":false,"dragging":false,"width":467,"height":566},{"id":"pxWqiHEyVADvhb5z-rP1V","type":"custom","position":{"x":676.1946548252492,"y":-58.2208979059896},"data":{"module":"modules.ModularDiffusers","action":"EncodePrompt","type":"custom","label":"Encode Prompt","category":"embedding","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"text_encoders":{"label":"Text Encoders *","type":"diffusers_auto_models","display":"input","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"prompt":{"label":"Prompt *","type":"string","display":"textarea","default":"","value":"grab the subject from each image and put them into an epic fight at the beach"},"embeddings":{"label":"Text Embeddings","type":"embeddings","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":8547040256,"min":8547040256,"max":8547040256},"executionTime":{"last":3.3116204738616943,"min":3.3116204738616943,"max":3.3116204738616943}},"measured":{"width":314,"height":334},"selected":false,"dragging":false,"width":314,"height":334},{"id":"KfGnP3xtipdvLPeCcOwbY","type":"custom","position":{"x":-106.05386755051046,"y":596.2094181627261},"data":{"module":"modules.Image","action":"Load","type":"custom","label":"Load Image","category":"image","description":"Load an image from a file","resizable":true,"skipParamsCheck":false,"style":{},"params":{"image":{"label":"Image","display":"output","type":"image","isConnected":true},"label":{"display":"ui_label","value":"Load Image"},"file":{"label":false,"display":"filebrowser","type":"str","fieldOptions":{"fileTypes":["image"],"multiple":true},"value":["https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/main/resources/turtle.png","https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/main/resources/kangaroo.png"]},"alpha_channel":{"label":"Alpha Channel","type":"string","options":["ignore","add alpha","remove alpha"],"default":"ignore"},"width":{"display":"output","type":"int","isConnected":false},"height":{"display":"output","type":"int","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":9568256,"min":9568256,"max":9568256},"executionTime":{"last":0.5478932857513428,"min":0.5478932857513428,"max":0.5478932857513428}},"measured":{"width":773,"height":621},"selected":false,"dragging":false,"width":773,"height":621}],"edges":[{"source":"WUjHpGhV8lGBGeJvaU010","target":"awx_oWVfLRXTd58gvvQs-","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"DMHVP_qrBOQWux-toX8cZ","type":"default","className":"category-diffusers_auto_model"},{"source":"WUjHpGhV8lGBGeJvaU010","target":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"unet_out","targetHandle":"unet","edgeType":"default","id":"vC6e383V52kMebYm59LzF","type":"default","className":"category-diffusers_auto_model"},{"source":"WUjHpGhV8lGBGeJvaU010","target":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"uM4JNMlHzaSnopfTbfR_a","type":"default","className":"category-diffusers_auto_model"},{"source":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"latents","target":"fNoX-2jsWixE29bRBWCPz","targetHandle":"latents","edgeType":"default","id":"DscyYE69Gmuw8HrS6KgWV","type":"default","className":"category-latents"},{"source":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"images","target":"Vf07pkxSCp8HUxgTLJQ6Z","targetHandle":"image","edgeType":"default","id":"1YuZYKf590oNRgVaqipwu","type":"default","className":"category-image"},{"source":"awx_oWVfLRXTd58gvvQs-","sourceHandle":"image_latents","target":"1nWjS_jWwtCQQwxTEQwC8","targetHandle":"image_latents","edgeType":"default","id":"KdjD0V5gBRfPNomzYkDzI","type":"default","className":"category-latents"},{"source":"WUjHpGhV8lGBGeJvaU010","target":"pxWqiHEyVADvhb5z-rP1V","sourceHandle":"text_encoders","targetHandle":"text_encoders","edgeType":"default","id":"UuRxJknGB0gDJk-5-VoQQ","type":"default","className":"category-diffusers_auto_models"},{"source":"pxWqiHEyVADvhb5z-rP1V","sourceHandle":"embeddings","target":"1nWjS_jWwtCQQwxTEQwC8","targetHandle":"embeddings","edgeType":"default","id":"SJnVhkeAumc6kcuhl6lYv","type":"default","className":"category-embeddings"},{"source":"KfGnP3xtipdvLPeCcOwbY","sourceHandle":"image","target":"awx_oWVfLRXTd58gvvQs-","targetHandle":"image","edgeType":"default","id":"5z7R0o8rNZ7H4GOrqCl3S","type":"default","className":"category-image"},{"source":"WUjHpGhV8lGBGeJvaU010","sourceHandle":"scheduler","target":"1nWjS_jWwtCQQwxTEQwC8","targetHandle":"scheduler","edgeType":"default","id":"tROCBNMkmHt4NtxXe1B8A","type":"default","className":"category-diffusers_auto_model"}],"viewport":{"x":173.4297084124869,"y":195.52989491409534,"zoom":0.5703818579342119}} \ No newline at end of file +{"nodes":[{"id":"1nWjS_jWwtCQQwxTEQwC8","type":"custom","position":{"x":1112.0754378533063,"y":-53.64280029341349},"data":{"module":"modules.ModularDiffusers","action":"Denoise","type":"custom","label":"Denoise","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"unet":{"label":"Denoise Model *","display":"input","type":"diffusers_auto_model","onSignal":["update_node",{"action":"signal","target":"guider"},{"action":"signal","target":"controlnet_bundle"}],"isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"embeddings":{"label":"Text Embeddings *","type":"embeddings","display":"input","isConnected":true},"width":{"label":"Width","type":"int","default":1024,"min":64,"step":8,"value":1024},"height":{"label":"Height","type":"int","default":1024,"min":64,"step":8,"value":1024},"seed":{"label":"Seed","type":"int","display":"random","default":0,"min":0,"max":4294967295,"value":0},"num_inference_steps":{"label":"Steps","type":"int","display":"slider","default":4,"min":1,"max":100,"value":4},"guidance_scale":{"label":"Guidance Scale","type":"float","display":"slider","default":1,"min":1,"max":30,"step":0.1,"value":1,"hidden":false},"image_latents":{"label":"Image Latents","type":"latents","display":"input","isConnected":true},"guider":{"label":"Guider","type":"custom_guider","display":"input","onChange":{"false":["guidance_scale"],"true":[]},"isConnected":false},"scheduler":{"label":"Scheduler *","type":"diffusers_auto_model","display":"input","isConnected":true},"latents":{"label":"Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"fNoX-2jsWixE29bRBWCPz","type":"custom","position":{"x":1614.1427900291944,"y":-44.67221638113184},"data":{"module":"modules.ModularDiffusers","action":"DecodeLatents","type":"custom","label":"Decode Latents","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"latents":{"label":"Latents *","type":"latents","display":"input","isConnected":true},"images":{"label":"Images","type":"image","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"awx_oWVfLRXTd58gvvQs-","type":"custom","position":{"x":707.5591054253803,"y":342.2244378590507},"data":{"module":"modules.ModularDiffusers","action":"ImageEncode","type":"custom","label":"Encode Image","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"image":{"label":"Image *","type":"image","display":"input","isConnected":true},"image_latents":{"label":"Image Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"Vf07pkxSCp8HUxgTLJQ6Z","type":"custom","position":{"x":1969.447042842304,"y":-32.70234521736318},"data":{"module":"modules.Image","action":"Preview","type":"custom","label":"Preview Image","category":"image","description":"Preview an image","resizable":true,"skipParamsCheck":false,"style":{},"params":{"vae":{"type":"pipeline","display":"input","label":"VAE","isConnected":false,"hidden":true},"device":{"type":"string","default":"cuda:0","hidden":true},"image":{"type":["image","latent"],"display":"input","onChange":{"action":"show","data":{"true":["vae","device"],"false":[]},"condition":{"type":"latent"}},"isConnected":true},"preview":{"display":"ui_image","type":"url","dataSource":"output","value":[]},"output":{"type":"image","display":"output","label":"All images","isConnected":false},"export":{"type":"str","default":"0","description":"Export the image at the given index. Leave empty to export all images."},"filtered":{"type":"image","display":"output","label":"Selected image","isConnected":false}}}},{"id":"WUjHpGhV8lGBGeJvaU010","type":"custom","position":{"x":122.23912873798855,"y":-66.76519907957203},"data":{"module":"modules.ModularDiffusers","action":"ModelsLoader","type":"custom","label":"Load Models","category":"loader","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"model_type":{"label":"Model Type","type":"string","options":{"":"","DummyCustomPipeline":"Custom","StableDiffusionXLModularPipeline":"Stable Diffusion XL","QwenImageModularPipeline":"Qwen Image","QwenImageEditModularPipeline":"Qwen Image Edit","QwenImageEditPlusModularPipeline":"Qwen Image Edit Plus","QwenImageLayeredModularPipeline":"Qwen Image Layered","FluxModularPipeline":"Flux","FluxKontextModularPipeline":"Flux Kontext","Flux2KleinModularPipeline":"Flux 2 Klein Distilled","ZImageModularPipeline":"Z-Image","WanModularPipeline":"WAN"},"onChange":["set_filters",{"action":"signal","target":"unet_out"},{"action":"signal","target":"text_encoders"},{"action":"signal","target":"vae_out"},{"action":"signal","target":"image_encoder"}],"disabled":false,"value":"Flux2KleinModularPipeline"},"repo_id":{"label":"Repository ID","display":"modelselect","type":"string","value":{"source":"hub","value":"black-forest-labs/FLUX.2-klein-4B"},"fieldOptions":{"noValidation":true,"sources":["hub","local"],"filter":{"hub":{"className":["Flux2KleinModularPipeline"]}}},"disabled":false,"default":{"source":"hub","value":"black-forest-labs/FLUX.2-klein-4B"}},"dtype":{"label":"dtype","options":["float32","float16","bfloat16"],"value":"bfloat16","disabled":false},"device":{"label":"Device","type":"string","value":"cuda:0"},"trust_remote_code":{"label":"Trust Remote Code","type":"boolean","value":false},"auto_offload":{"label":"Enable Auto Offload","type":"boolean","value":true},"unet":{"label":"Denoise Model","display":"input","type":"diffusers_auto_model","isConnected":false},"vae":{"label":"VAE","display":"input","type":"diffusers_auto_model","isConnected":false},"lora_list":{"label":"Lora","display":"input","type":"custom_lora","isConnected":false},"text_encoders":{"label":"Text Encoders","display":"output","type":"diffusers_auto_models","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"unet_out":{"label":"Denoise Model","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"vae_out":{"label":"VAE","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"scheduler":{"label":"Scheduler","display":"output","type":"diffusers_auto_model","isConnected":true},"image_encoder":{"label":"Image Encoder","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":false},"quant_config":{"label":"Quant Config","display":"input","type":"quant_config","isConnected":false},"revision":{"label":"Revision","type":"string","default":"","value":"e7b7dc27f91deacad38e78976d1f2b499d76a294"}}},"width":467,"height":566},{"id":"pxWqiHEyVADvhb5z-rP1V","type":"custom","position":{"x":676.1946548252492,"y":-58.2208979059896},"data":{"module":"modules.ModularDiffusers","action":"EncodePrompt","type":"custom","label":"Encode Prompt","category":"embedding","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"text_encoders":{"label":"Text Encoders *","type":"diffusers_auto_models","display":"input","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"prompt":{"label":"Prompt *","type":"string","display":"textarea","default":"","value":"grab the subject from each image and put them into an epic fight at the beach"},"embeddings":{"label":"Text Embeddings","type":"embeddings","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}},"width":314,"height":334},{"id":"KfGnP3xtipdvLPeCcOwbY","type":"custom","position":{"x":-106.05386755051046,"y":596.2094181627261},"data":{"module":"modules.Image","action":"Load","type":"custom","label":"Load Image","category":"image","description":"Load an image from a file","resizable":true,"skipParamsCheck":false,"style":{},"params":{"image":{"label":"Image","display":"output","type":"image","isConnected":true},"label":{"display":"ui_label","value":"Load Image"},"file":{"label":false,"display":"filebrowser","type":"str","fieldOptions":{"fileTypes":["image"],"multiple":true},"value":["https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/42ef98a1ee9295d29009dcc3716f0d5a56f47d48/resources/turtle.png","https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/42ef98a1ee9295d29009dcc3716f0d5a56f47d48/resources/kangaroo.png"]},"alpha_channel":{"label":"Alpha Channel","type":"string","options":["ignore","add alpha","remove alpha"],"default":"ignore"},"width":{"display":"output","type":"int","isConnected":false},"height":{"display":"output","type":"int","isConnected":false}}},"width":773,"height":621}],"edges":[{"source":"WUjHpGhV8lGBGeJvaU010","target":"awx_oWVfLRXTd58gvvQs-","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"DMHVP_qrBOQWux-toX8cZ","type":"default","className":"category-diffusers_auto_model"},{"source":"WUjHpGhV8lGBGeJvaU010","target":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"unet_out","targetHandle":"unet","edgeType":"default","id":"vC6e383V52kMebYm59LzF","type":"default","className":"category-diffusers_auto_model"},{"source":"WUjHpGhV8lGBGeJvaU010","target":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"uM4JNMlHzaSnopfTbfR_a","type":"default","className":"category-diffusers_auto_model"},{"source":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"latents","target":"fNoX-2jsWixE29bRBWCPz","targetHandle":"latents","edgeType":"default","id":"DscyYE69Gmuw8HrS6KgWV","type":"default","className":"category-latents"},{"source":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"images","target":"Vf07pkxSCp8HUxgTLJQ6Z","targetHandle":"image","edgeType":"default","id":"1YuZYKf590oNRgVaqipwu","type":"default","className":"category-image"},{"source":"awx_oWVfLRXTd58gvvQs-","sourceHandle":"image_latents","target":"1nWjS_jWwtCQQwxTEQwC8","targetHandle":"image_latents","edgeType":"default","id":"KdjD0V5gBRfPNomzYkDzI","type":"default","className":"category-latents"},{"source":"WUjHpGhV8lGBGeJvaU010","target":"pxWqiHEyVADvhb5z-rP1V","sourceHandle":"text_encoders","targetHandle":"text_encoders","edgeType":"default","id":"UuRxJknGB0gDJk-5-VoQQ","type":"default","className":"category-diffusers_auto_models"},{"source":"pxWqiHEyVADvhb5z-rP1V","sourceHandle":"embeddings","target":"1nWjS_jWwtCQQwxTEQwC8","targetHandle":"embeddings","edgeType":"default","id":"SJnVhkeAumc6kcuhl6lYv","type":"default","className":"category-embeddings"},{"source":"KfGnP3xtipdvLPeCcOwbY","sourceHandle":"image","target":"awx_oWVfLRXTd58gvvQs-","targetHandle":"image","edgeType":"default","id":"5z7R0o8rNZ7H4GOrqCl3S","type":"default","className":"category-image"},{"source":"WUjHpGhV8lGBGeJvaU010","sourceHandle":"scheduler","target":"1nWjS_jWwtCQQwxTEQwC8","targetHandle":"scheduler","edgeType":"default","id":"tROCBNMkmHt4NtxXe1B8A","type":"default","className":"category-diffusers_auto_model"}],"viewport":{"x":173.4297084124869,"y":195.52989491409534,"zoom":0.5703818579342119}} diff --git a/data/graphs/modular_diffusers/quantization.json b/data/graphs/modular_diffusers/quantization.json index 1ac9a4c..2fa0e2d 100644 --- a/data/graphs/modular_diffusers/quantization.json +++ b/data/graphs/modular_diffusers/quantization.json @@ -1 +1 @@ -{"nodes":[{"id":"xoO5v5A46aiDzRV8CdwQf","type":"custom","position":{"x":703.6294010837418,"y":-49.54467952315033},"data":{"module":"modules.ModularDiffusers","action":"EncodePrompt","type":"custom","label":"Encode Prompt","category":"embedding","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"text_encoders":{"label":"Text Encoders *","type":"diffusers_auto_models","display":"input","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"prompt":{"label":"Prompt *","type":"string","display":"textarea","default":"","value":"a cat"},"embeddings":{"label":"Text Embeddings","type":"embeddings","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":10809725952,"min":927646208,"max":16646008832},"executionTime":{"last":0.5361003875732422,"min":0.00002574920654296875,"max":7.7100136280059814}},"measured":{"width":292,"height":347},"selected":false,"dragging":false,"width":292,"height":347},{"id":"1nWjS_jWwtCQQwxTEQwC8","type":"custom","position":{"x":1092.9508742587448,"y":-50.910719779904696},"data":{"module":"modules.ModularDiffusers","action":"Denoise","type":"custom","label":"Denoise","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"unet":{"label":"Denoise Model *","display":"input","type":"diffusers_auto_model","onSignal":["update_node",{"action":"signal","target":"guider"},{"action":"signal","target":"controlnet_bundle"}],"isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"embeddings":{"label":"Text Embeddings *","type":"embeddings","display":"input","isConnected":true},"width":{"label":"Width","type":"int","default":1024,"min":64,"step":8,"value":1024},"height":{"label":"Height","type":"int","default":1024,"min":64,"step":8,"value":1024},"seed":{"label":"Seed","type":"int","display":"random","default":0,"min":0,"max":4294967295,"value":0},"num_inference_steps":{"label":"Steps","type":"int","display":"slider","default":4,"min":1,"max":100,"value":4},"guidance_scale":{"label":"Guidance Scale","type":"float","display":"slider","default":1,"min":1,"max":30,"step":0.1,"value":1},"image_latents":{"label":"Image Latents","type":"latents","display":"input","isConnected":false},"guider":{"label":"Guider","type":"custom_guider","display":"input","onChange":{"false":["guidance_scale"],"true":[]},"isConnected":false},"scheduler":{"label":"Scheduler *","type":"diffusers_auto_model","display":"input","isConnected":true},"latents":{"label":"Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":3320028672,"min":927646208,"max":29000342016},"executionTime":{"last":3.2859530448913574,"min":0.000019788742065429688,"max":168.20590543746948}},"measured":{"width":386,"height":456},"selected":false,"dragging":false},{"id":"fNoX-2jsWixE29bRBWCPz","type":"custom","position":{"x":1586.0914069470814,"y":-48.178639266395976},"data":{"module":"modules.ModularDiffusers","action":"DecodeLatents","type":"custom","label":"Decode Latents","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"latents":{"label":"Latents *","type":"latents","display":"input","isConnected":true},"images":{"label":"Images","type":"image","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":5004914688,"min":927646208,"max":28290637824},"executionTime":{"last":0.5742390155792236,"min":0.000010967254638671875,"max":2.0491414070129395}},"measured":{"width":228,"height":195},"selected":false,"dragging":false},{"id":"QxVdwi0hW7_hLjhcRrFNJ","type":"custom","position":{"x":230.97947224672117,"y":-97.35608850955407},"data":{"module":"modules.ModularDiffusers","action":"ModelsLoader","type":"custom","label":"Load Models","category":"loader","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"model_type":{"label":"Model Type","type":"string","options":{"":"","DummyCustomPipeline":"Custom","StableDiffusionXLModularPipeline":"Stable Diffusion XL","QwenImageModularPipeline":"Qwen Image","QwenImageEditModularPipeline":"Qwen Image Edit","QwenImageEditPlusModularPipeline":"Qwen Image Edit Plus","QwenImageLayeredModularPipeline":"Qwen Image Layered","FluxModularPipeline":"Flux","FluxKontextModularPipeline":"Flux Kontext","Flux2KleinModularPipeline":"Flux 2 Klein Distilled","ZImageModularPipeline":"Z-Image","WanModularPipeline":"WAN"},"onChange":["set_filters",{"action":"signal","target":"unet_out"},{"action":"signal","target":"text_encoders"},{"action":"signal","target":"vae_out"},{"action":"signal","target":"image_encoder"}],"disabled":false,"value":"Flux2KleinModularPipeline"},"repo_id":{"label":"Repository ID","display":"modelselect","type":"string","value":{"source":"hub","value":"black-forest-labs/FLUX.2-klein-4B"},"fieldOptions":{"noValidation":true,"sources":["hub","local"],"filter":{"hub":{"className":["Flux2KleinModularPipeline"]}}},"disabled":false,"default":{"source":"hub","value":"black-forest-labs/FLUX.2-klein-4B"}},"dtype":{"label":"dtype","options":["float32","float16","bfloat16"],"value":"bfloat16","disabled":false},"device":{"label":"Device","type":"string","value":"cuda:0","options":{"cuda:0":{"arch":"cuda","name":"NVIDIA GeForce RTX 5090 31.34GB (0)","label":["cuda:0"],"total_memory":33647820800,"index":0},"cpu:0":{"arch":"cpu","name":"CPU (0)","label":["cpu:0"],"total_memory":0,"index":0}}},"trust_remote_code":{"label":"Trust Remote Code","type":"boolean","value":false},"auto_offload":{"label":"Enable Auto Offload","type":"boolean","value":true},"unet":{"label":"Denoise Model","display":"input","type":"diffusers_auto_model","isConnected":false},"vae":{"label":"VAE","display":"input","type":"diffusers_auto_model","isConnected":false},"lora_list":{"label":"Lora","display":"input","type":"custom_lora","isConnected":false},"text_encoders":{"label":"Text Encoders","display":"output","type":"diffusers_auto_models","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"unet_out":{"label":"Denoise Model","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"vae_out":{"label":"VAE","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"scheduler":{"label":"Scheduler","display":"output","type":"diffusers_auto_model","isConnected":true},"image_encoder":{"label":"Image Encoder","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":false},"quant_config":{"label":"Quant Config","display":"input","type":"quant_config","isConnected":true}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":7991824896,"min":927646208,"max":30606155264},"executionTime":{"last":3.3953962326049805,"min":0.00003075599670410156,"max":24.532272577285767}},"measured":{"width":374,"height":550},"selected":false,"dragging":false,"width":374,"height":550},{"id":"VTCmChLrJK5KEJYBrRU05","type":"custom","position":{"x":1889.3523439465569,"y":-49.54467952315035},"data":{"module":"modules.Image","action":"Preview","type":"custom","label":"Preview Image","category":"image","description":"Preview an image","resizable":true,"skipParamsCheck":false,"style":{},"params":{"vae":{"type":"pipeline","display":"input","label":"VAE","isConnected":false,"hidden":true},"device":{"type":"string","default":"cuda:0","options":{"cuda:0":{"arch":"cuda","name":"NVIDIA GeForce RTX 5090 31.34GB (0)","label":["cuda:0"],"total_memory":33647820800,"index":0},"cpu:0":{"arch":"cpu","name":"CPU (0)","label":["cpu:0"],"total_memory":0,"index":0}},"hidden":true},"image":{"type":["image","latent"],"display":"input","onChange":{"action":"show","data":{"true":["vae","device"],"false":[]},"condition":{"type":"latent"}},"isConnected":true},"preview":{"display":"ui_image","type":"url","dataSource":"output","value":["/cache/VTCmChLrJK5KEJYBrRU05/output/0?format=WEBP&quality=100&t=1770219384.1695094"]},"output":{"type":"image","display":"output","label":"All images","isConnected":false},"export":{"type":"str","default":"0","description":"Export the image at the given index. Leave empty to export all images."},"filtered":{"type":"image","display":"output","label":"Selected image","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":2450582528,"min":927646208,"max":16881360384},"executionTime":{"last":0.0008842945098876953,"min":0.00002574920654296875,"max":0.0009913444519042969}},"measured":{"width":1044,"height":1241},"selected":false,"dragging":false},{"id":"UWzYElWHfY_22Krt_T7oE","type":"custom","position":{"x":-187.12909553554226,"y":-11.075605984738871},"data":{"module":"modules.ModularDiffusers","action":"QuantizationConfigNode","type":"custom","label":"Quantization Config","category":"loader","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"model_id":{"label":"Model ID","display":"modelselect","type":"string","value":{"source":"hub","value":"black-forest-labs/FLUX.2-klein-4B"},"fieldOptions":{"noValidation":true,"sources":["hub","local"]}},"subfolder":{"label":"Subfolder","type":"string","value":"transformer"},"load_layers_button":{"label":"Load Model Layers","display":"ui_button","value":false,"onChange":"update_skip_modules"},"component":{"label":"Component","type":"string","value":"transformer"},"quant_type":{"label":"Quant Type","type":"string","options":["bnb_4bit","bnb_8bit"],"value":"bnb_4bit","onChange":{"bnb_4bit":["bnb_4bit_quant_type","bnb_4bit_compute_dtype","bnb_4bit_use_double_quant"],"bnb_8bit":["llm_int8_threshold","llm_int8_has_fp16_weight"]}},"bnb_4bit_quant_type":{"label":"4-bit Quant Type","type":"string","options":["nf4","fp4"],"value":"nf4","hidden":false},"bnb_4bit_compute_dtype":{"label":"Compute Dtype","type":"string","options":["","float32","float16","bfloat16"],"value":"","hidden":false},"bnb_4bit_use_double_quant":{"label":"Double Quant","type":"boolean","value":false,"hidden":false},"llm_int8_threshold":{"label":"Int8 Threshold","type":"float","display":"slider","default":6,"min":0,"max":10,"step":0.5,"hidden":true},"llm_int8_has_fp16_weight":{"label":"Has FP16 Weight","type":"boolean","value":false,"hidden":true},"llm_int8_skip_modules":{"label":"Skip Modules","type":"string","display":"select","options":[],"fieldOptions":{"multiple":true},"value":[]},"quantization_config":{"label":"Quantization Config","type":"quant_config","display":"output","isConnected":true},"config_info":{"label":"Quantization Config Info","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":12696442880,"min":12696442880,"max":12696442880},"executionTime":{"last":0.0008592605590820312,"min":0.0008592605590820312,"max":0.0008592605590820312}},"measured":{"width":303,"height":500},"selected":false,"dragging":false}],"edges":[{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"OUG-jpOwjbgTJnmzFzmxd","type":"default","className":"category-diffusers_auto_model"},{"source":"fNoX-2jsWixE29bRBWCPz","target":"VTCmChLrJK5KEJYBrRU05","sourceHandle":"images","targetHandle":"image","edgeType":"default","id":"2x67dYbsJ57aWoIH5JLxx","type":"default","className":"category-image"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"unet_out","targetHandle":"unet","edgeType":"default","id":"JEh4NYlx3OyFulp0yHJzy","type":"default","className":"category-diffusers_auto_model"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"scheduler","targetHandle":"scheduler","edgeType":"default","id":"rokx_1vgNZzxkJZTim_XO","type":"default","className":"category-diffusers_auto_model"},{"source":"1nWjS_jWwtCQQwxTEQwC8","target":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"latents","targetHandle":"latents","edgeType":"default","id":"kGv_8gQYihRE8_690dGRK","type":"default","className":"category-latents"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"xoO5v5A46aiDzRV8CdwQf","sourceHandle":"text_encoders","targetHandle":"text_encoders","edgeType":"default","id":"eWmKJdS42183RvJejiR2Y","type":"default","className":"category-diffusers_auto_models"},{"source":"xoO5v5A46aiDzRV8CdwQf","target":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"embeddings","targetHandle":"embeddings","edgeType":"default","id":"XAmHuBXPGNiLaAsXv0_gc","type":"default","className":"category-embeddings"},{"source":"UWzYElWHfY_22Krt_T7oE","sourceHandle":"quantization_config","target":"QxVdwi0hW7_hLjhcRrFNJ","targetHandle":"quant_config","edgeType":"default","id":"3-i-5nRaGbd1cHB97gtaR","type":"default","className":"category-quant_config"}],"viewport":{"x":186.44996827734076,"y":150.5676115687125,"zoom":0.5586435690361113}} \ No newline at end of file +{"nodes":[{"id":"xoO5v5A46aiDzRV8CdwQf","type":"custom","position":{"x":703.6294010837418,"y":-49.54467952315033},"data":{"module":"modules.ModularDiffusers","action":"EncodePrompt","type":"custom","label":"Encode Prompt","category":"embedding","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"text_encoders":{"label":"Text Encoders *","type":"diffusers_auto_models","display":"input","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"prompt":{"label":"Prompt *","type":"string","display":"textarea","default":"","value":"a cat"},"embeddings":{"label":"Text Embeddings","type":"embeddings","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}},"width":292,"height":347},{"id":"1nWjS_jWwtCQQwxTEQwC8","type":"custom","position":{"x":1092.9508742587448,"y":-50.910719779904696},"data":{"module":"modules.ModularDiffusers","action":"Denoise","type":"custom","label":"Denoise","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"unet":{"label":"Denoise Model *","display":"input","type":"diffusers_auto_model","onSignal":["update_node",{"action":"signal","target":"guider"},{"action":"signal","target":"controlnet_bundle"}],"isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"embeddings":{"label":"Text Embeddings *","type":"embeddings","display":"input","isConnected":true},"width":{"label":"Width","type":"int","default":1024,"min":64,"step":8,"value":1024},"height":{"label":"Height","type":"int","default":1024,"min":64,"step":8,"value":1024},"seed":{"label":"Seed","type":"int","display":"random","default":0,"min":0,"max":4294967295,"value":0},"num_inference_steps":{"label":"Steps","type":"int","display":"slider","default":4,"min":1,"max":100,"value":4},"guidance_scale":{"label":"Guidance Scale","type":"float","display":"slider","default":1,"min":1,"max":30,"step":0.1,"value":1},"image_latents":{"label":"Image Latents","type":"latents","display":"input","isConnected":false},"guider":{"label":"Guider","type":"custom_guider","display":"input","onChange":{"false":["guidance_scale"],"true":[]},"isConnected":false},"scheduler":{"label":"Scheduler *","type":"diffusers_auto_model","display":"input","isConnected":true},"latents":{"label":"Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"fNoX-2jsWixE29bRBWCPz","type":"custom","position":{"x":1586.0914069470814,"y":-48.178639266395976},"data":{"module":"modules.ModularDiffusers","action":"DecodeLatents","type":"custom","label":"Decode Latents","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"Flux2KleinModularPipeline"},"disabled":false},"latents":{"label":"Latents *","type":"latents","display":"input","isConnected":true},"images":{"label":"Images","type":"image","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"QxVdwi0hW7_hLjhcRrFNJ","type":"custom","position":{"x":230.97947224672117,"y":-97.35608850955407},"data":{"module":"modules.ModularDiffusers","action":"ModelsLoader","type":"custom","label":"Load Models","category":"loader","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"model_type":{"label":"Model Type","type":"string","options":{"":"","DummyCustomPipeline":"Custom","StableDiffusionXLModularPipeline":"Stable Diffusion XL","QwenImageModularPipeline":"Qwen Image","QwenImageEditModularPipeline":"Qwen Image Edit","QwenImageEditPlusModularPipeline":"Qwen Image Edit Plus","QwenImageLayeredModularPipeline":"Qwen Image Layered","FluxModularPipeline":"Flux","FluxKontextModularPipeline":"Flux Kontext","Flux2KleinModularPipeline":"Flux 2 Klein Distilled","ZImageModularPipeline":"Z-Image","WanModularPipeline":"WAN"},"onChange":["set_filters",{"action":"signal","target":"unet_out"},{"action":"signal","target":"text_encoders"},{"action":"signal","target":"vae_out"},{"action":"signal","target":"image_encoder"}],"disabled":false,"value":"Flux2KleinModularPipeline"},"repo_id":{"label":"Repository ID","display":"modelselect","type":"string","value":{"source":"hub","value":"black-forest-labs/FLUX.2-klein-4B"},"fieldOptions":{"noValidation":true,"sources":["hub","local"],"filter":{"hub":{"className":["Flux2KleinModularPipeline"]}}},"disabled":false,"default":{"source":"hub","value":"black-forest-labs/FLUX.2-klein-4B"}},"dtype":{"label":"dtype","options":["float32","float16","bfloat16"],"value":"bfloat16","disabled":false},"device":{"label":"Device","type":"string","value":"cuda:0"},"trust_remote_code":{"label":"Trust Remote Code","type":"boolean","value":false},"auto_offload":{"label":"Enable Auto Offload","type":"boolean","value":true},"unet":{"label":"Denoise Model","display":"input","type":"diffusers_auto_model","isConnected":false},"vae":{"label":"VAE","display":"input","type":"diffusers_auto_model","isConnected":false},"lora_list":{"label":"Lora","display":"input","type":"custom_lora","isConnected":false},"text_encoders":{"label":"Text Encoders","display":"output","type":"diffusers_auto_models","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"unet_out":{"label":"Denoise Model","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"vae_out":{"label":"VAE","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":true},"scheduler":{"label":"Scheduler","display":"output","type":"diffusers_auto_model","isConnected":true},"image_encoder":{"label":"Image Encoder","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"Flux2KleinModularPipeline"},"isConnected":false},"quant_config":{"label":"Quant Config","display":"input","type":"quant_config","isConnected":true},"revision":{"label":"Revision","type":"string","default":"","value":"e7b7dc27f91deacad38e78976d1f2b499d76a294"}}},"width":374,"height":550},{"id":"VTCmChLrJK5KEJYBrRU05","type":"custom","position":{"x":1889.3523439465569,"y":-49.54467952315035},"data":{"module":"modules.Image","action":"Preview","type":"custom","label":"Preview Image","category":"image","description":"Preview an image","resizable":true,"skipParamsCheck":false,"style":{},"params":{"vae":{"type":"pipeline","display":"input","label":"VAE","isConnected":false,"hidden":true},"device":{"type":"string","default":"cuda:0","hidden":true},"image":{"type":["image","latent"],"display":"input","onChange":{"action":"show","data":{"true":["vae","device"],"false":[]},"condition":{"type":"latent"}},"isConnected":true},"preview":{"display":"ui_image","type":"url","dataSource":"output","value":[]},"output":{"type":"image","display":"output","label":"All images","isConnected":false},"export":{"type":"str","default":"0","description":"Export the image at the given index. Leave empty to export all images."},"filtered":{"type":"image","display":"output","label":"Selected image","isConnected":false}}}},{"id":"UWzYElWHfY_22Krt_T7oE","type":"custom","position":{"x":-187.12909553554226,"y":-11.075605984738871},"data":{"module":"modules.ModularDiffusers","action":"QuantizationConfigNode","type":"custom","label":"Quantization Config","category":"loader","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"model_id":{"label":"Model ID","display":"modelselect","type":"string","value":{"source":"hub","value":"black-forest-labs/FLUX.2-klein-4B"},"fieldOptions":{"noValidation":true,"sources":["hub","local"]}},"subfolder":{"label":"Subfolder","type":"string","value":"transformer"},"load_layers_button":{"label":"Load Model Layers","display":"ui_button","value":false,"onChange":"update_skip_modules"},"component":{"label":"Component","type":"string","value":"transformer"},"quant_type":{"label":"Quant Type","type":"string","options":["bnb_4bit","bnb_8bit"],"value":"bnb_4bit","onChange":{"bnb_4bit":["bnb_4bit_quant_type","bnb_4bit_compute_dtype","bnb_4bit_use_double_quant"],"bnb_8bit":["llm_int8_threshold","llm_int8_has_fp16_weight"]}},"bnb_4bit_quant_type":{"label":"4-bit Quant Type","type":"string","options":["nf4","fp4"],"value":"nf4","hidden":false},"bnb_4bit_compute_dtype":{"label":"Compute Dtype","type":"string","options":["","float32","float16","bfloat16"],"value":"","hidden":false},"bnb_4bit_use_double_quant":{"label":"Double Quant","type":"boolean","value":false,"hidden":false},"llm_int8_threshold":{"label":"Int8 Threshold","type":"float","display":"slider","default":6,"min":0,"max":10,"step":0.5,"hidden":true},"llm_int8_has_fp16_weight":{"label":"Has FP16 Weight","type":"boolean","value":false,"hidden":true},"llm_int8_skip_modules":{"label":"Skip Modules","type":"string","display":"select","options":[],"fieldOptions":{"multiple":true},"value":[]},"quantization_config":{"label":"Quantization Config","type":"quant_config","display":"output","isConnected":true},"config_info":{"label":"Quantization Config Info","type":"string","display":"output","isConnected":false}}}}],"edges":[{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"OUG-jpOwjbgTJnmzFzmxd","type":"default","className":"category-diffusers_auto_model"},{"source":"fNoX-2jsWixE29bRBWCPz","target":"VTCmChLrJK5KEJYBrRU05","sourceHandle":"images","targetHandle":"image","edgeType":"default","id":"2x67dYbsJ57aWoIH5JLxx","type":"default","className":"category-image"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"unet_out","targetHandle":"unet","edgeType":"default","id":"JEh4NYlx3OyFulp0yHJzy","type":"default","className":"category-diffusers_auto_model"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"scheduler","targetHandle":"scheduler","edgeType":"default","id":"rokx_1vgNZzxkJZTim_XO","type":"default","className":"category-diffusers_auto_model"},{"source":"1nWjS_jWwtCQQwxTEQwC8","target":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"latents","targetHandle":"latents","edgeType":"default","id":"kGv_8gQYihRE8_690dGRK","type":"default","className":"category-latents"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"xoO5v5A46aiDzRV8CdwQf","sourceHandle":"text_encoders","targetHandle":"text_encoders","edgeType":"default","id":"eWmKJdS42183RvJejiR2Y","type":"default","className":"category-diffusers_auto_models"},{"source":"xoO5v5A46aiDzRV8CdwQf","target":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"embeddings","targetHandle":"embeddings","edgeType":"default","id":"XAmHuBXPGNiLaAsXv0_gc","type":"default","className":"category-embeddings"},{"source":"UWzYElWHfY_22Krt_T7oE","sourceHandle":"quantization_config","target":"QxVdwi0hW7_hLjhcRrFNJ","targetHandle":"quant_config","edgeType":"default","id":"3-i-5nRaGbd1cHB97gtaR","type":"default","className":"category-quant_config"}],"viewport":{"x":186.44996827734076,"y":150.5676115687125,"zoom":0.5586435690361113}} diff --git a/data/graphs/modular_diffusers/text_to_image.json b/data/graphs/modular_diffusers/text_to_image.json index 6f949f5..18be990 100644 --- a/data/graphs/modular_diffusers/text_to_image.json +++ b/data/graphs/modular_diffusers/text_to_image.json @@ -1 +1 @@ -{"nodes":[{"id":"xoO5v5A46aiDzRV8CdwQf","type":"custom","position":{"x":703.6294010837418,"y":-49.54467952315033},"data":{"module":"modules.ModularDiffusers","action":"EncodePrompt","type":"custom","label":"Encode Prompt","category":"embedding","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"text_encoders":{"label":"Text Encoders *","type":"diffusers_auto_models","display":"input","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"prompt":{"label":"Prompt *","type":"string","display":"textarea","default":"","value":"cinematic film still of a white crow on top of a red turtle that is crossing a river while some monkeys show some encouragement from the other side"},"embeddings":{"label":"Text Embeddings","type":"embeddings","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":8326375936,"min":8326375936,"max":8326375936},"executionTime":{"last":3.5567071437835693,"min":3.5567071437835693,"max":3.5567071437835693}},"measured":{"width":292,"height":347},"selected":false,"dragging":false,"width":292,"height":347},{"id":"1nWjS_jWwtCQQwxTEQwC8","type":"custom","position":{"x":1092.9508742587448,"y":-50.910719779904696},"data":{"module":"modules.ModularDiffusers","action":"Denoise","type":"custom","label":"Denoise","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"unet":{"label":"Denoise Model *","display":"input","type":"diffusers_auto_model","onSignal":["update_node",{"action":"signal","target":"guider"},{"action":"signal","target":"controlnet_bundle"}],"isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"embeddings":{"label":"Text Embeddings *","type":"embeddings","display":"input","isConnected":true},"width":{"label":"Width","type":"int","default":1024,"min":64,"step":8,"value":1024,"hidden":false},"height":{"label":"Height","type":"int","default":1024,"min":64,"step":8,"value":1024,"hidden":false},"seed":{"label":"Seed","type":"int","display":"random","default":0,"min":0,"max":4294967295,"value":0},"num_inference_steps":{"label":"Steps","type":"int","display":"slider","default":9,"min":1,"max":100,"value":9},"guidance_scale":{"label":"Guidance Scale","type":"float","display":"slider","default":1,"min":1,"max":30,"step":0.1,"value":1,"hidden":false},"image_latents":{"label":"Image Latents","type":"latents","display":"input","onChange":{"false":["height","width"],"true":["strength"]},"isConnected":false},"strength":{"label":"Strength","type":"float","default":0.5,"min":0,"max":1,"step":0.01,"value":0.5,"hidden":true},"guider":{"label":"Guider","type":"custom_guider","display":"input","onChange":{"false":["guidance_scale"],"true":[]},"isConnected":false},"scheduler":{"label":"Scheduler *","type":"diffusers_auto_model","display":"input","isConnected":true},"latents":{"label":"Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":21050819584,"min":21050819584,"max":21050819584},"executionTime":{"last":4.785482168197632,"min":4.785482168197632,"max":4.785482168197632}},"measured":{"width":386,"height":456},"selected":false,"dragging":false},{"id":"fNoX-2jsWixE29bRBWCPz","type":"custom","position":{"x":1586.0914069470814,"y":-48.178639266395976},"data":{"module":"modules.ModularDiffusers","action":"DecodeLatents","type":"custom","label":"Decode Latents","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"latents":{"label":"Latents *","type":"latents","display":"input","isConnected":true},"images":{"label":"Images","type":"image","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":23276584960,"min":23276584960,"max":23276584960},"executionTime":{"last":0.6423635482788086,"min":0.6423635482788086,"max":0.6423635482788086}},"measured":{"width":236,"height":195},"selected":false,"dragging":false},{"id":"QxVdwi0hW7_hLjhcRrFNJ","type":"custom","position":{"x":270.59463969259855,"y":-57.74092106367662},"data":{"module":"modules.ModularDiffusers","action":"ModelsLoader","type":"custom","label":"Load Models","category":"loader","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"model_type":{"label":"Model Type","type":"string","options":{"":"","DummyCustomPipeline":"Custom","StableDiffusionXLModularPipeline":"Stable Diffusion XL","QwenImageModularPipeline":"Qwen Image","QwenImageEditModularPipeline":"Qwen Image Edit","QwenImageEditPlusModularPipeline":"Qwen Image Edit Plus","QwenImageLayeredModularPipeline":"Qwen Image Layered","FluxModularPipeline":"Flux","FluxKontextModularPipeline":"Flux Kontext","Flux2KleinModularPipeline":"Flux 2 Klein Distilled","ZImageModularPipeline":"Z-Image","WanModularPipeline":"WAN"},"onChange":["set_filters",{"action":"signal","target":"unet_out"},{"action":"signal","target":"text_encoders"},{"action":"signal","target":"vae_out"},{"action":"signal","target":"image_encoder"}],"disabled":false,"value":"ZImageModularPipeline"},"repo_id":{"label":"Repository ID","display":"modelselect","type":"string","value":{"source":"hub","value":"Tongyi-MAI/Z-Image-Turbo"},"fieldOptions":{"noValidation":true,"sources":["hub","local"],"filter":{"hub":{"className":["ZImageModularPipeline"]}}},"disabled":false,"default":{"source":"hub","value":"Tongyi-MAI/Z-Image-Turbo"}},"dtype":{"label":"dtype","options":["float32","float16","bfloat16"],"value":"bfloat16","disabled":false},"device":{"label":"Device","type":"string","value":"cuda:0","options":{"cuda:0":{"arch":"cuda","name":"NVIDIA GeForce RTX 5090 31.34GB (0)","label":["cuda:0"],"total_memory":33647820800,"index":0},"cpu:0":{"arch":"cpu","name":"CPU (0)","label":["cpu:0"],"total_memory":0,"index":0}}},"trust_remote_code":{"label":"Trust Remote Code","type":"boolean","value":false},"auto_offload":{"label":"Enable Auto Offload","type":"boolean","value":true},"unet":{"label":"Denoise Model","display":"input","type":"diffusers_auto_model","isConnected":false},"vae":{"label":"VAE","display":"input","type":"diffusers_auto_model","isConnected":false},"lora_list":{"label":"Lora","display":"input","type":"custom_lora","isConnected":false},"text_encoders":{"label":"Text Encoders","display":"output","type":"diffusers_auto_models","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"unet_out":{"label":"Denoise Model","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"vae_out":{"label":"VAE","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"scheduler":{"label":"Scheduler","display":"output","type":"diffusers_auto_model","isConnected":true},"image_encoder":{"label":"Image Encoder","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":false},"quant_config":{"label":"Quant Config","display":"input","type":"quant_config","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":9568256,"min":9568256,"max":9568256},"executionTime":{"last":8.71963095664978,"min":8.71963095664978,"max":8.71963095664978}},"measured":{"width":319,"height":563},"selected":false,"dragging":false},{"id":"VTCmChLrJK5KEJYBrRU05","type":"custom","position":{"x":1889.3523439465569,"y":-49.54467952315035},"data":{"module":"modules.Image","action":"Preview","type":"custom","label":"Preview Image","category":"image","description":"Preview an image","resizable":true,"skipParamsCheck":false,"style":{},"params":{"vae":{"type":"pipeline","display":"input","label":"VAE","isConnected":false,"hidden":true},"device":{"type":"string","default":"cuda:0","options":{"cuda:0":{"arch":"cuda","name":"NVIDIA GeForce RTX 5090 31.34GB (0)","label":["cuda:0"],"total_memory":33647820800,"index":0},"cpu:0":{"arch":"cpu","name":"CPU (0)","label":["cpu:0"],"total_memory":0,"index":0}},"hidden":true},"image":{"type":["image","latent"],"display":"input","onChange":{"action":"show","data":{"true":["vae","device"],"false":[]},"condition":{"type":"latent"}},"isConnected":true},"preview":{"display":"ui_image","type":"url","dataSource":"output","value":["/cache/VTCmChLrJK5KEJYBrRU05/output/0?format=WEBP&quality=100&t=1770208275.3147793"]},"output":{"type":"image","display":"output","label":"All images","isConnected":false},"export":{"type":"str","default":"0","description":"Export the image at the given index. Leave empty to export all images."},"filtered":{"type":"image","display":"output","label":"Selected image","isConnected":false}},"time":[0,0,0],"memory":[0,0,0],"cache":false,"progress":0,"isCached":true,"memoryUsage":{"last":20723826688,"min":20723826688,"max":20723826688},"executionTime":{"last":0.000049591064453125,"min":0.000049591064453125,"max":0.000049591064453125}},"measured":{"width":1044,"height":1241},"selected":true,"dragging":false}],"edges":[{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"xoO5v5A46aiDzRV8CdwQf","sourceHandle":"text_encoders","targetHandle":"text_encoders","edgeType":"default","id":"g6w-_uAD61EwctAKgNRMH","type":"default","className":"category-diffusers_auto_models"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"unet_out","targetHandle":"unet","edgeType":"default","id":"QqZpkd6D_t1mTvqU262m7","type":"default","className":"category-diffusers_auto_model"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"CE_VEmveEYAewB77dYxSI","type":"default","className":"category-diffusers_auto_model"},{"source":"xoO5v5A46aiDzRV8CdwQf","sourceHandle":"embeddings","target":"1nWjS_jWwtCQQwxTEQwC8","targetHandle":"embeddings","edgeType":"default","id":"d2Do3C6rr3mcckWT5G_Qb","type":"default","className":"category-embeddings"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","sourceHandle":"scheduler","target":"1nWjS_jWwtCQQwxTEQwC8","targetHandle":"scheduler","edgeType":"default","id":"XwvXEEFWETP57Y10DqUHT","type":"default","className":"category-diffusers_auto_model"},{"source":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"latents","target":"fNoX-2jsWixE29bRBWCPz","targetHandle":"latents","edgeType":"default","id":"pwcEpv4EWm_ujEPHoS5dr","type":"default","className":"category-latents"},{"source":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"images","target":"VTCmChLrJK5KEJYBrRU05","targetHandle":"image","edgeType":"default","id":"GovyvgTGYP-3CdIdEI8Ip","type":"default","className":"category-image"}],"viewport":{"x":-144.7378476718609,"y":101.17496497943574,"zoom":0.7320428479728138}} \ No newline at end of file +{"nodes":[{"id":"xoO5v5A46aiDzRV8CdwQf","type":"custom","position":{"x":703.6294010837418,"y":-49.54467952315033},"data":{"module":"modules.ModularDiffusers","action":"EncodePrompt","type":"custom","label":"Encode Prompt","category":"embedding","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"text_encoders":{"label":"Text Encoders *","type":"diffusers_auto_models","display":"input","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"prompt":{"label":"Prompt *","type":"string","display":"textarea","default":"","value":"cinematic film still of a white crow on top of a red turtle that is crossing a river while some monkeys show some encouragement from the other side"},"embeddings":{"label":"Text Embeddings","type":"embeddings","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}},"width":292,"height":347},{"id":"1nWjS_jWwtCQQwxTEQwC8","type":"custom","position":{"x":1092.9508742587448,"y":-50.910719779904696},"data":{"module":"modules.ModularDiffusers","action":"Denoise","type":"custom","label":"Denoise","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"unet":{"label":"Denoise Model *","display":"input","type":"diffusers_auto_model","onSignal":["update_node",{"action":"signal","target":"guider"},{"action":"signal","target":"controlnet_bundle"}],"isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"embeddings":{"label":"Text Embeddings *","type":"embeddings","display":"input","isConnected":true},"width":{"label":"Width","type":"int","default":1024,"min":64,"step":8,"value":1024,"hidden":false},"height":{"label":"Height","type":"int","default":1024,"min":64,"step":8,"value":1024,"hidden":false},"seed":{"label":"Seed","type":"int","display":"random","default":0,"min":0,"max":4294967295,"value":0},"num_inference_steps":{"label":"Steps","type":"int","display":"slider","default":9,"min":1,"max":100,"value":9},"guidance_scale":{"label":"Guidance Scale","type":"float","display":"slider","default":1,"min":1,"max":30,"step":0.1,"value":1,"hidden":false},"image_latents":{"label":"Image Latents","type":"latents","display":"input","onChange":{"false":["height","width"],"true":["strength"]},"isConnected":false},"strength":{"label":"Strength","type":"float","default":0.5,"min":0,"max":1,"step":0.01,"value":0.5,"hidden":true},"guider":{"label":"Guider","type":"custom_guider","display":"input","onChange":{"false":["guidance_scale"],"true":[]},"isConnected":false},"scheduler":{"label":"Scheduler *","type":"diffusers_auto_model","display":"input","isConnected":true},"latents":{"label":"Latents","type":"latents","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"fNoX-2jsWixE29bRBWCPz","type":"custom","position":{"x":1586.0914069470814,"y":-48.178639266395976},"data":{"module":"modules.ModularDiffusers","action":"DecodeLatents","type":"custom","label":"Decode Latents","category":"sampler","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"vae":{"label":"VAE *","display":"input","type":"diffusers_auto_model","onSignal":"update_node","isConnected":true,"signal":{"direction":"output","value":"ZImageModularPipeline"},"disabled":false},"latents":{"label":"Latents *","type":"latents","display":"input","isConnected":true},"images":{"label":"Images","type":"image","display":"output","isConnected":true},"doc":{"label":"Doc","type":"string","display":"output","isConnected":false}}}},{"id":"QxVdwi0hW7_hLjhcRrFNJ","type":"custom","position":{"x":270.59463969259855,"y":-57.74092106367662},"data":{"module":"modules.ModularDiffusers","action":"ModelsLoader","type":"custom","label":"Load Models","category":"loader","description":"","resizable":true,"skipParamsCheck":true,"style":{},"params":{"model_type":{"label":"Model Type","type":"string","options":{"":"","DummyCustomPipeline":"Custom","StableDiffusionXLModularPipeline":"Stable Diffusion XL","QwenImageModularPipeline":"Qwen Image","QwenImageEditModularPipeline":"Qwen Image Edit","QwenImageEditPlusModularPipeline":"Qwen Image Edit Plus","QwenImageLayeredModularPipeline":"Qwen Image Layered","FluxModularPipeline":"Flux","FluxKontextModularPipeline":"Flux Kontext","Flux2KleinModularPipeline":"Flux 2 Klein Distilled","ZImageModularPipeline":"Z-Image","WanModularPipeline":"WAN"},"onChange":["set_filters",{"action":"signal","target":"unet_out"},{"action":"signal","target":"text_encoders"},{"action":"signal","target":"vae_out"},{"action":"signal","target":"image_encoder"}],"disabled":false,"value":"ZImageModularPipeline"},"repo_id":{"label":"Repository ID","display":"modelselect","type":"string","value":{"source":"hub","value":"Tongyi-MAI/Z-Image-Turbo"},"fieldOptions":{"noValidation":true,"sources":["hub","local"],"filter":{"hub":{"className":["ZImageModularPipeline"]}}},"disabled":false,"default":{"source":"hub","value":"Tongyi-MAI/Z-Image-Turbo"}},"dtype":{"label":"dtype","options":["float32","float16","bfloat16"],"value":"bfloat16","disabled":false},"device":{"label":"Device","type":"string","value":"cuda:0"},"trust_remote_code":{"label":"Trust Remote Code","type":"boolean","value":false},"auto_offload":{"label":"Enable Auto Offload","type":"boolean","value":true},"unet":{"label":"Denoise Model","display":"input","type":"diffusers_auto_model","isConnected":false},"vae":{"label":"VAE","display":"input","type":"diffusers_auto_model","isConnected":false},"lora_list":{"label":"Lora","display":"input","type":"custom_lora","isConnected":false},"text_encoders":{"label":"Text Encoders","display":"output","type":"diffusers_auto_models","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"unet_out":{"label":"Denoise Model","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"vae_out":{"label":"VAE","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":true},"scheduler":{"label":"Scheduler","display":"output","type":"diffusers_auto_model","isConnected":true},"image_encoder":{"label":"Image Encoder","display":"output","type":"diffusers_auto_model","signal":{"direction":"output","origin":"model_type","value":"ZImageModularPipeline"},"isConnected":false},"quant_config":{"label":"Quant Config","display":"input","type":"quant_config","isConnected":false},"revision":{"label":"Revision","type":"string","default":"","value":"f332072aa78be7aecdf3ee76d5c247082da564a6"}}}},{"id":"VTCmChLrJK5KEJYBrRU05","type":"custom","position":{"x":1889.3523439465569,"y":-49.54467952315035},"data":{"module":"modules.Image","action":"Preview","type":"custom","label":"Preview Image","category":"image","description":"Preview an image","resizable":true,"skipParamsCheck":false,"style":{},"params":{"vae":{"type":"pipeline","display":"input","label":"VAE","isConnected":false,"hidden":true},"device":{"type":"string","default":"cuda:0","hidden":true},"image":{"type":["image","latent"],"display":"input","onChange":{"action":"show","data":{"true":["vae","device"],"false":[]},"condition":{"type":"latent"}},"isConnected":true},"preview":{"display":"ui_image","type":"url","dataSource":"output","value":[]},"output":{"type":"image","display":"output","label":"All images","isConnected":false},"export":{"type":"str","default":"0","description":"Export the image at the given index. Leave empty to export all images."},"filtered":{"type":"image","display":"output","label":"Selected image","isConnected":false}}}}],"edges":[{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"xoO5v5A46aiDzRV8CdwQf","sourceHandle":"text_encoders","targetHandle":"text_encoders","edgeType":"default","id":"g6w-_uAD61EwctAKgNRMH","type":"default","className":"category-diffusers_auto_models"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"unet_out","targetHandle":"unet","edgeType":"default","id":"QqZpkd6D_t1mTvqU262m7","type":"default","className":"category-diffusers_auto_model"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","target":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"vae_out","targetHandle":"vae","edgeType":"default","id":"CE_VEmveEYAewB77dYxSI","type":"default","className":"category-diffusers_auto_model"},{"source":"xoO5v5A46aiDzRV8CdwQf","sourceHandle":"embeddings","target":"1nWjS_jWwtCQQwxTEQwC8","targetHandle":"embeddings","edgeType":"default","id":"d2Do3C6rr3mcckWT5G_Qb","type":"default","className":"category-embeddings"},{"source":"QxVdwi0hW7_hLjhcRrFNJ","sourceHandle":"scheduler","target":"1nWjS_jWwtCQQwxTEQwC8","targetHandle":"scheduler","edgeType":"default","id":"XwvXEEFWETP57Y10DqUHT","type":"default","className":"category-diffusers_auto_model"},{"source":"1nWjS_jWwtCQQwxTEQwC8","sourceHandle":"latents","target":"fNoX-2jsWixE29bRBWCPz","targetHandle":"latents","edgeType":"default","id":"pwcEpv4EWm_ujEPHoS5dr","type":"default","className":"category-latents"},{"source":"fNoX-2jsWixE29bRBWCPz","sourceHandle":"images","target":"VTCmChLrJK5KEJYBrRU05","targetHandle":"image","edgeType":"default","id":"GovyvgTGYP-3CdIdEI8Ip","type":"default","className":"category-image"}],"viewport":{"x":-144.7378476718609,"y":101.17496497943574,"zoom":0.7320428479728138}} diff --git a/data/graphs/studio/ace-step-audio-pipeline/audio-continuation.json b/data/graphs/studio/ace-step-audio-pipeline/audio-continuation.json new file mode 100644 index 0000000..10aa895 --- /dev/null +++ b/data/graphs/studio/ace-step-audio-pipeline/audio-continuation.json @@ -0,0 +1,2041 @@ +{ + "edges": [ + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-04", + "targetHandle": "audio", + "type": "default" + }, + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "output", + "style": { + "stroke": "#F472B6" + }, + "target": "node-01", + "targetHandle": "audio", + "type": "default" + }, + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "output", + "style": { + "stroke": "#F472B6" + }, + "target": "node-03", + "targetHandle": "continuation", + "type": "default" + }, + { + "className": "category-audio_diffusion_pipeline", + "data": { + "connectionType": "audio_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#E879F9", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "pipeline", + "style": { + "stroke": "#E879F9" + }, + "target": "node-02", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-07", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-05", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-02", + "targetHandle": "source_audio", + "type": "default" + }, + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-08", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-03", + "targetHandle": "source", + "type": "default" + }, + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-09", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-04", + "targetHandle": "reference", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "6d7c68dd092604d0b8d50ca77f51e79c7f5784ab71689822a03ac65515d82e07", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Export", + "cache": false, + "category": "Audio", + "description": "Save audio to a WAV file and expose a preview.", + "label": "Export Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "audio": { + "display": "input", + "isConnected": true, + "label": "Audio", + "type": [ + "audio", + "str" + ] + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "file": { + "display": "output", + "isConnected": false, + "label": "File", + "type": "audio" + }, + "filename": { + "default": "{PATH:audio}/MoDiff_{HASH:6}.wav", + "label": "File", + "type": "str" + }, + "preview": { + "dataSource": "file", + "display": "ui_audio", + "type": "url" + }, + "sample_rate": { + "default": 48000, + "label": "Export Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 2640, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Audio", + "description": "Generate audio with a Diffusers audio pipeline.", + "label": "Diffusers.GenerateAudio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "audio": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "audio_cover_strength": { + "default": 0.85, + "display": "slider", + "label": "Cover Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.5 + }, + "audio_duration": { + "default": 30, + "label": "Duration", + "max": 240, + "min": 1, + "step": 0.5, + "type": "float", + "value": 75 + }, + "bpm": { + "default": 0, + "label": "BPM", + "max": 400, + "min": 0, + "type": "int", + "value": 170 + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "extension_duration": { + "default": 15, + "label": "Extension", + "max": 180, + "min": 1, + "step": 0.5, + "type": "float", + "value": 15 + }, + "guidance_scale": { + "default": 1, + "description": "XL Turbo is guidance-distilled; values above 1 are ignored by the Diffusers pipeline.", + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "keyscale": { + "default": "", + "label": "Key", + "type": "string", + "value": "C# minor" + }, + "lora_scale": { + "default": 1, + "description": "Per-generation ACE-Step LoRA multiplier from Diffusers attention_kwargs.", + "display": "slider", + "label": "LoRA call strength", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float" + }, + "lyrics": { + "default": "", + "display": "textarea", + "label": "Lyrics", + "type": "text", + "value": "[Intro]\nSignal waking, low and slow\n\n[Verse]\nBlocks ignite beneath the wire\nShape the noise and feed the fire\nImage, motion, sound align\nEvery path becomes design\nHold the pulse in groups of three\nBuild the chain and set it free\n\n[Pre-Chorus]\nOne by one the modules rise\nPressure climbing through the lines\n\n[Chorus]\nMoDiff, move the whole graph now\nBreak it down and build it loud\nRun the chain, let modules shift\nMake the impossible a modular gift\n\n[Bridge]\n\n[Chorus]\nMoDiff, move the whole graph now\nEvery signal ringing out\nRun the chain, let modules shift\nMake the impossible a modular gift\n\n[Outro]\nMoDiff—lock the final line\n\n[Continuation]\nFrom the silence, count to three\nOne last circuit, set it free\n\n[Final Hook]\nMoDiff, drive the signal home\nEvery path returns as one\n\n[Hard Stop]" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 8, + "description": "ACE-Step v1.5 XL Turbo is designed for 8 denoising steps.", + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_waveforms": { + "default": 1, + "label": "Variations", + "max": 8, + "min": 1, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "audio_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Continue the supplied 75-second alternative-metal song from absolute time 75 to 90 seconds, generating exactly one 15-second tail at 170 BPM in C-sharp minor and strict 3/4. Preserve its down-tuned seven-string guitars, pick bass, tight acoustic metal kit, coarse male lead, chorus-only female scream double, melody language, vocalist identity, mix, room, loudness, and three-beat pulse. Treat the source hard stop as one intentional dramatic breath before a final coda, not as permission to restart with a new intro. Relative to the generated tail: 0-2 seconds, re-enter on beat one with the established low-string motif and drum tone; 2-6 seconds, sing the two-line continuation couplet; 6-12 seconds, lift into the two-line final hook over the established chorus harmony; 12-15 seconds, complete the last phrase and resolve every instrument and vocal together on one new hard stop. Match beat phase, key, timbre, noise floor, stereo width, and ambience at the join. Do not replay the intro, change singer, drift into 4/4 or 6/8, quote unrelated lyrics, fade out, clip, or leave trailing audio." + }, + "reference_audio": { + "display": "input", + "isConnected": false, + "label": "Reference Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "repainting_end": { + "default": 0, + "label": "Repaint End", + "min": 0, + "step": 0.01, + "type": "float", + "value": 10 + }, + "repainting_start": { + "default": 0, + "label": "Repaint Start", + "min": 0, + "step": 0.01, + "type": "float", + "value": 0 + }, + "return_continuation_tail": { + "default": true, + "label": "Return Tail Only", + "type": "bool", + "value": true + }, + "sample_rate": { + "default": 48000, + "label": "Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + }, + "sample_rate_out": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8303 + } + }, + "shift": { + "default": 3, + "display": "slider", + "label": "Shift", + "max": 10, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "source_audio": { + "display": "input", + "isConnected": true, + "label": "Source Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "stable_audio_guidance": { + "default": 7, + "label": "Stable Audio Guidance", + "max": 20, + "min": 0, + "type": "float" + }, + "stable_audio_steps": { + "default": 100, + "label": "Stable Audio Steps", + "max": 300, + "min": 1, + "type": "int" + }, + "task_type": { + "default": "text2music", + "label": "Task", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text2music", + "schemaVersion": 1, + "value": "text2music" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "repaint", + "schemaVersion": 1, + "value": "repaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "continuation", + "schemaVersion": 1, + "value": "continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "extract", + "schemaVersion": 1, + "value": "extract" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "lego", + "schemaVersion": 1, + "value": "lego" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "complete", + "schemaVersion": 1, + "value": "complete" + } + ], + "type": "string", + "value": "continuation" + }, + "timesignature": { + "default": "4", + "label": "Time", + "type": "string", + "value": "3" + }, + "vocal_language": { + "default": "en", + "label": "Language", + "type": "string", + "value": "en" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 1320, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Join", + "cache": false, + "category": "Audio", + "description": "Append a continuation to its source while preserving exact duration.", + "label": "Join Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "boundary_fade_seconds": { + "default": 0.01, + "label": "Boundary Fade", + "max": 1, + "min": 0, + "step": 0.001, + "type": "float", + "value": 0.01 + }, + "continuation": { + "display": "input", + "isConnected": true, + "label": "Continuation", + "type": [ + "audio", + "str" + ] + }, + "duration": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "sample_rate": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + }, + "source": { + "display": "input", + "isConnected": true, + "label": "Source", + "type": [ + "audio", + "str" + ] + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioJoin", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 2200, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "MatchLoudness", + "cache": false, + "category": "Audio", + "description": "Match generated audio to a reference window without changing its dynamics.", + "label": "Match Audio Loudness", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "adjustment_db": { + "display": "output", + "isConnected": false, + "label": "Adjustment", + "type": "float" + }, + "audio": { + "display": "input", + "isConnected": true, + "label": "Audio", + "type": [ + "audio", + "str" + ] + }, + "input_lufs": { + "display": "output", + "isConnected": false, + "label": "Input LUFS", + "type": "float" + }, + "max_adjustment_db": { + "default": 12, + "label": "Max Adjustment", + "max": 30, + "min": 0, + "step": 0.5, + "type": "float", + "value": 12 + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "output_lufs": { + "display": "output", + "isConnected": false, + "label": "Output LUFS", + "type": "float" + }, + "reference": { + "display": "input", + "isConnected": true, + "label": "Reference", + "type": [ + "audio", + "str" + ] + }, + "reference_lufs": { + "display": "output", + "isConnected": false, + "label": "Reference LUFS", + "type": "float" + }, + "reference_window_seconds": { + "default": 15, + "label": "Reference Tail", + "min": 0, + "step": 0.1, + "type": "float", + "value": 15 + }, + "target_peak_dbfs": { + "default": -1, + "label": "Peak Ceiling", + "max": 0, + "min": -9, + "step": 0.1, + "type": "float", + "value": -1 + }, + "true_peak_dbfs": { + "display": "output", + "isConnected": false, + "label": "True Peak", + "type": "float" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioLoudnessMatch", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 1760, + "y": 117 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Audio", + "description": "Load a generic Diffusers audio pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_audio", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_audio", + "schemaVersion": 1, + "value": "text_to_audio" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_variation", + "schemaVersion": 1, + "value": "audio_variation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_continuation", + "schemaVersion": 1, + "value": "audio_continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_repaint", + "schemaVersion": 1, + "value": "audio_repaint" + } + ], + "type": "string", + "value": "audio_continuation" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "ACE-Step/acestep-v15-xl-turbo-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "audio_diffusion_pipeline" + }, + "pipeline_class": { + "default": "AceStepPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "AceStepPipeline", + "schemaVersion": 1, + "value": "AceStepPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "StableAudioPipeline", + "schemaVersion": 1, + "value": "StableAudioPipeline" + } + ], + "type": "string", + "value": "AceStepPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "200ba991ae448051e14b0183157e35c2d27c9fb0" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-05", + "position": { + "x": 880, + "y": 89 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Audio", + "description": "Load an audio file as a reusable audio object.", + "label": "Load Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "audio": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "channels": { + "display": "output", + "isConnected": false, + "label": "Channels", + "type": "int" + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "audio" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "audio/ace_step_audio_continuation.source_audio_yHwfjX.wav" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File", + "type": "str" + }, + "preview": { + "dataSource": "filename", + "display": "ui_audio", + "type": "url" + }, + "sample_rate": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadAudio", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-08", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 87.87077294685989, + "zoom": 0.3647342995169082 + } +} diff --git a/data/graphs/studio/ace-step-audio-pipeline/audio-repaint.json b/data/graphs/studio/ace-step-audio-pipeline/audio-repaint.json new file mode 100644 index 0000000..1037e9f --- /dev/null +++ b/data/graphs/studio/ace-step-audio-pipeline/audio-repaint.json @@ -0,0 +1,1760 @@ +{ + "edges": [ + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-01", + "targetHandle": "audio", + "type": "default" + }, + { + "className": "category-audio_diffusion_pipeline", + "data": { + "connectionType": "audio_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#E879F9", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "pipeline", + "style": { + "stroke": "#E879F9" + }, + "target": "node-02", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-05", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-03", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-02", + "targetHandle": "source_audio", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "8a72d58c81399c99dce95530bfd40153d03056cb8a7cceb67ace2463a5805048", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Export", + "cache": false, + "category": "Audio", + "description": "Save audio to a WAV file and expose a preview.", + "label": "Export Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "audio": { + "display": "input", + "isConnected": true, + "label": "Audio", + "type": [ + "audio", + "str" + ] + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "file": { + "display": "output", + "isConnected": false, + "label": "File", + "type": "audio" + }, + "filename": { + "default": "{PATH:audio}/MoDiff_{HASH:6}.wav", + "label": "File", + "type": "str" + }, + "preview": { + "dataSource": "file", + "display": "ui_audio", + "type": "url" + }, + "sample_rate": { + "default": 48000, + "label": "Export Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Audio", + "description": "Generate audio with a Diffusers audio pipeline.", + "label": "Diffusers.GenerateAudio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "audio": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "audio_cover_strength": { + "default": 0.85, + "display": "slider", + "label": "Cover Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.5 + }, + "audio_duration": { + "default": 30, + "label": "Duration", + "max": 240, + "min": 1, + "step": 0.5, + "type": "float", + "value": 30 + }, + "bpm": { + "default": 0, + "label": "BPM", + "max": 400, + "min": 0, + "type": "int", + "value": 170 + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "extension_duration": { + "default": 15, + "label": "Extension", + "max": 180, + "min": 1, + "step": 0.5, + "type": "float", + "value": 15 + }, + "guidance_scale": { + "default": 1, + "description": "XL Turbo is guidance-distilled; values above 1 are ignored by the Diffusers pipeline.", + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "keyscale": { + "default": "", + "label": "Key", + "type": "string", + "value": "C# minor" + }, + "lora_scale": { + "default": 1, + "description": "Per-generation ACE-Step LoRA multiplier from Diffusers attention_kwargs.", + "display": "slider", + "label": "LoRA call strength", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float" + }, + "lyrics": { + "default": "", + "display": "textarea", + "label": "Lyrics", + "type": "text", + "value": "[intro]\nSignal waking, low and slow\n\n[verse]\nBlocks ignite beneath the wire\nShape the noise and feed the fire\nImage, motion, sound align\nEvery path becomes design\n\n[chorus]\nMoDiff, move the whole graph now\nBreak it down and build it loud\nRun the chain, let modules shift\nMake the impossible a modular gift\n\n[bridge]\nCut the grid, the low strings climb\n\n[outro]\nMoDiff—lock the final line\n\n[hard stop]" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 8, + "description": "ACE-Step v1.5 XL Turbo is designed for 8 denoising steps.", + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_waveforms": { + "default": 1, + "label": "Variations", + "max": 8, + "min": 1, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "audio_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Task: regenerate only the selected source-audio region while treating all audio before and after that interval as immutable. Musical contract: preserve the source tempo, key, meter, chord progression, melodic destination, lyric wording, vocalist identity, instrumentation, groove, and section function. Replace the damaged phrase with a natural alternative performance, not a new composition. Arrangement and timbre: continue the same down-tuned guitar tone, bass articulation, drum-room character, vocal intensity, stereo placement, and density heard immediately around the selection. Let fills and syllables lead causally into the untouched next phrase. Two-boundary integration: match beat phase, pitch, loudness, noise floor, ambience, reverb tail, decay, and transient shape at both the entrance and exit; preserve sufficient pre-roll and release so no note or word is cut unnaturally. Avoid an audible splice, flammed drum attack, phase smear, tempo drift, changed singer, lyric substitution, sudden mix-width change, clipping, silence, or a reverb tail crossing incorrectly into untouched audio." + }, + "reference_audio": { + "display": "input", + "isConnected": false, + "label": "Reference Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "repainting_end": { + "default": 0, + "label": "Repaint End", + "min": 0, + "step": 0.01, + "type": "float", + "value": 10 + }, + "repainting_start": { + "default": 0, + "label": "Repaint Start", + "min": 0, + "step": 0.01, + "type": "float", + "value": 0 + }, + "return_continuation_tail": { + "default": true, + "label": "Return Tail Only", + "type": "bool", + "value": false + }, + "sample_rate": { + "default": 48000, + "label": "Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + }, + "sample_rate_out": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8304 + } + }, + "shift": { + "default": 3, + "display": "slider", + "label": "Shift", + "max": 10, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "source_audio": { + "display": "input", + "isConnected": true, + "label": "Source Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "stable_audio_guidance": { + "default": 7, + "label": "Stable Audio Guidance", + "max": 20, + "min": 0, + "type": "float" + }, + "stable_audio_steps": { + "default": 100, + "label": "Stable Audio Steps", + "max": 300, + "min": 1, + "type": "int" + }, + "task_type": { + "default": "text2music", + "label": "Task", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text2music", + "schemaVersion": 1, + "value": "text2music" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "repaint", + "schemaVersion": 1, + "value": "repaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "continuation", + "schemaVersion": 1, + "value": "continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "extract", + "schemaVersion": 1, + "value": "extract" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "lego", + "schemaVersion": 1, + "value": "lego" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "complete", + "schemaVersion": 1, + "value": "complete" + } + ], + "type": "string", + "value": "repaint" + }, + "timesignature": { + "default": "4", + "label": "Time", + "type": "string", + "value": "4/4" + }, + "vocal_language": { + "default": "en", + "label": "Language", + "type": "string", + "value": "en" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 1320, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Audio", + "description": "Load a generic Diffusers audio pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_audio", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_audio", + "schemaVersion": 1, + "value": "text_to_audio" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_variation", + "schemaVersion": 1, + "value": "audio_variation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_continuation", + "schemaVersion": 1, + "value": "audio_continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_repaint", + "schemaVersion": 1, + "value": "audio_repaint" + } + ], + "type": "string", + "value": "audio_repaint" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "ACE-Step/acestep-v15-xl-turbo-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "audio_diffusion_pipeline" + }, + "pipeline_class": { + "default": "AceStepPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "AceStepPipeline", + "schemaVersion": 1, + "value": "AceStepPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "StableAudioPipeline", + "schemaVersion": 1, + "value": "StableAudioPipeline" + } + ], + "type": "string", + "value": "AceStepPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "200ba991ae448051e14b0183157e35c2d27c9fb0" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-03", + "position": { + "x": 880, + "y": 89 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Audio", + "description": "Load an audio file as a reusable audio object.", + "label": "Load Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "audio": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "channels": { + "display": "output", + "isConnected": false, + "label": "Channels", + "type": "int" + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "audio" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "audio/ace_step_audio_repaint.source_audio_QFidT9.wav" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File", + "type": "str" + }, + "preview": { + "dataSource": "filename", + "display": "ui_audio", + "type": "url" + }, + "sample_rate": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadAudio", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + } + ], + "viewport": { + "x": 138.6310008136697, + "y": 43, + "zoom": 0.4377542717656631 + } +} diff --git a/data/graphs/studio/ace-step-audio-pipeline/audio-variation.json b/data/graphs/studio/ace-step-audio-pipeline/audio-variation.json new file mode 100644 index 0000000..f37d87e --- /dev/null +++ b/data/graphs/studio/ace-step-audio-pipeline/audio-variation.json @@ -0,0 +1,1760 @@ +{ + "edges": [ + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-01", + "targetHandle": "audio", + "type": "default" + }, + { + "className": "category-audio_diffusion_pipeline", + "data": { + "connectionType": "audio_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#E879F9", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "pipeline", + "style": { + "stroke": "#E879F9" + }, + "target": "node-02", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-05", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-03", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-02", + "targetHandle": "source_audio", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "8a72d58c81399c99dce95530bfd40153d03056cb8a7cceb67ace2463a5805048", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Export", + "cache": false, + "category": "Audio", + "description": "Save audio to a WAV file and expose a preview.", + "label": "Export Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "audio": { + "display": "input", + "isConnected": true, + "label": "Audio", + "type": [ + "audio", + "str" + ] + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "file": { + "display": "output", + "isConnected": false, + "label": "File", + "type": "audio" + }, + "filename": { + "default": "{PATH:audio}/MoDiff_{HASH:6}.wav", + "label": "File", + "type": "str" + }, + "preview": { + "dataSource": "file", + "display": "ui_audio", + "type": "url" + }, + "sample_rate": { + "default": 48000, + "label": "Export Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Audio", + "description": "Generate audio with a Diffusers audio pipeline.", + "label": "Diffusers.GenerateAudio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "audio": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "audio_cover_strength": { + "default": 0.85, + "display": "slider", + "label": "Cover Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.75 + }, + "audio_duration": { + "default": 30, + "label": "Duration", + "max": 240, + "min": 1, + "step": 0.5, + "type": "float", + "value": 30 + }, + "bpm": { + "default": 0, + "label": "BPM", + "max": 400, + "min": 0, + "type": "int", + "value": 170 + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "extension_duration": { + "default": 15, + "label": "Extension", + "max": 180, + "min": 1, + "step": 0.5, + "type": "float", + "value": 15 + }, + "guidance_scale": { + "default": 1, + "description": "XL Turbo is guidance-distilled; values above 1 are ignored by the Diffusers pipeline.", + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "keyscale": { + "default": "", + "label": "Key", + "type": "string", + "value": "C# minor" + }, + "lora_scale": { + "default": 1, + "description": "Per-generation ACE-Step LoRA multiplier from Diffusers attention_kwargs.", + "display": "slider", + "label": "LoRA call strength", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float" + }, + "lyrics": { + "default": "", + "display": "textarea", + "label": "Lyrics", + "type": "text", + "value": "[intro]\nSignal waking, low and slow\n\n[verse]\nBlocks ignite beneath the wire\nShape the noise and feed the fire\nImage, motion, sound align\nEvery path becomes design\n\n[chorus]\nMoDiff, move the whole graph now\nBreak it down and build it loud\nRun the chain, let modules shift\nMake the impossible a modular gift\n\n[bridge]\nCut the grid, the low strings climb\n\n[outro]\nMoDiff—lock the final line\n\n[hard stop]" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 8, + "description": "ACE-Step v1.5 XL Turbo is designed for 8 denoising steps.", + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_waveforms": { + "default": 1, + "label": "Variations", + "max": 8, + "min": 1, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "audio_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Task: create a cohesive alternative-metal cover of the supplied source recording, not a loose song with similar mood. Source invariants: preserve the recognizable lead melody, lyric wording and phrase timing, harmonic movement, section order, meter, tempo, and total duration. The source remains the authority if its musical metadata differs from the written brief. Transformation: recast the arrangement with down-tuned seven-string rhythm guitars, articulate pick bass, a tight acoustic metal kit, coarse male lead vocal, and a restrained female scream double only on the main hook. Keep melodic contour recognizable while changing timbre, articulation, voicing, and production weight. Section behavior: let the opening retain space, build density through the verse, make the chorus wider and rhythmically heavier, insert only a short transitional guitar figure where the source permits it, and preserve the source ending cadence rather than adding a new section. Mix and continuity: match source phrase boundaries, keep tempo and pitch stable, center lead elements, spread rhythm guitars, retain intelligible lyrics, and avoid clipping, phase smear, abrupt loudness jumps, or a premature fade." + }, + "reference_audio": { + "display": "input", + "isConnected": false, + "label": "Reference Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "repainting_end": { + "default": 0, + "label": "Repaint End", + "min": 0, + "step": 0.01, + "type": "float", + "value": 10 + }, + "repainting_start": { + "default": 0, + "label": "Repaint Start", + "min": 0, + "step": 0.01, + "type": "float", + "value": 0 + }, + "return_continuation_tail": { + "default": true, + "label": "Return Tail Only", + "type": "bool", + "value": false + }, + "sample_rate": { + "default": 48000, + "label": "Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + }, + "sample_rate_out": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8302 + } + }, + "shift": { + "default": 3, + "display": "slider", + "label": "Shift", + "max": 10, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "source_audio": { + "display": "input", + "isConnected": true, + "label": "Source Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "stable_audio_guidance": { + "default": 7, + "label": "Stable Audio Guidance", + "max": 20, + "min": 0, + "type": "float" + }, + "stable_audio_steps": { + "default": 100, + "label": "Stable Audio Steps", + "max": 300, + "min": 1, + "type": "int" + }, + "task_type": { + "default": "text2music", + "label": "Task", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text2music", + "schemaVersion": 1, + "value": "text2music" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "repaint", + "schemaVersion": 1, + "value": "repaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "continuation", + "schemaVersion": 1, + "value": "continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "extract", + "schemaVersion": 1, + "value": "extract" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "lego", + "schemaVersion": 1, + "value": "lego" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "complete", + "schemaVersion": 1, + "value": "complete" + } + ], + "type": "string", + "value": "cover" + }, + "timesignature": { + "default": "4", + "label": "Time", + "type": "string", + "value": "4/4" + }, + "vocal_language": { + "default": "en", + "label": "Language", + "type": "string", + "value": "en" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 1320, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Audio", + "description": "Load a generic Diffusers audio pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_audio", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_audio", + "schemaVersion": 1, + "value": "text_to_audio" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_variation", + "schemaVersion": 1, + "value": "audio_variation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_continuation", + "schemaVersion": 1, + "value": "audio_continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_repaint", + "schemaVersion": 1, + "value": "audio_repaint" + } + ], + "type": "string", + "value": "audio_variation" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "ACE-Step/acestep-v15-xl-turbo-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "audio_diffusion_pipeline" + }, + "pipeline_class": { + "default": "AceStepPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "AceStepPipeline", + "schemaVersion": 1, + "value": "AceStepPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "StableAudioPipeline", + "schemaVersion": 1, + "value": "StableAudioPipeline" + } + ], + "type": "string", + "value": "AceStepPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "200ba991ae448051e14b0183157e35c2d27c9fb0" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-03", + "position": { + "x": 880, + "y": 89 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Audio", + "description": "Load an audio file as a reusable audio object.", + "label": "Load Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "audio": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "channels": { + "display": "output", + "isConnected": false, + "label": "Channels", + "type": "int" + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "audio" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "audio/ace_step_audio_variation.source_audio_re-mYl.wav" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File", + "type": "str" + }, + "preview": { + "dataSource": "filename", + "display": "ui_audio", + "type": "url" + }, + "sample_rate": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadAudio", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + } + ], + "viewport": { + "x": 138.6310008136697, + "y": 43, + "zoom": 0.4377542717656631 + } +} diff --git a/data/graphs/studio/ace-step-audio-pipeline/text-to-audio--ace-step-chinese-new-year-lora.json b/data/graphs/studio/ace-step-audio-pipeline/text-to-audio--ace-step-chinese-new-year-lora.json new file mode 100644 index 0000000..a85238f --- /dev/null +++ b/data/graphs/studio/ace-step-audio-pipeline/text-to-audio--ace-step-chinese-new-year-lora.json @@ -0,0 +1,1776 @@ +{ + "edges": [ + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-01", + "targetHandle": "audio", + "type": "default" + }, + { + "className": "category-audio_diffusion_pipeline", + "data": { + "connectionType": "audio_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#E879F9", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "pipeline", + "style": { + "stroke": "#E879F9" + }, + "target": "node-06", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-05", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-03", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-audio_diffusion_pipeline", + "data": { + "connectionType": "audio_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#E879F9", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "output", + "style": { + "stroke": "#E879F9" + }, + "target": "node-02", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "a1f413a91149e83d1b28bb991e43609343bdf30f2b06b5bb811ece78a0db2704", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Export", + "cache": false, + "category": "Audio", + "description": "Save audio to a WAV file and expose a preview.", + "label": "Export Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "audio": { + "display": "input", + "isConnected": true, + "label": "Audio", + "type": [ + "audio", + "str" + ] + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "file": { + "display": "output", + "isConnected": false, + "label": "File", + "type": "audio" + }, + "filename": { + "default": "{PATH:audio}/MoDiff_{HASH:6}.wav", + "label": "File", + "type": "str" + }, + "preview": { + "dataSource": "file", + "display": "ui_audio", + "type": "url" + }, + "sample_rate": { + "default": 48000, + "label": "Export Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 2200, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Audio", + "description": "Generate audio with a Diffusers audio pipeline.", + "label": "Diffusers.GenerateAudio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "audio": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "audio_cover_strength": { + "default": 0.85, + "display": "slider", + "label": "Cover Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.5 + }, + "audio_duration": { + "default": 30, + "label": "Duration", + "max": 240, + "min": 1, + "step": 0.5, + "type": "float", + "value": 10 + }, + "bpm": { + "default": 0, + "label": "BPM", + "max": 400, + "min": 0, + "type": "int", + "value": 96 + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "extension_duration": { + "default": 15, + "label": "Extension", + "max": 180, + "min": 1, + "step": 0.5, + "type": "float", + "value": 15 + }, + "guidance_scale": { + "default": 1, + "description": "XL Turbo is guidance-distilled; values above 1 are ignored by the Diffusers pipeline.", + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "keyscale": { + "default": "", + "label": "Key", + "type": "string", + "value": "D major" + }, + "lora_scale": { + "default": 1, + "description": "Per-generation ACE-Step LoRA multiplier from Diffusers attention_kwargs.", + "display": "slider", + "label": "LoRA call strength", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float" + }, + "lyrics": { + "default": "", + "display": "textarea", + "label": "Lyrics", + "type": "text", + "value": "[instrumental]" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 8, + "description": "ACE-Step v1.5 XL Turbo is designed for 8 denoising steps.", + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_waveforms": { + "default": 1, + "label": "Variations", + "max": 8, + "min": 1, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "audio_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Chinese traditional festival instrumental, expressive erhu lead, plucked pipa responses, warm dizi phrases, hand drums and small gongs, elegant pentatonic melody, peaceful opening growing into a joyful New Year procession, acoustic ensemble, natural dynamics, spacious studio recording, detailed balanced mix and clean mastering." + }, + "reference_audio": { + "display": "input", + "isConnected": false, + "label": "Reference Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "repainting_end": { + "default": 0, + "label": "Repaint End", + "min": 0, + "step": 0.01, + "type": "float", + "value": 10 + }, + "repainting_start": { + "default": 0, + "label": "Repaint Start", + "min": 0, + "step": 0.01, + "type": "float", + "value": 0 + }, + "return_continuation_tail": { + "default": true, + "label": "Return Tail Only", + "type": "bool", + "value": false + }, + "sample_rate": { + "default": 48000, + "label": "Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + }, + "sample_rate_out": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 42 + } + }, + "shift": { + "default": 3, + "display": "slider", + "label": "Shift", + "max": 10, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "source_audio": { + "display": "input", + "isConnected": false, + "label": "Source Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "stable_audio_guidance": { + "default": 7, + "label": "Stable Audio Guidance", + "max": 20, + "min": 0, + "type": "float" + }, + "stable_audio_steps": { + "default": 100, + "label": "Stable Audio Steps", + "max": 300, + "min": 1, + "type": "int" + }, + "task_type": { + "default": "text2music", + "label": "Task", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text2music", + "schemaVersion": 1, + "value": "text2music" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "repaint", + "schemaVersion": 1, + "value": "repaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "continuation", + "schemaVersion": 1, + "value": "continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "extract", + "schemaVersion": 1, + "value": "extract" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "lego", + "schemaVersion": 1, + "value": "lego" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "complete", + "schemaVersion": 1, + "value": "complete" + } + ], + "type": "string", + "value": "text2music" + }, + "timesignature": { + "default": "4", + "label": "Time", + "type": "string", + "value": "4/4" + }, + "vocal_language": { + "default": "en", + "label": "Language", + "type": "string", + "value": "en" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 1760, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Audio", + "description": "Load a generic Diffusers audio pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_audio", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_audio", + "schemaVersion": 1, + "value": "text_to_audio" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_variation", + "schemaVersion": 1, + "value": "audio_variation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_continuation", + "schemaVersion": 1, + "value": "audio_continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_repaint", + "schemaVersion": 1, + "value": "audio_repaint" + } + ], + "type": "string", + "value": "text_to_audio" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Runware/acestep-v15-turbo-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "audio_diffusion_pipeline" + }, + "pipeline_class": { + "default": "AceStepPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "AceStepPipeline", + "schemaVersion": 1, + "value": "AceStepPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "StableAudioPipeline", + "schemaVersion": 1, + "value": "StableAudioPipeline" + } + ], + "type": "string", + "value": "AceStepPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "be23effe449c5957947f3020fd63bee23c64abe4" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-03", + "position": { + "x": 880, + "y": 89 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Audio", + "description": "Load an ACE-Step LoRA from MoDiff's managed cache or a local folder.", + "label": "Load Diffusers Audio LoRA", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "adapter_name": { + "default": "audio_style", + "label": "Adapter name", + "type": "string", + "value": "chinese_new_year" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "LoRA", + "type": "string", + "value": { + "source": "hub", + "value": "ACE-Step/ACE-Step-v1.5-chinese-new-year-LoRA" + } + }, + "expected_sha256": { + "default": "", + "label": "Expected SHA-256", + "type": "string", + "value": "78650245c79cbfda7169eae34eb2ccb5f5e639b31a99da0a153a5bbd74194b0d" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "audio_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "audio_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "label": "Replace existing adapters", + "type": "bool", + "value": true + }, + "scale": { + "default": 0.7, + "display": "slider", + "label": "Strength", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "weight_name": { + "default": "adapter_model.safetensors", + "label": "Weight name", + "type": "string", + "value": "adapter_model.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 52.70798403193612, + "zoom": 0.4219560878243513 + } +} diff --git a/data/graphs/studio/ace-step-audio-pipeline/text-to-audio--ace-step-custom-lora.json b/data/graphs/studio/ace-step-audio-pipeline/text-to-audio--ace-step-custom-lora.json new file mode 100644 index 0000000..e6f6d79 --- /dev/null +++ b/data/graphs/studio/ace-step-audio-pipeline/text-to-audio--ace-step-custom-lora.json @@ -0,0 +1,1775 @@ +{ + "edges": [ + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-01", + "targetHandle": "audio", + "type": "default" + }, + { + "className": "category-audio_diffusion_pipeline", + "data": { + "connectionType": "audio_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#E879F9", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "pipeline", + "style": { + "stroke": "#E879F9" + }, + "target": "node-06", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-05", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-03", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-audio_diffusion_pipeline", + "data": { + "connectionType": "audio_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#E879F9", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "output", + "style": { + "stroke": "#E879F9" + }, + "target": "node-02", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "a1f413a91149e83d1b28bb991e43609343bdf30f2b06b5bb811ece78a0db2704", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Export", + "cache": false, + "category": "Audio", + "description": "Save audio to a WAV file and expose a preview.", + "label": "Export Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "audio": { + "display": "input", + "isConnected": true, + "label": "Audio", + "type": [ + "audio", + "str" + ] + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "file": { + "display": "output", + "isConnected": false, + "label": "File", + "type": "audio" + }, + "filename": { + "default": "{PATH:audio}/MoDiff_{HASH:6}.wav", + "label": "File", + "type": "str" + }, + "preview": { + "dataSource": "file", + "display": "ui_audio", + "type": "url" + }, + "sample_rate": { + "default": 48000, + "label": "Export Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 2200, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Audio", + "description": "Generate audio with a Diffusers audio pipeline.", + "label": "Diffusers.GenerateAudio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "audio": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "audio_cover_strength": { + "default": 0.85, + "display": "slider", + "label": "Cover Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.5 + }, + "audio_duration": { + "default": 30, + "label": "Duration", + "max": 240, + "min": 1, + "step": 0.5, + "type": "float", + "value": 20 + }, + "bpm": { + "default": 0, + "label": "BPM", + "max": 400, + "min": 0, + "type": "int", + "value": 92 + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "extension_duration": { + "default": 15, + "label": "Extension", + "max": 180, + "min": 1, + "step": 0.5, + "type": "float", + "value": 15 + }, + "guidance_scale": { + "default": 1, + "description": "XL Turbo is guidance-distilled; values above 1 are ignored by the Diffusers pipeline.", + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "keyscale": { + "default": "", + "label": "Key", + "type": "string", + "value": "A minor" + }, + "lora_scale": { + "default": 1, + "description": "Per-generation ACE-Step LoRA multiplier from Diffusers attention_kwargs.", + "display": "slider", + "label": "LoRA call strength", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float" + }, + "lyrics": { + "default": "", + "display": "textarea", + "label": "Lyrics", + "type": "text", + "value": "[instrumental]" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 8, + "description": "ACE-Step v1.5 XL Turbo is designed for 8 denoising steps.", + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_waveforms": { + "default": 1, + "label": "Variations", + "max": 8, + "min": 1, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "audio_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Original cinematic folk song shaped by the selected personal style adapter, intimate lead vocal, fingerpicked acoustic guitar, bowed strings entering in the chorus, restrained hand percussion, clear verse and chorus structure, natural performance timing, detailed balanced mix." + }, + "reference_audio": { + "display": "input", + "isConnected": false, + "label": "Reference Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "repainting_end": { + "default": 0, + "label": "Repaint End", + "min": 0, + "step": 0.01, + "type": "float", + "value": 10 + }, + "repainting_start": { + "default": 0, + "label": "Repaint Start", + "min": 0, + "step": 0.01, + "type": "float", + "value": 0 + }, + "return_continuation_tail": { + "default": true, + "label": "Return Tail Only", + "type": "bool", + "value": false + }, + "sample_rate": { + "default": 48000, + "label": "Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + }, + "sample_rate_out": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8451 + } + }, + "shift": { + "default": 3, + "display": "slider", + "label": "Shift", + "max": 10, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "source_audio": { + "display": "input", + "isConnected": false, + "label": "Source Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "stable_audio_guidance": { + "default": 7, + "label": "Stable Audio Guidance", + "max": 20, + "min": 0, + "type": "float" + }, + "stable_audio_steps": { + "default": 100, + "label": "Stable Audio Steps", + "max": 300, + "min": 1, + "type": "int" + }, + "task_type": { + "default": "text2music", + "label": "Task", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text2music", + "schemaVersion": 1, + "value": "text2music" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "repaint", + "schemaVersion": 1, + "value": "repaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "continuation", + "schemaVersion": 1, + "value": "continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "extract", + "schemaVersion": 1, + "value": "extract" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "lego", + "schemaVersion": 1, + "value": "lego" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "complete", + "schemaVersion": 1, + "value": "complete" + } + ], + "type": "string", + "value": "text2music" + }, + "timesignature": { + "default": "4", + "label": "Time", + "type": "string", + "value": "4/4" + }, + "vocal_language": { + "default": "en", + "label": "Language", + "type": "string", + "value": "en" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 1760, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Audio", + "description": "Load a generic Diffusers audio pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_audio", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_audio", + "schemaVersion": 1, + "value": "text_to_audio" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_variation", + "schemaVersion": 1, + "value": "audio_variation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_continuation", + "schemaVersion": 1, + "value": "audio_continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_repaint", + "schemaVersion": 1, + "value": "audio_repaint" + } + ], + "type": "string", + "value": "text_to_audio" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Runware/acestep-v15-turbo-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "audio_diffusion_pipeline" + }, + "pipeline_class": { + "default": "AceStepPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "AceStepPipeline", + "schemaVersion": 1, + "value": "AceStepPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "StableAudioPipeline", + "schemaVersion": 1, + "value": "StableAudioPipeline" + } + ], + "type": "string", + "value": "AceStepPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "be23effe449c5957947f3020fd63bee23c64abe4" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-03", + "position": { + "x": 880, + "y": 89 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Audio", + "description": "Load an ACE-Step LoRA from MoDiff's managed cache or a local folder.", + "label": "Load Diffusers Audio LoRA", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "adapter_name": { + "default": "audio_style", + "label": "Adapter name", + "type": "string", + "value": "my_style" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "LoRA", + "type": "string", + "value": { + "source": "local", + "value": "loras/ace-step/my-style" + } + }, + "expected_sha256": { + "default": "", + "label": "Expected SHA-256", + "type": "string" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "audio_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "audio_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "label": "Replace existing adapters", + "type": "bool", + "value": true + }, + "scale": { + "default": 0.7, + "display": "slider", + "label": "Strength", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.7 + }, + "weight_name": { + "default": "adapter_model.safetensors", + "label": "Weight name", + "type": "string", + "value": "adapter_model.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 52.70798403193612, + "zoom": 0.4219560878243513 + } +} diff --git a/data/graphs/studio/ace-step-audio-pipeline/text-to-audio.json b/data/graphs/studio/ace-step-audio-pipeline/text-to-audio.json new file mode 100644 index 0000000..a46fb33 --- /dev/null +++ b/data/graphs/studio/ace-step-audio-pipeline/text-to-audio.json @@ -0,0 +1,1657 @@ +{ + "edges": [ + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-01", + "targetHandle": "audio", + "type": "default" + }, + { + "className": "category-audio_diffusion_pipeline", + "data": { + "connectionType": "audio_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#E879F9", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "pipeline", + "style": { + "stroke": "#E879F9" + }, + "target": "node-02", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-05", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-03", + "targetHandle": "execution_recipe", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "5aa395fdc16922fddfa56b594b1ec0710943c97be93094690c170185d4b0b8e9", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Export", + "cache": false, + "category": "Audio", + "description": "Save audio to a WAV file and expose a preview.", + "label": "Export Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "audio": { + "display": "input", + "isConnected": true, + "label": "Audio", + "type": [ + "audio", + "str" + ] + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "file": { + "display": "output", + "isConnected": false, + "label": "File", + "type": "audio" + }, + "filename": { + "default": "{PATH:audio}/MoDiff_{HASH:6}.wav", + "label": "File", + "type": "str" + }, + "preview": { + "dataSource": "file", + "display": "ui_audio", + "type": "url" + }, + "sample_rate": { + "default": 48000, + "label": "Export Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Audio", + "description": "Generate audio with a Diffusers audio pipeline.", + "label": "Diffusers.GenerateAudio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "audio": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "audio_cover_strength": { + "default": 0.85, + "display": "slider", + "label": "Cover Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.5 + }, + "audio_duration": { + "default": 30, + "label": "Duration", + "max": 240, + "min": 1, + "step": 0.5, + "type": "float", + "value": 75 + }, + "bpm": { + "default": 0, + "label": "BPM", + "max": 400, + "min": 0, + "type": "int", + "value": 170 + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "extension_duration": { + "default": 15, + "label": "Extension", + "max": 180, + "min": 1, + "step": 0.5, + "type": "float", + "value": 15 + }, + "guidance_scale": { + "default": 1, + "description": "XL Turbo is guidance-distilled; values above 1 are ignored by the Diffusers pipeline.", + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "keyscale": { + "default": "", + "label": "Key", + "type": "string", + "value": "C# minor" + }, + "lora_scale": { + "default": 1, + "description": "Per-generation ACE-Step LoRA multiplier from Diffusers attention_kwargs.", + "display": "slider", + "label": "LoRA call strength", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float" + }, + "lyrics": { + "default": "", + "display": "textarea", + "label": "Lyrics", + "type": "text", + "value": "[Intro]\nSignal waking, low and slow\n\n[Verse]\nBlocks ignite beneath the wire\nShape the noise and feed the fire\nImage, motion, sound align\nEvery path becomes design\nHold the pulse in groups of three\nBuild the chain and set it free\n\n[Pre-Chorus]\nOne by one the modules rise\nPressure climbing through the lines\n\n[Chorus]\nMoDiff, move the whole graph now\nBreak it down and build it loud\nRun the chain, let modules shift\nMake the impossible a modular gift\n\n[Bridge]\n\n[Chorus]\nMoDiff, move the whole graph now\nEvery signal ringing out\nRun the chain, let modules shift\nMake the impossible a modular gift\n\n[Outro]\nMoDiff—lock the final line" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 8, + "description": "ACE-Step v1.5 XL Turbo is designed for 8 denoising steps.", + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_waveforms": { + "default": 1, + "label": "Variations", + "max": 8, + "min": 1, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "audio_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Dark modern alternative metal with nu-metal and djent production. Use an unmistakable triple-meter groove: strong beat one, two lighter quarter-note pulses, and palm-muted riffs resolving in three-beat phrases, never 4/4 or 6/8. Use down-tuned seven-string guitars, pick bass, a tight acoustic metal kit, sparse sub impact, a coarse male lead, and a female scream double only on the chorus. Approximate arrangement: 0-6 seconds, filtered clean arpeggio and reverse texture; 6-23 seconds, restrained verse; 23-32 seconds, tom-led pre-chorus; 32-49 seconds, full chorus with wide guitars, triple-meter double-kick, riff-locked bass, and layered hook; 49-57 seconds, tapping bridge and one tom fill; 57-72 seconds, strongest final chorus; 72-75 seconds, short closing tag and unified hard stop. Keep precise attacks, natural pick noise, human drum velocity, intelligible aggression, centered kick, snare, bass, and lead, wide guitars and backing scream, punchy low mids, restrained cymbals, a short dark room, and clean headroom. Band and vocal stop on one transient with no fade, trailing silence, extra outro, clipping, or artist imitation." + }, + "reference_audio": { + "display": "input", + "isConnected": false, + "label": "Reference Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "repainting_end": { + "default": 0, + "label": "Repaint End", + "min": 0, + "step": 0.01, + "type": "float", + "value": 10 + }, + "repainting_start": { + "default": 0, + "label": "Repaint Start", + "min": 0, + "step": 0.01, + "type": "float", + "value": 0 + }, + "return_continuation_tail": { + "default": true, + "label": "Return Tail Only", + "type": "bool", + "value": false + }, + "sample_rate": { + "default": 48000, + "label": "Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int", + "value": 48000 + }, + "sample_rate_out": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8301 + } + }, + "shift": { + "default": 3, + "display": "slider", + "label": "Shift", + "max": 10, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "source_audio": { + "display": "input", + "isConnected": false, + "label": "Source Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "stable_audio_guidance": { + "default": 7, + "label": "Stable Audio Guidance", + "max": 20, + "min": 0, + "type": "float" + }, + "stable_audio_steps": { + "default": 100, + "label": "Stable Audio Steps", + "max": 300, + "min": 1, + "type": "int" + }, + "task_type": { + "default": "text2music", + "label": "Task", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text2music", + "schemaVersion": 1, + "value": "text2music" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "repaint", + "schemaVersion": 1, + "value": "repaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "continuation", + "schemaVersion": 1, + "value": "continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "extract", + "schemaVersion": 1, + "value": "extract" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "lego", + "schemaVersion": 1, + "value": "lego" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "complete", + "schemaVersion": 1, + "value": "complete" + } + ], + "type": "string", + "value": "text2music" + }, + "timesignature": { + "default": "4", + "label": "Time", + "type": "string", + "value": "3" + }, + "vocal_language": { + "default": "en", + "label": "Language", + "type": "string", + "value": "en" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 1320, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Audio", + "description": "Load a generic Diffusers audio pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_audio", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_audio", + "schemaVersion": 1, + "value": "text_to_audio" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_variation", + "schemaVersion": 1, + "value": "audio_variation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_continuation", + "schemaVersion": 1, + "value": "audio_continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_repaint", + "schemaVersion": 1, + "value": "audio_repaint" + } + ], + "type": "string", + "value": "text_to_audio" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "ACE-Step/acestep-v15-xl-turbo-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "audio_diffusion_pipeline" + }, + "pipeline_class": { + "default": "AceStepPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "AceStepPipeline", + "schemaVersion": 1, + "value": "AceStepPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "StableAudioPipeline", + "schemaVersion": 1, + "value": "StableAudioPipeline" + } + ], + "type": "string", + "value": "AceStepPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "200ba991ae448051e14b0183157e35c2d27c9fb0" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "audioPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-03", + "position": { + "x": 880, + "y": 89 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + } + ], + "viewport": { + "x": 165.55288852725795, + "y": 43, + "zoom": 0.4377542717656631 + } +} diff --git a/data/graphs/studio/flux-canny-pipeline/control-image.json b/data/graphs/studio/flux-canny-pipeline/control-image.json new file mode 100644 index 0000000..f866c17 --- /dev/null +++ b/data/graphs/studio/flux-canny-pipeline/control-image.json @@ -0,0 +1,1915 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "control_image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "bb40c6ef87afd7d2a24a3237b9d607bde370bdd97d59f20d67b40fba1e2c1d4a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "ControlGenerate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate from a control image with a Diffusers image pipeline.", + "label": "Diffusers.Control", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "control_image": { + "display": "input", + "isConnected": true, + "label": "Control Image", + "required": true, + "type": "image" + }, + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 30 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 50 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Control contract: strictly follow the supplied Canny control image as the authoritative architecture for massing, roofline, facade divisions, openings, stairs, perspective, horizon, and primary edges; do not move, omit, or invent major structural lines. Appearance brief: render the controlled structure as a compact coastal research pavilion built from board-formed concrete, low-iron glass, dark bronze mullions, weathered timber soffits, and a shallow reflecting pool. Add only scale-appropriate doors, railings, interior desks, dune grass, and one distant person where the control geometry permits. Camera and light: retain the control viewpoint and vanishing lines, wide 32 mm architectural lens, late-afternoon overcast key with a warm break near the horizon, soft interior practicals, grounded environmental shadows, realistic glass reflections, subtle sea haze, restrained gray-bronze-amber palette, and crisp material microtexture. Output must honor the edge map while remaining photographic: no warped facade, shifted windows, extra floor, broken stairs, ignored control lines, cluttered foreground, impossible reflection, flat light, blur, or rendered labels." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8405 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageControl", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "control_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-Canny-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxControlPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "27c3d8bdc17509b47cf4fd9ba25ab1c7508a69a2" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": "images/flux_control_canny.control_image_g40cSP.png" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + } + ], + "viewport": { + "x": 195.52286482851378, + "y": 43, + "zoom": 0.3618022864828514 + } +} diff --git a/data/graphs/studio/flux-depth-pipeline/control-image.json b/data/graphs/studio/flux-depth-pipeline/control-image.json new file mode 100644 index 0000000..56b6e2c --- /dev/null +++ b/data/graphs/studio/flux-depth-pipeline/control-image.json @@ -0,0 +1,1915 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "control_image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "bb40c6ef87afd7d2a24a3237b9d607bde370bdd97d59f20d67b40fba1e2c1d4a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "ControlGenerate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate from a control image with a Diffusers image pipeline.", + "label": "Diffusers.Control", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "control_image": { + "display": "input", + "isConnected": true, + "label": "Control Image", + "required": true, + "type": "image" + }, + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 30 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 50 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Strictly follow the supplied depth map as authoritative for the camera, boat-repair shed geometry, timber ribs, workbench positions, skiff hull, open doorway, floor plane and occlusion. Render a real working wooden-boat repair shed on an overcast morning: one partially restored clinker-built fishing skiff resting securely on two timber trestles, old structural framing, steel hand tools, two naturally coiled ropes, scattered sawdust and cool daylight entering through the open doors. Preserve every major depth boundary and vanishing line while adding only scale-compatible construction detail. Use natural documentary exposure, believable worn timber, oxidized metal, fibrous rope, grounded furniture and directional floor shadows." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8409 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageControl", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "control_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-Depth-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxControlPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "fb5e9b1bae41b8c8adcea4ea2a87b74dd298f07a" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": "images/flux_depth_control.control_image_gdUEj_.png" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + } + ], + "viewport": { + "x": 0, + "y": 0, + "zoom": 1 + } +} diff --git a/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-cinematic-octane-3d.json b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-cinematic-octane-3d.json new file mode 100644 index 0000000..1e8c4ec --- /dev/null +++ b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-cinematic-octane-3d.json @@ -0,0 +1,2016 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-06", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "ee0adeffc971a3e0b2f87422d3433464f3a4ce3657c57b8943d1b502017714ee", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 24 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "cinematic_octane, 3D Portrait, 3d render of one original near-future deep-sea salvage engineer standing inside a compact pressure-dock airlock. Show a weathered woman in her late thirties wearing a graphite diving suit with an oxidized-brass pressure collar, one transparent helmet carried under her left arm, a small scar through the right eyebrow, damp short black hair, and no logo or lettering. Frame a vertical waist-up 50 mm portrait from slightly below eye level. Place the engineer on the right third, with a circular steel hatch, wet cable conduits, one amber maintenance lamp and a glimpse of dark ocean through a thick round window behind her. Use cinematic Octane-style physically based materials, ray-traced reflections, restrained volumetric haze, crisp suit microtexture, realistic skin, a cool cyan environment key, warm amber rim light and deep but readable contrast. Keep the low-strength 3D appearance subtle: premium cinematic character visualization rather than plastic toy styling. No extra person, duplicate limb, deformed hand, helmet on the head, floating equipment, readable text, watermark or franchise character." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 9371 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 768 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 2200, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string", + "value": "render_3d" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "prithivMLmods/3D-Render-Flux-LoRA" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "64e2788c9d236a3e5f62323baa8853116e2a9c7df41cd2a048141eb22cd46d89" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": false + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 0.18 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "3D_Portrait.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter:render_3d", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string", + "value": "cinematic_octane" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "aixonlab/FLUX.1-dev-LoRA-Cinematic-Octane" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "cad317378978ba03438c9f00a4fa5ef0628c4a65937c69b8420feaee5e780f81" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": true + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "cinematic-octane.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 2640, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 152.41982346450902, + "zoom": 0.38874586244942994 + } +} diff --git a/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-film-noir.json b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-film-noir.json new file mode 100644 index 0000000..9bc8e57 --- /dev/null +++ b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-film-noir.json @@ -0,0 +1,1895 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "c59246fe4bfd6442dd261622acc38dfebeb6f72ee7167090a10531826563fe1a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "One exhausted night-shift radio operator discovers an urgent handwritten warning emerging from a teletype machine in an empty 1940s newsroom, one hand stopping above the paper while the other reaches for a black telephone, rain streaking the tall window and a clock approaching midnight, low Dutch camera angle, hard venetian-blind shadows, cigarette haze without a visible smoker, in the style of FLMNR, high contrast black and white, one clear dramatic decision, no modern device, logo or watermark." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8452 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string", + "value": "film_noir" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "dvyio/flux-lora-film-noir" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "2970393ce5376e982a594808c1ff0a87f9cec5ddc0da2c094d2a4305d4079324" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": true + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 1 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "5ee2c3c6409f4618a134b883da64d04e_pytorch_lora_weights.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 127.2050681431005, + "zoom": 0.45017035775127767 + } +} diff --git a/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-ghibli-story.json b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-ghibli-story.json new file mode 100644 index 0000000..15fe992 --- /dev/null +++ b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-ghibli-story.json @@ -0,0 +1,1895 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "c59246fe4bfd6442dd261622acc38dfebeb6f72ee7167090a10531826563fe1a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 768 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 30 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Ghibli style original-character story illustration on a broad rural station platform safely separated from the railway. Show two complete travelers together in the foreground: a young bicycle courier with a short chestnut bob, round brass goggles, teal rain cape, bicycle, and one red travel satchel; beside her, a visibly elderly woman station keeper with silver-gray hair, a wrinkled kind face, forest-green uniform, and her own smaller red travel case. They pause together before departure with warm, familiar expressions. Keep both people, both bags, and the entire bicycle on the platform, never on the rails. A small cream local train is stopped behind them across the platform edge. Lush mossy forest after rain, warm late-afternoon light, hand-painted cel-animation background, no existing franchise character, logo, lettering or watermark." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 9361 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string", + "value": "ghibli_style" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "alvarobartt/ghibli-characters-flux-lora" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "5216bd7eeb12bf6f18cd5d40cb090831796b28aca7446577d08c5a7e4a09dc63" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": true + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "ghibli-characters-flux-lora.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 127.2050681431005, + "zoom": 0.45017035775127767 + } +} diff --git a/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-oil-painting.json b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-oil-painting.json new file mode 100644 index 0000000..b7d240b --- /dev/null +++ b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-oil-painting.json @@ -0,0 +1,1895 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "c59246fe4bfd6442dd261622acc38dfebeb6f72ee7167090a10531826563fe1a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "A realistic oil painting of one weathered lighthouse keeper carrying a brass storm lantern along a cliff path at dusk, the lighthouse beam beginning to sweep across a darkening sea while a squall approaches beyond the headland. Large simple composition, one complete figure, wind-bent grass, restrained earth and ultramarine palette, strong textured brushwork, soft distant background, believable anatomy and a clear journey toward the lit doorway." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8451 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string", + "value": "oil_painting" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "dtthanh/flux_oil_painting_lora" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "6de4e6d451ad7690db7185cf84235bf9c15c80aaeb69793fb6647fab62bdd704" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": true + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 0.85 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "flux-oilpainting1.3-00001.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 127.2050681431005, + "zoom": 0.45017035775127767 + } +} diff --git a/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-paper-cutout.json b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-paper-cutout.json new file mode 100644 index 0000000..3f2ddd5 --- /dev/null +++ b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-paper-cutout.json @@ -0,0 +1,1895 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "c59246fe4bfd6442dd261622acc38dfebeb6f72ee7167090a10531826563fe1a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "An original folktale scene of a small village astronomer climbing a moonlit hill to relight a fallen star lantern before dawn, layered midnight-blue hills, amber village windows, silver clouds and one winding path built from visibly cut textured paper with raised edges and soft cast shadows, Paper Cutout Style, coherent foreground middle ground and sky layers, simple readable silhouette, no existing character, text, logo or plastic 3D render." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8455 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string", + "value": "paper_cutout" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "Norod78/Flux_1_Dev_LoRA_Paper-Cutout-Style" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "1863f382199698b8b756d98ecd8060698d5928ed30009cd16f0531f142e4057b" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": true + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 0.9 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "Flux_1_Dev_LoRA_Paper-Cutout-Style.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 127.2050681431005, + "zoom": 0.45017035775127767 + } +} diff --git a/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-photoreal-documentary.json b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-photoreal-documentary.json new file mode 100644 index 0000000..b3770e6 --- /dev/null +++ b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-photoreal-documentary.json @@ -0,0 +1,1895 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "c59246fe4bfd6442dd261622acc38dfebeb6f72ee7167090a10531826563fe1a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "A natural documentary photograph of a rural veterinarian examining an injured tawny owl inside a working wildlife rescue clinic at dawn. Show one veterinarian in plain navy scrubs supporting the owl with both gloved hands while a rehabilitator adjusts one small examination lamp, stainless worktable, folded cotton towel, transport crate and rain-dark trees visible through the window. Eye-level 50 mm lens, practical window and lamp light, realistic skin, feathers, fabric and metal, restrained color, believable anatomy and hand contact, no glamour pose, illustration, CGI, logo, text or duplicated animal." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8456 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string", + "value": "photoreal" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "XLabs-AI/flux-RealismLora" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "0a83a924b822b70b5e458d27935ebfa7713edaee04ff9f194209525354031eca" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": true + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 0.85 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "lora.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 127.2050681431005, + "zoom": 0.45017035775127767 + } +} diff --git a/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-retro-comic.json b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-retro-comic.json new file mode 100644 index 0000000..18aaecf --- /dev/null +++ b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-retro-comic.json @@ -0,0 +1,1895 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "c59246fe4bfd6442dd261622acc38dfebeb6f72ee7167090a10531826563fe1a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "c0m1c style vintage 1930s comic strip panel on a city-hall rooftop: one night watchman yanks down a red emergency lever beside an enormous brass alarm bell as a meteor streaks high across the open sky toward the sleeping city below. Show the lever, bell, watchman and distant meteor as four clearly separate objects with readable cause and effect. Include exactly one large speech balloon. Inside it print exactly these three uppercase words, including the middle word: “WAKE THE CITY!” The balloon must read WAKE THE CITY!, not WAKE CITY, and no other text may appear. Bold ink contours, limited vermilion teal and cream printing, coarse halftone dots, dramatic diagonal composition, aged paper edges, no telescope, cannon, firearm, beam, extra panel, duplicated limb or watermark." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 9362 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string", + "value": "retro_comic" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "renderartist/retrocomicflux" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "af31beee9ea67955d36425f25624d2585fa31271680013e5c9731690aeb78f9d" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": true + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 0.9 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "Retro_Comic_Flux_v2_renderartist.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 127.2050681431005, + "zoom": 0.45017035775127767 + } +} diff --git a/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-watercolor.json b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-watercolor.json new file mode 100644 index 0000000..812563f --- /dev/null +++ b/data/graphs/studio/flux-dev-pipeline/text-to-image--flux-lora-watercolor.json @@ -0,0 +1,1895 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "c59246fe4bfd6442dd261622acc38dfebeb6f72ee7167090a10531826563fe1a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "AQUACOLTOK watercolor painting on clean white cotton paper with exactly two complete people. On the near half of a narrow footbridge, one mountain trail engineer kneels and secures the final plank. On the far bank, one hiker stands and waits safely, fully visible and clearly separate from the engineer. Show the bridge connecting both banks, a turquoise stream below, one red survey flag and one compact tool satchel beside the engineer, and pale alpine peaks dissolving into mist. Transparent pigment blooms, confident indigo and burnt-sienna linework, generous unpainted margins, clear cause and effect, no extra people, lettering, logo or photorealistic rendering." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 9363 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string", + "value": "watercolor" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "SebastianBodza/Flux_Aquarell_Watercolor_v2" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "e63e44417df35456f425329ad4334143a8bc9fd10316345730ade434106b050e" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": true + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 0.9 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "lora.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 127.2050681431005, + "zoom": 0.45017035775127767 + } +} diff --git a/data/graphs/studio/flux-dev-pipeline/text-to-image.json b/data/graphs/studio/flux-dev-pipeline/text-to-image.json new file mode 100644 index 0000000..ce1bd9c --- /dev/null +++ b/data/graphs/studio/flux-dev-pipeline/text-to-image.json @@ -0,0 +1,1774 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-05", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "897ca20c9ef2207a48a0ae2cdd5ac826206919e35cba73e0f159296038fdafbe", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Create a high-end documentary photograph inside a working coastal aircraft-restoration hangar during a winter rainstorm. Show one experienced mechanic inspecting the exposed radial engine of a 1940s aluminum seaplane. Frame the aircraft nose, engine, near wing root and both pontoons while the tail continues naturally outside the square crop. The aircraft rests on locked maintenance stands with both pontoons grounded, and every hose, cylinder, fastener and tool has believable mechanical scale and attachment. Compose a wide chest-height 35 mm view through a partially open hangar door, with rain streaks and the gray harbor outside, the mechanic and engine on the right third, a rolling tool chest and oil-stained work mat in the foreground, and timber roof trusses receding into depth. Preserve natural anatomy, safe working posture and clear spatial layers. Use cool storm daylight balanced by warm overhead shop lamps, realistic oxidized aluminum, wet concrete reflections, worn cotton coveralls, restrained color and fine editorial grain. Keep the fuselage completely unlettered: no decal, serial, writing, emblem, symbol or logo. No floating parts, invented aircraft geometry, duplicate person, plastic surface or theatrical glow." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8402 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 83.75249868490266, + "zoom": 0.5560231457127828 + } +} diff --git a/data/graphs/studio/flux-fill-pipeline/inpaint.json b/data/graphs/studio/flux-fill-pipeline/inpaint.json new file mode 100644 index 0000000..c7362e4 --- /dev/null +++ b/data/graphs/studio/flux-fill-pipeline/inpaint.json @@ -0,0 +1,2051 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "mask_image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "a6b91f0765a5f94bee618a8d5a931c2f3a0094f0efa818492ab5cfc093b53765", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Inpaint", + "cache": false, + "category": "Diffusers Image", + "description": "Inpaint or fill with a Diffusers image pipeline.", + "label": "Diffusers.Inpaint", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 30 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "image": { + "description": "A single source image or a list of references for pipelines that support multi-reference editing.", + "display": "input", + "isConnected": true, + "label": "Image or references", + "required": true, + "type": "image" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "mask_image": { + "display": "input", + "isConnected": true, + "label": "Mask", + "required": true, + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 50 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Mask contract: replace only the masked source region with a compact walnut-and-brass tabletop radio; preserve all unmasked people, hands, props, typography, background geometry, camera framing, and source pixels conceptually unchanged. Replacement design: low rectangular smoked-walnut cabinet, woven charcoal speaker cloth, two knurled brass dials, one narrow amber frequency scale without readable station text, small rubber feet, and a believable size relative to adjacent objects. Integration: continue any hidden tabletop and background through the old object, match local lens perspective, horizon, focus plane, texture scale, grain, and exposure, then ground the radio with correct contact shadow, cast-shadow softness, nearby color spill, reflections, and foreground occlusion. Use the full mask without leaking across its boundary. Result: one seamless source photograph with no halo, pasted edge, floating base, repeated texture, warped radio, extra knobs, changed unmasked content, mismatched lighting, or low-detail fill." + }, + "reference_strength": { + "default": 1, + "description": "Relative influence of every reference after the first composition anchor, when supported by the selected adapter.", + "label": "Secondary reference strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8404 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageInpaint", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 220 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "inpaint" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-Fill-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxFillPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "358293da0354175698b67ec8299acf928313a78a" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 234 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 201 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/flux_fill_inpaint.reference_image_1_4ClGFo.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "remove alpha" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": "images/flux_fill_inpaint.mask_image_1np_gG.png" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadMask", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 0, + "y": 720 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 1760, + "y": 374 + }, + "type": "custom" + } + ], + "viewport": { + "x": 325.868684569868, + "y": 43, + "zoom": 0.24487938097405554 + } +} diff --git a/data/graphs/studio/flux-fill-pipeline/outpaint.json b/data/graphs/studio/flux-fill-pipeline/outpaint.json new file mode 100644 index 0000000..a15d033 --- /dev/null +++ b/data/graphs/studio/flux-fill-pipeline/outpaint.json @@ -0,0 +1,2051 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "mask_image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "a6b91f0765a5f94bee618a8d5a931c2f3a0094f0efa818492ab5cfc093b53765", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Inpaint", + "cache": false, + "category": "Diffusers Image", + "description": "Inpaint or fill with a Diffusers image pipeline.", + "label": "Diffusers.Inpaint", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 30 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "image": { + "description": "A single source image or a list of references for pipelines that support multi-reference editing.", + "display": "input", + "isConnected": true, + "label": "Image or references", + "required": true, + "type": "image" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "mask_image": { + "display": "input", + "isConnected": true, + "label": "Mask", + "required": true, + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 50 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Outpaint only beyond the supplied source boundaries to widen the same documentary photograph of a traditional bookbinder at a workbench. Keep the complete original center image, hands, open book, bone folder, linen thread, cutting mat, bench edge, camera, exposure and focus conceptually unchanged. Continue the real workshop at both sides with asymmetric shelves of paper, one small cast-iron book press, cropped binding hand tools and natural window falloff, all at correct 50 mm perspective and scale. Match wood grain, paper texture, shadow direction, film grain and color temperature across both seams. Deliver one continuous wide photograph with the single original worker and naturally varied workshop detail." + }, + "reference_strength": { + "default": 1, + "description": "Relative influence of every reference after the first composition anchor, when supported by the selected adapter.", + "label": "Secondary reference strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8408 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1536 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageInpaint", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 220 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "outpaint" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-Fill-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxFillPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "358293da0354175698b67ec8299acf928313a78a" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 234 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 201 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/flux_fill_outpaint.reference_image_1_tKvQoS.png" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "remove alpha" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": "images/flux_fill_outpaint.mask_image_9DuFpE.png" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadMask", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 0, + "y": 720 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 1760, + "y": 374 + }, + "type": "custom" + } + ], + "viewport": { + "x": 0, + "y": 0, + "zoom": 1 + } +} diff --git a/data/graphs/studio/flux-kontext-pipeline/edit-image.json b/data/graphs/studio/flux-kontext-pipeline/edit-image.json new file mode 100644 index 0000000..b73ec1d --- /dev/null +++ b/data/graphs/studio/flux-kontext-pipeline/edit-image.json @@ -0,0 +1,1928 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "0ecf669f3112afec77f566eca45f6905b3028355280c0ef3fccfe5e83f26f501", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Edit", + "cache": false, + "category": "Diffusers Image", + "description": "Edit an image with a Diffusers image pipeline.", + "label": "Diffusers.Edit", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "image": { + "description": "A single source image or a list of references for pipelines that support multi-reference editing.", + "display": "input", + "isConnected": true, + "label": "Image or references", + "required": true, + "type": "image" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Edit the source photo. Keep the bottle outline, pump, label, exact ASTERMIST and LAVENDER SLEEP SPRAY text, camera, background, and shadow fixed. Change only the lavender body to brushed graphite metal, clear cap to frosted glass, and silver collar to amber metal. Match light, perspective, grain, reflections, and contact shadow. No shape drift, moved parts, duplicate edges, or misspelled text." + }, + "reference_strength": { + "default": 1, + "description": "Relative influence of every reference after the first composition anchor, when supported by the selected adapter.", + "label": "Secondary reference strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8443 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageEdit", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "edit_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-Kontext-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxKontextPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/flux_kontext_edit.reference_image_1_2xze13.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + } + ], + "viewport": { + "x": 189.01042367182242, + "y": 43, + "zoom": 0.3618022864828514 + } +} diff --git a/data/graphs/studio/flux-kontext-pipeline/multi-image-reference-edit.json b/data/graphs/studio/flux-kontext-pipeline/multi-image-reference-edit.json new file mode 100644 index 0000000..e512b43 --- /dev/null +++ b/data/graphs/studio/flux-kontext-pipeline/multi-image-reference-edit.json @@ -0,0 +1,1929 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "0ecf669f3112afec77f566eca45f6905b3028355280c0ef3fccfe5e83f26f501", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Edit", + "cache": false, + "category": "Diffusers Image", + "description": "Edit an image with a Diffusers image pipeline.", + "label": "Diffusers.Edit", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "image": { + "description": "A single source image or a list of references for pipelines that support multi-reference editing.", + "display": "input", + "isConnected": true, + "label": "Image or references", + "required": true, + "type": "image" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Use the first reference as the fixed ASTERMIST bottle and the second only for cobalt ceramic color, warm studio light, and tactile material. Keep the bottle silhouette, pump, label, exact text, camera, crop, and background fixed. Change only the body to brushed cobalt enamel and collar to champagne aluminum. Match source reflections, grain, focus, and shadow. Do not add the potter, pottery, objects, controls, seams, duplicate edges, or new text." + }, + "reference_strength": { + "default": 1, + "description": "Relative influence of every reference after the first composition anchor, when supported by the selected adapter.", + "label": "Secondary reference strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8407 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageEdit", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "multi_image_reference_edit" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-Kontext-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxKontextPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/flux_kontext_multi_reference.reference_image_1_0u5yjJ.webp", + "images/flux_kontext_multi_reference.reference_image_2_24zP-d.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + } + ], + "viewport": { + "x": 122.43880295897776, + "y": 43, + "zoom": 0.3618022864828514 + } +} diff --git a/data/graphs/studio/flux-krea-pipeline/text-to-image.json b/data/graphs/studio/flux-krea-pipeline/text-to-image.json new file mode 100644 index 0000000..261ee9c --- /dev/null +++ b/data/graphs/studio/flux-krea-pipeline/text-to-image.json @@ -0,0 +1,1776 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-05", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "897ca20c9ef2207a48a0ae2cdd5ac826206919e35cba73e0f159296038fdafbe", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Create a natural editorial portrait of one ceramic artist in a sunlit coastal workshop, hands resting beside a half-finished cobalt vase. Preserve believable anatomy, clay dust, linen texture, wood grain, and an uncluttered silhouette. Compose a waist-up 50 mm view with the artist on the right third, shelves receding softly behind, warm window key from camera left, cool skylight fill, realistic contact shadows, restrained film color, and no rendered text, duplicate hands, plastic skin, warped pottery, or stock-photo staging." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8406 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-Krea-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "8162a9c7b05a641be098422bf2fcf335615c2f28" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 224.28614660390048, + "y": 43, + "zoom": 0.3618022864828514 + } +} diff --git a/data/graphs/studio/flux-redux-pipeline/edit-image.json b/data/graphs/studio/flux-redux-pipeline/edit-image.json new file mode 100644 index 0000000..02558e3 --- /dev/null +++ b/data/graphs/studio/flux-redux-pipeline/edit-image.json @@ -0,0 +1,1928 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "0ecf669f3112afec77f566eca45f6905b3028355280c0ef3fccfe5e83f26f501", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Edit", + "cache": false, + "category": "Diffusers Image", + "description": "Edit an image with a Diffusers image pipeline.", + "label": "Diffusers.Edit", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "image": { + "description": "A single source image or a list of references for pipelines that support multi-reference editing.", + "display": "input", + "isConnected": true, + "label": "Image or references", + "required": true, + "type": "image" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 28 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Create a natural-color documentary variation of the source watchmaker photograph. Use the source as the visual reference for one elderly watchmaker, his hand-held wristwatch, dark wool cardigan, repair bench, parts drawers, north-facing window and camera on the sill. Keep these recognizable visual cues while allowing Redux to create a new coherent composition rather than claiming pixel-exact preservation. Show the watchmaker seated at one bench, examining one complete watch above a shallow tray containing a few brass gears, with tweezers and small steel tools grounded on worn wood. Use honest north-window daylight, realistic skin, wool, glass, brass and timber texture, restrained color, fine reportage grain, natural anatomy, practical object scale and one plausible 50 mm camera perspective." + }, + "reference_strength": { + "default": 1, + "description": "Relative influence of every reference after the first composition anchor, when supported by the selected adapter.", + "label": "Secondary reference strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8410 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageEdit", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "edit_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-Redux-dev" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxReduxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "c95859fbf7703ca4d6824b4da4407d7cd0434f81" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "group_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/flux_redux_edit.reference_image_1_Uynnfa.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + } + ], + "viewport": { + "x": 189.01042367182242, + "y": 43, + "zoom": 0.3618022864828514 + } +} diff --git a/data/graphs/studio/flux-schnell-pipeline/text-to-image.json b/data/graphs/studio/flux-schnell-pipeline/text-to-image.json new file mode 100644 index 0000000..a57ec1e --- /dev/null +++ b/data/graphs/studio/flux-schnell-pipeline/text-to-image.json @@ -0,0 +1,1774 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-05", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "897ca20c9ef2207a48a0ae2cdd5ac826206919e35cba73e0f159296038fdafbe", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 4 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Create a photorealistic editorial landscape photograph of coastal conservation work after a rain shower on a windswept Atlantic headland. Show an experienced dry-stone mason in a mustard rain shell and navy work trousers repairing one storm-damaged field wall. The mason kneels side-on with both gloved hands setting one flat gray slate into a clear gap; the wall is built from irregular local stone laid in stable overlapping courses with visible through-stones and small packing stones, never mortar or impossible balancing rocks. Build a detailed working foreground around the repair: a wooden wheelbarrow holding sorted slate, one canvas tool roll with a mason hammer and two chisels, a taut yellow string line between short stakes, a folded waterproof site plan, muddy boot prints, and separate neat piles of large face stones and small packing stones. Every tool rests naturally and the worker has believable anatomy, grip, weight and ground contact. Use a natural eye-level 35 mm camera from the wet footpath. Let the repaired wall lead diagonally from the near left toward the worker at center, with rough grass, a distant white cottage, layered sea cliffs and gray Atlantic water receding on the right. Keep the horizon level and the perspective documentary rather than heroic. Use cool late-afternoon overcast light with one weak sun break catching wet slate edges, believable water-darkened stone and fabric, restrained reflections, natural gray-green color, fine documentary grain, and no signs, letters, logos or rendered text." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8427 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.1-schnell" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "FluxPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "741f7c3ce8b383c54771c7003378a50191e9efe9" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 83.75249868490266, + "zoom": 0.5560231457127828 + } +} diff --git a/data/graphs/studio/flux2-klein-pipeline/edit-image.json b/data/graphs/studio/flux2-klein-pipeline/edit-image.json new file mode 100644 index 0000000..412e5a4 --- /dev/null +++ b/data/graphs/studio/flux2-klein-pipeline/edit-image.json @@ -0,0 +1,1926 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "0ecf669f3112afec77f566eca45f6905b3028355280c0ef3fccfe5e83f26f501", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Edit", + "cache": false, + "category": "Diffusers Image", + "description": "Edit an image with a Diffusers image pipeline.", + "label": "Diffusers.Edit", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "image": { + "description": "A single source image or a list of references for pipelines that support multi-reference editing.", + "display": "input", + "isConnected": true, + "label": "Image or references", + "required": true, + "type": "image" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 4 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Preserve the source cube geometry, camera angle, crop, limestone slab, background, focus, and light direction. Change only the cobalt glass to transparent emerald green glass with physically consistent transmission, refraction, highlights, and colored spill. Keep every edge and shadow anchored to the original photograph; do not add objects, reshape the cube, move the camera, replace the surface, introduce text, or alter unrelated pixels." + }, + "reference_strength": { + "default": 1, + "description": "Relative influence of every reference after the first composition anchor, when supported by the selected adapter.", + "label": "Secondary reference strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 174 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageEdit", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "edit_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.2-klein-4B" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "Flux2KleinPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/flux2_klein_edit.reference_image_1_5_-LyN.png" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 54.003712296983736, + "zoom": 0.49048723897911833 + } +} diff --git a/data/graphs/studio/flux2-klein-pipeline/multi-image-reference-edit.json b/data/graphs/studio/flux2-klein-pipeline/multi-image-reference-edit.json new file mode 100644 index 0000000..2ebfd6c --- /dev/null +++ b/data/graphs/studio/flux2-klein-pipeline/multi-image-reference-edit.json @@ -0,0 +1,1927 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "0ecf669f3112afec77f566eca45f6905b3028355280c0ef3fccfe5e83f26f501", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Edit", + "cache": false, + "category": "Diffusers Image", + "description": "Edit an image with a Diffusers image pipeline.", + "label": "Diffusers.Edit", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "image": { + "description": "A single source image or a list of references for pipelines that support multi-reference editing.", + "display": "input", + "isConnected": true, + "label": "Image or references", + "required": true, + "type": "image" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 4 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Use the first reference as the exact composition and geometry anchor for one worn hiking boot resting on a damp trail rock. Use the second reference only for indigo woven-canvas texture, rust-orange stitching and dark waxed-leather material evidence. Preserve the single boot silhouette, sole construction, complete lace path, camera, crop, scale, mossy rock, background focus and overcast light. Transform only the existing boot-panel materials: apply the three reference materials coherently with correct weave direction, purposeful seam placement, natural edge wear, firm contact shadow and restrained moisture. Deliver one continuous outdoor product photograph with an unchanged sole and one physically complete boot." + }, + "reference_strength": { + "default": 1, + "description": "Relative influence of every reference after the first composition anchor, when supported by the selected adapter.", + "label": "Secondary reference strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 175 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageEdit", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "multi_image_reference_edit" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.2-klein-4B" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "Flux2KleinPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/flux2_klein_multi_reference.reference_image_1_YbGr_8.webp", + "images/flux2_klein_multi_reference.reference_image_2_45N85G.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 84.93143083630596, + "zoom": 0.4189456995640111 + } +} diff --git a/data/graphs/studio/flux2-klein-pipeline/text-to-image.json b/data/graphs/studio/flux2-klein-pipeline/text-to-image.json new file mode 100644 index 0000000..2e4584c --- /dev/null +++ b/data/graphs/studio/flux2-klein-pipeline/text-to-image.json @@ -0,0 +1,1774 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-05", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "897ca20c9ef2207a48a0ae2cdd5ac826206919e35cba73e0f159296038fdafbe", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 4 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Create an editorial material-study photograph in a sunlit conservation studio. Hero subject: one hand-cast translucent cobalt glass cube with a precise silhouette, subtly irregular thick glass walls, trapped microbubbles, internal refraction, and physically accurate blue caustics. Place it on layered pale limestone and crumpled archival linen, with a cropped brass caliper and color swatch entering the far foreground as supporting scale cues, never competing subjects. Compose a low front three-quarter 70 mm view with the cube on the right third, diagonal late-afternoon window light crossing the table, a long prismatic shadow, cool reflected fill, softly receding plaster shelves, and visible dust motes. Deliver tactile stone grain, glass edge highlights, linen fibers, grounded contact, rich foreground-midground-background depth, and no labels, duplicate cubes, warped edges, blacked-out glass, floating geometry, or sterile white-cyclorama staging." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 173 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "black-forest-labs/FLUX.2-klein-4B" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "Flux2KleinPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "e7b7dc27f91deacad38e78976d1f2b499d76a294" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 83.75249868490266, + "zoom": 0.5560231457127828 + } +} diff --git a/data/graphs/studio/ltxvideo-pipeline/image-to-video.json b/data/graphs/studio/ltxvideo-pipeline/image-to-video.json new file mode 100644 index 0000000..a9ea177 --- /dev/null +++ b/data/graphs/studio/ltxvideo-pipeline/image-to-video.json @@ -0,0 +1,2142 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-07", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "reference_images", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-05", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-04", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-06", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "30ceebd6f17debd419dafcd81155921ed2d2bcc427ecfc10fd755c3f0829ebe3", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "_native_math" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/ltx_video_image_to_video.reference_image_1_yEmtJ0.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 2200, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.7 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.7 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 512 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "image_to_video" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 161 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Ten-second photoreal rally follow-pan from the supplied still. Motion begins immediately: the white classic car accelerates across the foreground and clears frame center before second three, throwing one coherent fan of tire spray. At frame center the roadside camera pans decisively right with the car, sweeping road markings and guardrail posts across more than half the frame, then holds the car as it rapidly recedes around the upper-right bend. Preserve body, four wheels, lamps, road contact and scale. Natural rotation and blur; no hold, collision, morphing or text." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": true, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8302 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.7 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 1 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": false, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 768 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-06", + "position": { + "x": 1320, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Lightricks/LTX-Video-0.9.8-13B-distilled" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "LTXConditionPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "7c64400e1861cc0d7b98d570a1926d5408ec60cd" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-07", + "position": { + "x": 880, + "y": 131 + }, + "type": "custom" + } + ], + "viewport": { + "x": 0, + "y": 0, + "zoom": 1 + } +} diff --git a/data/graphs/studio/ltxvideo-pipeline/reference-to-video.json b/data/graphs/studio/ltxvideo-pipeline/reference-to-video.json new file mode 100644 index 0000000..3fb58ba --- /dev/null +++ b/data/graphs/studio/ltxvideo-pipeline/reference-to-video.json @@ -0,0 +1,2143 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-07", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "reference_images", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-05", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-04", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-06", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "30ceebd6f17debd419dafcd81155921ed2d2bcc427ecfc10fd755c3f0829ebe3", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "_native_math" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/ltx_video_multi_reference.reference_image_1_mzKsDT.webp", + "images/ltx_video_multi_reference.reference_image_2_jTf52D.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 1760, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 2200, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.55 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 512 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "reference_to_video" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 81 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Five-second photoreal stabilized run between the supplied coastal boardwalk keyframes. Move forward immediately and evenly: wet plank seams stream under the lens, opening posts pass behind camera, new right rope posts sweep past, and grass bends left in wind. Preserve one connected walkway, ocean left, cliff right, cold storm light, rain, scale and perspective. Reach the closing view gradually through real parallax. No hold, late jump, dissolve, zoom-only motion, mirrored coast, duplicated posts, people, text, illustration or CGI." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": true, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8304 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.55 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 1 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": false, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 768 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-06", + "position": { + "x": 1320, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Lightricks/LTX-Video-0.9.8-13B-distilled" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "LTXConditionPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "7c64400e1861cc0d7b98d570a1926d5408ec60cd" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-07", + "position": { + "x": 880, + "y": 131 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 94.20571524064172, + "zoom": 0.3532754010695187 + } +} diff --git a/data/graphs/studio/ltxvideo-pipeline/text-to-video.json b/data/graphs/studio/ltxvideo-pipeline/text-to-video.json new file mode 100644 index 0000000..8df4ef6 --- /dev/null +++ b/data/graphs/studio/ltxvideo-pipeline/text-to-video.json @@ -0,0 +1,2008 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-06", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-04", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-03", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "835989de85eaec4a4f81449e7ca8f05d637197c04fa17c66beedc547f86404fc", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "_native_math" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 1760, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 24 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 2200, + "y": 145 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 24 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 512 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "text_to_video" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 121 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Five-second photoreal single take through a mountain train’s front window in rain. The camera moves rapidly forward from frame one above two straight wet rails. Sleepers rush out beneath the lens; close pine trunks and blank signal posts sweep backward past both edges; raindrops streak upward on the glass; a stone tunnel grows steadily ahead. Keep rail spacing, horizon, forest depth and forward direction stable. Natural overcast light. No exterior train, people, station, writing, cut, zoom, warped track, reversal, freeze or animation." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": false, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8301 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 1 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": false, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 768 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Lightricks/LTX-Video-0.9.8-13B-distilled" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "LTXConditionPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "7c64400e1861cc0d7b98d570a1926d5408ec60cd" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-06", + "position": { + "x": 880, + "y": 117 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 47.64279918864099, + "zoom": 0.4288032454361055 + } +} diff --git a/data/graphs/studio/ltxvideo-pipeline/video-to-video.json b/data/graphs/studio/ltxvideo-pipeline/video-to-video.json new file mode 100644 index 0000000..3bb8e9e --- /dev/null +++ b/data/graphs/studio/ltxvideo-pipeline/video-to-video.json @@ -0,0 +1,2247 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-08", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "video", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-04", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "output", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-07", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-06", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-05", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-07", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "807c5952b0659ddefe1d6505e8facd866afe543913844df3cf38bb14e8d7c0f5", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "_native_math" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Video", + "description": "Load a video from a file path.", + "label": "Load Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "video" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "videos/ltx_video_video_to_video.source_video_mlqe-9.mp4" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File Name", + "type": "str" + }, + "fps": { + "display": "output", + "isConnected": false, + "label": "FPS", + "type": "float" + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "label": { + "display": "ui_label", + "value": "Load Video" + }, + "video": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 505 + }, + "type": "custom" + }, + { + "data": { + "action": "Normalize", + "cache": false, + "category": "Video Conditioning", + "description": "Trim, resize, crop, and normalize a video frame list for video models.", + "label": "Normalize Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.VideoConditioning", + "params": { + "fit": { + "default": "cover", + "label": "Fit", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "contain", + "schemaVersion": 1, + "value": "contain" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "stretch", + "schemaVersion": 1, + "value": "stretch" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 512 + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "type": "int", + "value": 81 + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Video", + "type": [ + "video", + "image" + ] + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 768 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "normalizeVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 692 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 318 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.6 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 512 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "video_to_video" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 81 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Photoreal generative remaster of the supplied rocket launch. Improve fine edge clarity, smoke detail, natural tonal separation and stable compression while preserving the exact event. Keep the same rigid white rocket, nose cone, body diameter, vertical trajectory, camera, sky, terrain and plume timing. The rocket rises continuously and stays fully visible. No restyle, extra booster, bent body, duplicate rocket, explosion, altered flight path, writing, logo, flicker, cut or static hold." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": false, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8313 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 1 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 768 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-07", + "position": { + "x": 1320, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Lightricks/LTX-Video-0.9.8-13B-distilled" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "LTXConditionPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "7c64400e1861cc0d7b98d570a1926d5408ec60cd" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-08", + "position": { + "x": 880, + "y": 290 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 57.85003900156005, + "zoom": 0.4122464898595944 + } +} diff --git a/data/graphs/studio/qwen-image-edit-modular-pipeline/edit-image.json b/data/graphs/studio/qwen-image-edit-modular-pipeline/edit-image.json new file mode 100644 index 0000000..b058ce8 --- /dev/null +++ b/data/graphs/studio/qwen-image-edit-modular-pipeline/edit-image.json @@ -0,0 +1,1132 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-latents", + "data": { + "connectionType": "latents" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#6366F1", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "latents", + "style": { + "stroke": "#6366F1" + }, + "target": "node-01", + "targetHandle": "latents", + "type": "default" + }, + { + "className": "category-latents", + "data": { + "connectionType": "latents" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#6366F1", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "image_latents", + "style": { + "stroke": "#6366F1" + }, + "target": "node-02", + "targetHandle": "image_latents", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-03", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "scheduler", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-02", + "targetHandle": "scheduler", + "type": "default" + }, + { + "className": "category-diffusers_auto_models", + "data": { + "connectionType": "diffusers_auto_models" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#8B5CF6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "text_encoders", + "style": { + "stroke": "#8B5CF6" + }, + "target": "node-07", + "targetHandle": "text_encoders", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-08", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "unet_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-02", + "targetHandle": "unet", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-09", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "vae_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-01", + "targetHandle": "vae", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-10", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "vae_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-03", + "targetHandle": "vae", + "type": "default" + }, + { + "className": "category-embeddings", + "data": { + "connectionType": "embeddings" + }, + "edgeType": "default", + "id": "edge-11", + "markerEnd": { + "color": "#FBBF24", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "embeddings", + "style": { + "stroke": "#FBBF24" + }, + "target": "node-02", + "targetHandle": "embeddings", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "4d7ce9be51540b9611aa48cdb1f0c4c0e0fe92c7b2858b49731bd7694d903b02", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "DecodeLatents", + "cache": false, + "category": "sampler", + "description": "", + "label": "Decode Latents", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "latents": { + "display": "input", + "isConnected": true, + "label": "Latents *", + "type": "latents" + }, + "vae": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "VAE *", + "onSignal": "update_node", + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "decode", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 369 + }, + "type": "custom" + }, + { + "data": { + "action": "Denoise", + "cache": false, + "category": "sampler", + "description": "", + "label": "Denoise", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "embeddings": { + "display": "input", + "isConnected": true, + "label": "Text Embeddings *", + "type": "embeddings" + }, + "guidance_scale": { + "default": 4, + "display": "slider", + "hidden": false, + "label": "Guidance Scale", + "max": 30, + "min": 1, + "step": 0.1, + "type": "float", + "value": 4 + }, + "guider": { + "display": "input", + "isConnected": false, + "label": "Guider", + "onChange": { + "false": [ + "guidance_scale" + ], + "true": [] + }, + "signal": { + "direction": "input", + "origin": "unet" + }, + "type": "custom_guider" + }, + "image_latents": { + "display": "input", + "isConnected": true, + "label": "Image Latents *", + "type": "latents" + }, + "latents": { + "display": "output", + "isConnected": true, + "label": "Latents", + "type": "latents" + }, + "num_inference_steps": { + "default": 40, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 50 + }, + "scheduler": { + "display": "input", + "isConnected": true, + "label": "Scheduler *", + "type": "diffusers_auto_model" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 6203 + } + }, + "unet": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "Denoise Model *", + "onSignal": [ + "update_node", + { + "action": "signal", + "target": "guider" + }, + { + "action": "signal", + "target": "controlnet_bundle" + } + ], + "required": true, + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "denoise", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 880, + "y": 285 + }, + "type": "custom" + }, + { + "data": { + "action": "ImageEncode", + "cache": false, + "category": "sampler", + "description": "", + "label": "Encode Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "encode_summary": { + "dataSource": "encode_summary_data", + "display": "ui_text", + "hidden": true, + "label": "Encode summary", + "type": "text" + }, + "encode_summary_data": { + "display": "output", + "hidden": true, + "isConnected": false, + "label": "Encode summary", + "type": "str" + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image *", + "type": "image" + }, + "image_latents": { + "display": "output", + "isConnected": true, + "label": "Image Latents", + "type": "latents" + }, + "vae": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "VAE *", + "onSignal": "update_node", + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "imageEncode", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 440, + "y": 500 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/qwen_character_angles.reference_image_1_EL2KBE.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 626 + }, + "type": "custom" + }, + { + "data": { + "action": "ModelsLoader", + "cache": false, + "category": "loader", + "description": "", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "auto_offload": { + "label": "Enable Auto Offload", + "type": "boolean", + "value": false + }, + "device": { + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "disabled": false, + "label": "dtype", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "value": "bfloat16" + }, + "image_encoder": { + "display": "output", + "isConnected": false, + "label": "Image Encoder", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "lora_list": { + "display": "input", + "isConnected": false, + "label": "Lora", + "type": "custom_lora" + }, + "model_type": { + "disabled": false, + "label": "Model Type", + "onChange": [ + "set_filters", + { + "action": "signal", + "target": "unet_out" + }, + { + "action": "signal", + "target": "text_encoders" + }, + { + "action": "signal", + "target": "vae_out" + }, + { + "action": "signal", + "target": "image_encoder" + } + ], + "options": { + "": "", + "DummyCustomPipeline": "Custom", + "Flux2KleinModularPipeline": "Flux 2 Klein Distilled", + "FluxKontextModularPipeline": "Flux Kontext", + "FluxModularPipeline": "Flux", + "QwenImageEditModularPipeline": "Qwen-Image-Edit", + "QwenImageEditPlusModularPipeline": "Qwen-Image-Edit-2511", + "QwenImageLayeredModularPipeline": "Qwen-Image-Layered", + "QwenImageModularPipeline": "Qwen-Image-2512", + "StableDiffusionXLModularPipeline": "Stable Diffusion XL", + "WanImage2VideoModularPipeline": "WAN2 I2V", + "WanModularPipeline": "WAN2 T2V", + "ZImageModularPipeline": "Z-Image" + }, + "type": "string", + "value": "QwenImageEditModularPipeline" + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quant_config": { + "display": "input", + "isConnected": false, + "label": "Quant Config", + "type": "quant_config" + }, + "repo_id": { + "disabled": false, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": { + "className": [ + "QwenImageEditModularPipeline" + ] + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Repository ID", + "type": "string", + "value": { + "source": "hub", + "value": "Qwen/Qwen-Image-Edit" + } + }, + "scheduler": { + "display": "output", + "isConnected": true, + "label": "Scheduler", + "type": "diffusers_auto_model" + }, + "text_encoders": { + "display": "output", + "isConnected": true, + "label": "Text Encoders", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_models" + }, + "trust_remote_code": { + "label": "Trust Remote Code", + "type": "boolean", + "value": false + }, + "unet": { + "display": "input", + "isConnected": false, + "label": "Denoise Model", + "type": "diffusers_auto_model" + }, + "unet_out": { + "display": "output", + "isConnected": true, + "label": "Denoise Model", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "vae": { + "display": "input", + "isConnected": false, + "label": "VAE", + "type": "diffusers_auto_model" + }, + "vae_out": { + "display": "output", + "isConnected": true, + "label": "VAE", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "revision": { + "label": "Revision", + "type": "string", + "default": "", + "value": "ac7f9318f633fc4b5778c59367c8128225f1e3de" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "models", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-05", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1760, + "y": 327 + }, + "type": "custom" + }, + { + "data": { + "action": "EncodePrompt", + "cache": false, + "category": "embedding", + "description": "", + "label": "Encode Prompt", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "embeddings": { + "display": "output", + "isConnected": true, + "label": "Text Embeddings", + "type": "embeddings" + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image *", + "type": "image" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "string", + "value": "identity drift, different outfit, different face, borders, labels, captions, inconsistent lighting, extra limbs, warped anatomy" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt *", + "type": "string", + "value": "Create a 2x2 character turnaround contact sheet from the source character image. Preserve the character identity, face shape, hair, outfit materials, color palette, age, body proportions, and core silhouette. Render four clean views in one image: front close portrait, three-quarter view, low-angle hero view, and wide full-body view. Keep lighting, background style, and rendering quality consistent across all four panels. Do not change the character into a different person. No labels, no panel borders, no captions." + }, + "text_encoders": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "Text Encoders *", + "onSignal": "update_node", + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_models" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "prompt", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 440, + "y": 182 + }, + "type": "custom" + } + ], + "viewport": { + "x": 165.87608318890813, + "y": 43, + "zoom": 0.4662045060658579 + } +} diff --git a/data/graphs/studio/qwen-image-edit-modular-pipeline/inpaint.json b/data/graphs/studio/qwen-image-edit-modular-pipeline/inpaint.json new file mode 100644 index 0000000..f63b537 --- /dev/null +++ b/data/graphs/studio/qwen-image-edit-modular-pipeline/inpaint.json @@ -0,0 +1,2049 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "mask_image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "a6b91f0765a5f94bee618a8d5a931c2f3a0094f0efa818492ab5cfc093b53765", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Inpaint", + "cache": false, + "category": "Diffusers Image", + "description": "Inpaint or fill with a Diffusers image pipeline.", + "label": "Diffusers.Inpaint", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 4 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "image": { + "description": "A single source image or a list of references for pipelines that support multi-reference editing.", + "display": "input", + "isConnected": true, + "label": "Image or references", + "required": true, + "type": "image" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "mask_image": { + "display": "input", + "isConnected": true, + "label": "Mask", + "required": true, + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "changed unmasked pixels, visible mask boundary, wrong scale, mismatched lighting, floating object, altered background" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Mask contract: replace only the small masked charcoal box on the entryway console with one handcrafted stoneware keepsake chest; treat the complete unmasked entryway as immutable. Replacement design: preserve the original box outer width, height, depth, position and rectangular silhouette so the new chest fills the masked footprint. Construct four straight vertical stoneware walls, softly chamfered corners, uninterrupted rectangular faces, a narrow unglazed clay foot and one flat fitted dark-walnut slab lid. Finish the body in deep forest-green low-sheen celadon with subtle horizontal hand-finishing texture and a thin cork gasket line. Integration: match the source camera perspective, focus plane, soft left window light, warm wall bounce, texture scale, grain, wall tone and exposure; ground the stoneware chest with one physically correct contact shadow, a restrained broad glaze highlight facing the window, and believable base occlusion, then feather the boundary without a halo. Preserve the source crop, doorway, switch plate, hooks, folded scarf, ceramic bowl, basket, wall color, console geometry and every region outside the mask." + }, + "reference_strength": { + "default": 1, + "description": "Relative influence of every reference after the first composition anchor, when supported by the selected adapter.", + "label": "Secondary reference strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 6131 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageInpaint", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 220 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "inpaint" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Qwen/Qwen-Image-Edit" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "QwenImageEditInpaintPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "ac7f9318f633fc4b5778c59367c8128225f1e3de" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 234 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 201 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/qwen_inpaint_object_replace.reference_image_1_FOmuJE.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "remove alpha" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": "images/qwen_inpaint_object_replace.mask_image_LHQj9E.png" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadMask", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 0, + "y": 720 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 1760, + "y": 374 + }, + "type": "custom" + } + ], + "viewport": { + "x": 299.9959568733153, + "y": 43, + "zoom": 0.2900269541778976 + } +} diff --git a/data/graphs/studio/qwen-image-edit-modular-pipeline/outpaint.json b/data/graphs/studio/qwen-image-edit-modular-pipeline/outpaint.json new file mode 100644 index 0000000..2590811 --- /dev/null +++ b/data/graphs/studio/qwen-image-edit-modular-pipeline/outpaint.json @@ -0,0 +1,2103 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "canvas", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "mask_image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "mask_image", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "8398ca2cb140258c414eba5d4760d9fadaef43e98e67abef05fee17d1f61cf64", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Inpaint", + "cache": false, + "category": "Diffusers Image", + "description": "Inpaint or fill with a Diffusers image pipeline.", + "label": "Diffusers.Inpaint", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 4 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 768 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "image": { + "description": "A single source image or a list of references for pipelines that support multi-reference editing.", + "display": "input", + "isConnected": true, + "label": "Image or references", + "required": true, + "type": "image" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "mask_image": { + "display": "input", + "isConnected": true, + "label": "Mask", + "required": true, + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "repeated rock patterns, stretched subject, changed face, duplicate person, extra equipment, broken horizon, mismatched weather, changed original crop, obvious seam, painted landscape, fantasy glow" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 12 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Extend the source documentary portrait of a marine field researcher into a wide environmental photograph while preserving the original person, facial identity, waterproof jacket, pose, scale, central crop, horizon, camera height, lens perspective and overcast light. Generate only a level continuation of the same low basalt foreshore outside the source: similarly sized layered black rocks, shallow tide pools, sparse low wind-bent grass and uninterrupted distant gray water. Keep the shoreline elevation low and continuous; do not introduce cliffs, coves, headlands or large new landforms. Match rock scale, atmospheric depth, cloud structure, grain, focus falloff and shadow softness across both transitions. Keep the original image visually anchored in the center. Do not add another person, duplicate equipment, repeat rock patterns, stretch the body, change the face, or turn the new borders into a painted or fantastical landscape." + }, + "reference_strength": { + "default": 1, + "description": "Relative influence of every reference after the first composition anchor, when supported by the selected adapter.", + "label": "Secondary reference strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 7302 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1344 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageInpaint", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 290 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "outpaint" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Qwen/Qwen-Image-Edit" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "QwenImageEditInpaintPipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "ac7f9318f633fc4b5778c59367c8128225f1e3de" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 304 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 257 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/qwen_outpaint_aspect_template.reference_image_1_6T0Dq2.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 603 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1760, + "y": 444 + }, + "type": "custom" + }, + { + "data": { + "action": "OutpaintCanvas", + "cache": false, + "category": "Diffusers Image", + "description": "Prepare a model-neutral expanded canvas and white-generate boundary mask.", + "label": "Outpaint Canvas", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "bottom": { + "default": 0, + "label": "Bottom margin", + "max": 2048, + "min": 0, + "step": 16, + "type": "int", + "value": 0 + }, + "canvas": { + "display": "output", + "isConnected": true, + "label": "Canvas", + "type": "image" + }, + "feather": { + "default": 8, + "label": "Mask feather", + "max": 128, + "min": 0, + "step": 1, + "type": "float", + "value": 48 + }, + "fill_color": { + "default": "#000000", + "label": "Fill color", + "type": "string", + "value": "black" + }, + "height": { + "default": 768, + "label": "Canvas height", + "max": 2048, + "min": 64, + "step": 16, + "type": "int", + "value": 768 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Source image", + "type": "image" + }, + "left": { + "default": 256, + "label": "Left margin", + "max": 2048, + "min": 0, + "step": 16, + "type": "int", + "value": 256 + }, + "mask_image": { + "display": "output", + "isConnected": true, + "label": "Mask image", + "type": "image" + }, + "overlap": { + "default": 24, + "label": "Seam overlap", + "max": 256, + "min": 0, + "step": 4, + "type": "int", + "value": 96 + }, + "right": { + "default": 256, + "label": "Right margin", + "max": 2048, + "min": 0, + "step": 16, + "type": "int", + "value": 256 + }, + "top": { + "default": 0, + "label": "Top margin", + "max": 2048, + "min": 0, + "step": 16, + "type": "int", + "value": 0 + }, + "width": { + "default": 1344, + "label": "Canvas width", + "max": 2048, + "min": 64, + "step": 16, + "type": "int", + "value": 1344 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "qwenOutpaintCanvas", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 440, + "y": 692 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 64.78701645175636, + "zoom": 0.4699866607381058 + } +} diff --git a/data/graphs/studio/qwen-image-edit-plus-modular-pipeline/edit-image.json b/data/graphs/studio/qwen-image-edit-plus-modular-pipeline/edit-image.json new file mode 100644 index 0000000..f48910e --- /dev/null +++ b/data/graphs/studio/qwen-image-edit-plus-modular-pipeline/edit-image.json @@ -0,0 +1,1255 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-latents", + "data": { + "connectionType": "latents" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#6366F1", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "latents", + "style": { + "stroke": "#6366F1" + }, + "target": "node-01", + "targetHandle": "latents", + "type": "default" + }, + { + "className": "category-latents", + "data": { + "connectionType": "latents" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#6366F1", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "image_latents", + "style": { + "stroke": "#6366F1" + }, + "target": "node-02", + "targetHandle": "image_latents", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-03", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-08", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-custom_lora", + "data": { + "connectionType": "custom_lora" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#F0ABFC", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "lora", + "style": { + "stroke": "#F0ABFC" + }, + "target": "node-06", + "targetHandle": "lora_list", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "scheduler", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-02", + "targetHandle": "scheduler", + "type": "default" + }, + { + "className": "category-diffusers_auto_models", + "data": { + "connectionType": "diffusers_auto_models" + }, + "edgeType": "default", + "id": "edge-08", + "markerEnd": { + "color": "#8B5CF6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "text_encoders", + "style": { + "stroke": "#8B5CF6" + }, + "target": "node-08", + "targetHandle": "text_encoders", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-09", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "unet_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-02", + "targetHandle": "unet", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-10", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "vae_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-01", + "targetHandle": "vae", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-11", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "vae_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-03", + "targetHandle": "vae", + "type": "default" + }, + { + "className": "category-embeddings", + "data": { + "connectionType": "embeddings" + }, + "edgeType": "default", + "id": "edge-12", + "markerEnd": { + "color": "#FBBF24", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "embeddings", + "style": { + "stroke": "#FBBF24" + }, + "target": "node-02", + "targetHandle": "embeddings", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "d3ae8841468d4e3a0ae78e3e444cc2b5377ef8f712ce152fa1a3f0c820cac5b3", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "DecodeLatents", + "cache": false, + "category": "sampler", + "description": "", + "label": "Decode Latents", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "latents": { + "display": "input", + "isConnected": true, + "label": "Latents *", + "type": "latents" + }, + "vae": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "VAE *", + "onSignal": "update_node", + "signal": { + "direction": "input" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "decode", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 229 + }, + "type": "custom" + }, + { + "data": { + "action": "Denoise", + "cache": false, + "category": "sampler", + "description": "", + "label": "Denoise", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "embeddings": { + "display": "input", + "isConnected": true, + "label": "Text Embeddings *", + "type": "embeddings" + }, + "guidance_scale": { + "default": 4, + "display": "slider", + "hidden": false, + "label": "Guidance Scale", + "max": 30, + "min": 1, + "step": 0.1, + "type": "float", + "value": 1 + }, + "guider": { + "display": "input", + "isConnected": false, + "label": "Guider", + "onChange": { + "false": [ + "guidance_scale" + ], + "true": [] + }, + "signal": { + "direction": "input", + "origin": "unet" + }, + "type": "custom_guider" + }, + "image_latents": { + "display": "input", + "isConnected": true, + "label": "Image Latents *", + "type": "latents" + }, + "latents": { + "display": "output", + "isConnected": true, + "label": "Latents", + "type": "latents" + }, + "num_inference_steps": { + "default": 40, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 4 + }, + "scheduler": { + "display": "input", + "isConnected": true, + "label": "Scheduler *", + "type": "diffusers_auto_model" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 6101 + } + }, + "unet": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "Denoise Model *", + "onSignal": [ + "update_node", + { + "action": "signal", + "target": "guider" + }, + { + "action": "signal", + "target": "controlnet_bundle" + } + ], + "required": true, + "signal": { + "direction": "input" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "denoise", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 1320, + "y": 145 + }, + "type": "custom" + }, + { + "data": { + "action": "ImageEncode", + "cache": false, + "category": "sampler", + "description": "", + "label": "Encode Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "encode_summary": { + "dataSource": "encode_summary_data", + "display": "ui_text", + "hidden": true, + "label": "Encode summary", + "type": "text" + }, + "encode_summary_data": { + "display": "output", + "hidden": true, + "isConnected": false, + "label": "Encode summary", + "type": "str" + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image *", + "type": "image" + }, + "image_latents": { + "display": "output", + "isConnected": true, + "label": "Image Latents", + "type": "latents" + }, + "vae": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "VAE *", + "onSignal": "update_node", + "signal": { + "direction": "input" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "imageEncode", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 880, + "y": 360 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/character_edit.reference_image_1__k_ZFF.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Lora", + "cache": false, + "category": "adapters", + "description": "", + "label": "Lora", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "22226e8d05d354bb356627d428809f5afd7819399b077238a2b70a82883a904f" + }, + "lora": { + "display": "output", + "isConnected": true, + "label": "Lora", + "type": "custom_lora" + }, + "model": { + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": { + "className": [ + "" + ] + }, + "local": { + "className": [ + "" + ] + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "lightx2v/Qwen-Image-Edit-2511-Lightning" + } + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 20, + "min": -20, + "step": 0.1, + "type": "float", + "value": 1 + }, + "scheduler_class": { + "label": "Scheduler Class", + "type": "string", + "value": "FlowMatchEulerDiscreteScheduler" + }, + "scheduler_config": { + "display": "textarea", + "label": "Scheduler Config (JSON)", + "type": "text", + "value": "{\"base_image_seq_len\":256,\"base_shift\":1.0986122886681098,\"invert_sigmas\":false,\"max_image_seq_len\":8192,\"max_shift\":1.0986122886681098,\"num_train_timesteps\":1000,\"shift\":1,\"shift_terminal\":null,\"stochastic_sampling\":false,\"time_shift_type\":\"exponential\",\"use_beta_sigmas\":false,\"use_dynamic_shifting\":true,\"use_exponential_sigmas\":false,\"use_karras_sigmas\":false}" + }, + "weight_name": { + "label": "Weight Name", + "type": "string", + "value": "Qwen-Image-Edit-2511-Lightning-4steps-V1.0-bf16.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "ModelsLoader", + "cache": false, + "category": "loader", + "description": "", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "auto_offload": { + "label": "Enable Auto Offload", + "type": "boolean", + "value": false + }, + "device": { + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "disabled": false, + "label": "dtype", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "value": "bfloat16" + }, + "image_encoder": { + "display": "output", + "isConnected": false, + "label": "Image Encoder", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "lora_list": { + "display": "input", + "isConnected": true, + "label": "Lora", + "type": "custom_lora" + }, + "model_type": { + "disabled": false, + "label": "Model Type", + "onChange": [ + "set_filters", + { + "action": "signal", + "target": "unet_out" + }, + { + "action": "signal", + "target": "text_encoders" + }, + { + "action": "signal", + "target": "vae_out" + }, + { + "action": "signal", + "target": "image_encoder" + } + ], + "options": { + "": "", + "DummyCustomPipeline": "Custom", + "Flux2KleinModularPipeline": "Flux 2 Klein Distilled", + "FluxKontextModularPipeline": "Flux Kontext", + "FluxModularPipeline": "Flux", + "QwenImageEditModularPipeline": "Qwen-Image-Edit", + "QwenImageEditPlusModularPipeline": "Qwen-Image-Edit-2511", + "QwenImageLayeredModularPipeline": "Qwen-Image-Layered", + "QwenImageModularPipeline": "Qwen-Image-2512", + "StableDiffusionXLModularPipeline": "Stable Diffusion XL", + "WanImage2VideoModularPipeline": "WAN2 I2V", + "WanModularPipeline": "WAN2 T2V", + "ZImageModularPipeline": "Z-Image" + }, + "type": "string", + "value": "QwenImageEditPlusModularPipeline" + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quant_config": { + "display": "input", + "isConnected": false, + "label": "Quant Config", + "type": "quant_config" + }, + "repo_id": { + "disabled": false, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": { + "className": [ + "QwenImageEditPlusModularPipeline" + ] + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Repository ID", + "type": "string", + "value": { + "source": "hub", + "value": "Qwen/Qwen-Image-Edit-2511" + } + }, + "scheduler": { + "display": "output", + "isConnected": true, + "label": "Scheduler", + "type": "diffusers_auto_model" + }, + "text_encoders": { + "display": "output", + "isConnected": true, + "label": "Text Encoders", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_models" + }, + "trust_remote_code": { + "label": "Trust Remote Code", + "type": "boolean", + "value": false + }, + "unet": { + "display": "input", + "isConnected": false, + "label": "Denoise Model", + "type": "diffusers_auto_model" + }, + "unet_out": { + "display": "output", + "isConnected": true, + "label": "Denoise Model", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "vae": { + "display": "input", + "isConnected": false, + "label": "VAE", + "type": "diffusers_auto_model" + }, + "vae_out": { + "display": "output", + "isConnected": true, + "label": "VAE", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "revision": { + "label": "Revision", + "type": "string", + "default": "", + "value": "6f3ccc0b56e431dc6a0c2b2039706d7d26f22cb9" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "models", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-06", + "position": { + "x": 440, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 2200, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "EncodePrompt", + "cache": false, + "category": "embedding", + "description": "", + "label": "Encode Prompt", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "embeddings": { + "display": "output", + "isConnected": true, + "label": "Text Embeddings", + "type": "embeddings" + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image *", + "type": "image" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "string", + "value": "identity drift, different face, changed hairstyle, missing copper streak, changed pose, changed hands or microphone, extra fingers, extra limbs, plastic skin, changed ship position, enlarged ship, sharp ship, changed horizon, changed rocks or water, moved practical lights, translucent clothing, neon, armor, cyberpunk, illustration, mismatched jacket lighting, malformed seams, warped eyes, visible edit boundary, text, watermark" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt *", + "type": "string", + "value": "Restyle only the source singer into a realistic coastal field-recording presenter while preserving her exact face shape, age, warm skin tone, gaze direction, hairstyle and copper streak, hand positions, microphone, pose, crop, camera perspective, and the complete rocky-coast background. Replace the silver jacket with one tailored rust-brown waxed-canvas field jacket: matte weathered fabric, dark navy wool collar, reinforced shoulder panels, two believable brass snaps, and one narrow slate-blue scarf tucked naturally inside the collar. Keep the garment correctly fitted around both shoulders, elbows, wrists, and microphone grip. Treat every non-clothing region as structurally locked. Keep the distant ship as a comparably small, soft, out-of-focus silhouette in the same upper-left distance; preserve the horizon, water highlights, rocks, amber practical lights, hair flyaways, face, hands, microphone, cable, crop, focus, and original warm late-afternoon illumination. The result must remain a documentary photograph, not science fiction, fashion illustration, or animation." + }, + "text_encoders": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "Text Encoders *", + "onSignal": "update_node", + "signal": { + "direction": "input" + }, + "type": "diffusers_auto_models" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "prompt", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-08", + "position": { + "x": 880, + "y": 42 + }, + "type": "custom" + } + ], + "viewport": { + "x": 99.04752851711021, + "y": 43, + "zoom": 0.4091254752851711 + } +} diff --git a/data/graphs/studio/qwen-image-edit-plus-modular-pipeline/multi-image-reference-edit.json b/data/graphs/studio/qwen-image-edit-plus-modular-pipeline/multi-image-reference-edit.json new file mode 100644 index 0000000..6a61174 --- /dev/null +++ b/data/graphs/studio/qwen-image-edit-plus-modular-pipeline/multi-image-reference-edit.json @@ -0,0 +1,1256 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-latents", + "data": { + "connectionType": "latents" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#6366F1", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "latents", + "style": { + "stroke": "#6366F1" + }, + "target": "node-01", + "targetHandle": "latents", + "type": "default" + }, + { + "className": "category-latents", + "data": { + "connectionType": "latents" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#6366F1", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "image_latents", + "style": { + "stroke": "#6366F1" + }, + "target": "node-02", + "targetHandle": "image_latents", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-03", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-08", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-custom_lora", + "data": { + "connectionType": "custom_lora" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#F0ABFC", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "lora", + "style": { + "stroke": "#F0ABFC" + }, + "target": "node-06", + "targetHandle": "lora_list", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "scheduler", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-02", + "targetHandle": "scheduler", + "type": "default" + }, + { + "className": "category-diffusers_auto_models", + "data": { + "connectionType": "diffusers_auto_models" + }, + "edgeType": "default", + "id": "edge-08", + "markerEnd": { + "color": "#8B5CF6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "text_encoders", + "style": { + "stroke": "#8B5CF6" + }, + "target": "node-08", + "targetHandle": "text_encoders", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-09", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "unet_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-02", + "targetHandle": "unet", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-10", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "vae_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-01", + "targetHandle": "vae", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-11", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "vae_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-03", + "targetHandle": "vae", + "type": "default" + }, + { + "className": "category-embeddings", + "data": { + "connectionType": "embeddings" + }, + "edgeType": "default", + "id": "edge-12", + "markerEnd": { + "color": "#FBBF24", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "embeddings", + "style": { + "stroke": "#FBBF24" + }, + "target": "node-02", + "targetHandle": "embeddings", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "d3ae8841468d4e3a0ae78e3e444cc2b5377ef8f712ce152fa1a3f0c820cac5b3", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "DecodeLatents", + "cache": false, + "category": "sampler", + "description": "", + "label": "Decode Latents", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "latents": { + "display": "input", + "isConnected": true, + "label": "Latents *", + "type": "latents" + }, + "vae": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "VAE *", + "onSignal": "update_node", + "signal": { + "direction": "input" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "decode", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 229 + }, + "type": "custom" + }, + { + "data": { + "action": "Denoise", + "cache": false, + "category": "sampler", + "description": "", + "label": "Denoise", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "embeddings": { + "display": "input", + "isConnected": true, + "label": "Text Embeddings *", + "type": "embeddings" + }, + "guidance_scale": { + "default": 4, + "display": "slider", + "hidden": false, + "label": "Guidance Scale", + "max": 30, + "min": 1, + "step": 0.1, + "type": "float", + "value": 1 + }, + "guider": { + "display": "input", + "isConnected": false, + "label": "Guider", + "onChange": { + "false": [ + "guidance_scale" + ], + "true": [] + }, + "signal": { + "direction": "input", + "origin": "unet" + }, + "type": "custom_guider" + }, + "image_latents": { + "display": "input", + "isConnected": true, + "label": "Image Latents *", + "type": "latents" + }, + "latents": { + "display": "output", + "isConnected": true, + "label": "Latents", + "type": "latents" + }, + "num_inference_steps": { + "default": 40, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 4 + }, + "scheduler": { + "display": "input", + "isConnected": true, + "label": "Scheduler *", + "type": "diffusers_auto_model" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 6201 + } + }, + "unet": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "Denoise Model *", + "onSignal": [ + "update_node", + { + "action": "signal", + "target": "guider" + }, + { + "action": "signal", + "target": "controlnet_bundle" + } + ], + "required": true, + "signal": { + "direction": "input" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "denoise", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 1320, + "y": 145 + }, + "type": "custom" + }, + { + "data": { + "action": "ImageEncode", + "cache": false, + "category": "sampler", + "description": "", + "label": "Encode Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "encode_summary": { + "dataSource": "encode_summary_data", + "display": "ui_text", + "hidden": true, + "label": "Encode summary", + "type": "text" + }, + "encode_summary_data": { + "display": "output", + "hidden": true, + "isConnected": false, + "label": "Encode summary", + "type": "str" + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image *", + "type": "image" + }, + "image_latents": { + "display": "output", + "isConnected": true, + "label": "Image Latents", + "type": "latents" + }, + "vae": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "VAE *", + "onSignal": "update_node", + "signal": { + "direction": "input" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "imageEncode", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 880, + "y": 360 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/qwen_product_ad_composite.reference_image_1_HquO9m.webp", + "images/qwen_product_ad_composite.reference_image_2_UbQnUD.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Lora", + "cache": false, + "category": "adapters", + "description": "", + "label": "Lora", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "22226e8d05d354bb356627d428809f5afd7819399b077238a2b70a82883a904f" + }, + "lora": { + "display": "output", + "isConnected": true, + "label": "Lora", + "type": "custom_lora" + }, + "model": { + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": { + "className": [ + "" + ] + }, + "local": { + "className": [ + "" + ] + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "lightx2v/Qwen-Image-Edit-2511-Lightning" + } + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 20, + "min": -20, + "step": 0.1, + "type": "float", + "value": 1 + }, + "scheduler_class": { + "label": "Scheduler Class", + "type": "string", + "value": "FlowMatchEulerDiscreteScheduler" + }, + "scheduler_config": { + "display": "textarea", + "label": "Scheduler Config (JSON)", + "type": "text", + "value": "{\"base_image_seq_len\":256,\"base_shift\":1.0986122886681098,\"invert_sigmas\":false,\"max_image_seq_len\":8192,\"max_shift\":1.0986122886681098,\"num_train_timesteps\":1000,\"shift\":1,\"shift_terminal\":null,\"stochastic_sampling\":false,\"time_shift_type\":\"exponential\",\"use_beta_sigmas\":false,\"use_dynamic_shifting\":true,\"use_exponential_sigmas\":false,\"use_karras_sigmas\":false}" + }, + "weight_name": { + "label": "Weight Name", + "type": "string", + "value": "Qwen-Image-Edit-2511-Lightning-4steps-V1.0-bf16.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "ModelsLoader", + "cache": false, + "category": "loader", + "description": "", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "auto_offload": { + "label": "Enable Auto Offload", + "type": "boolean", + "value": false + }, + "device": { + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "disabled": false, + "label": "dtype", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "value": "bfloat16" + }, + "image_encoder": { + "display": "output", + "isConnected": false, + "label": "Image Encoder", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "lora_list": { + "display": "input", + "isConnected": true, + "label": "Lora", + "type": "custom_lora" + }, + "model_type": { + "disabled": false, + "label": "Model Type", + "onChange": [ + "set_filters", + { + "action": "signal", + "target": "unet_out" + }, + { + "action": "signal", + "target": "text_encoders" + }, + { + "action": "signal", + "target": "vae_out" + }, + { + "action": "signal", + "target": "image_encoder" + } + ], + "options": { + "": "", + "DummyCustomPipeline": "Custom", + "Flux2KleinModularPipeline": "Flux 2 Klein Distilled", + "FluxKontextModularPipeline": "Flux Kontext", + "FluxModularPipeline": "Flux", + "QwenImageEditModularPipeline": "Qwen-Image-Edit", + "QwenImageEditPlusModularPipeline": "Qwen-Image-Edit-2511", + "QwenImageLayeredModularPipeline": "Qwen-Image-Layered", + "QwenImageModularPipeline": "Qwen-Image-2512", + "StableDiffusionXLModularPipeline": "Stable Diffusion XL", + "WanImage2VideoModularPipeline": "WAN2 I2V", + "WanModularPipeline": "WAN2 T2V", + "ZImageModularPipeline": "Z-Image" + }, + "type": "string", + "value": "QwenImageEditPlusModularPipeline" + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quant_config": { + "display": "input", + "isConnected": false, + "label": "Quant Config", + "type": "quant_config" + }, + "repo_id": { + "disabled": false, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": { + "className": [ + "QwenImageEditPlusModularPipeline" + ] + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Repository ID", + "type": "string", + "value": { + "source": "hub", + "value": "Qwen/Qwen-Image-Edit-2511" + } + }, + "scheduler": { + "display": "output", + "isConnected": true, + "label": "Scheduler", + "type": "diffusers_auto_model" + }, + "text_encoders": { + "display": "output", + "isConnected": true, + "label": "Text Encoders", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_models" + }, + "trust_remote_code": { + "label": "Trust Remote Code", + "type": "boolean", + "value": false + }, + "unet": { + "display": "input", + "isConnected": false, + "label": "Denoise Model", + "type": "diffusers_auto_model" + }, + "unet_out": { + "display": "output", + "isConnected": true, + "label": "Denoise Model", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "vae": { + "display": "input", + "isConnected": false, + "label": "VAE", + "type": "diffusers_auto_model" + }, + "vae_out": { + "display": "output", + "isConnected": true, + "label": "VAE", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "revision": { + "label": "Revision", + "type": "string", + "default": "", + "value": "6f3ccc0b56e431dc6a0c2b2039706d7d26f22cb9" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "models", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-06", + "position": { + "x": 440, + "y": 47 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 2200, + "y": 187 + }, + "type": "custom" + }, + { + "data": { + "action": "EncodePrompt", + "cache": false, + "category": "embedding", + "description": "", + "label": "Encode Prompt", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "embeddings": { + "display": "output", + "isConnected": true, + "label": "Text Embeddings", + "type": "embeddings" + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image *", + "type": "image" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "string", + "value": "headline, body copy, caption, logo, any printed text, fake letters, gibberish typography, graphic panel, neon studio backdrop, collage edges, mismatched perspective, closed cube, solid block, glowing glass, floating product, missing platform, missing person, missing lake reflection, contradictory refraction, wrong vessel proportions, duplicate vessel, synthetic illustration, arbitrary neon, unrelated props covering product" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt *", + "type": "string", + "value": "Create one premium magazine advertisement by combining the two supplied references. Input 1 defines the restrained editorial grid, generous warm-white margins and disciplined typography hierarchy. Input 2 defines the immutable ASTERMIST lavender sleep-spray bottle. Build a believable bedside ritual context: place exactly one ASTERMIST bottle at normal cosmetic scale on a honed limestone nightstand beside one folded linen eye mask and a small dried-lavender stem. A softly blurred bed and dawn window belong to the same quiet room; every object rests on the same physical surface with coherent perspective, contact shadows and window light. Preserve the bottle’s pale-lavender glass, liquid level, dip tube, satin-silver atomizer, clear cap and rectangular label. Keep both exact label lines readable: “ASTERMIST” and “LAVENDER SLEEP SPRAY”. Do not enlarge it into architecture, a sculpture or a surreal monument. Use Input 1 only for layout discipline. Add exactly one clean headline in the upper negative space: “NIGHT RITUAL”. Include no other campaign copy. Use black editorial type, warm stone, muted lavender and natural blue dawn light with realistic glass refraction and premium photographic grain. The output must read as one real-location commercial photograph, not a pasted packshot or fantasy composite: matching scale, lens, light direction, color spill, focus falloff and surface contact, with no collage edge, halo, floating product, unrelated scenery or arbitrary object replacement." + }, + "text_encoders": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "Text Encoders *", + "onSignal": "update_node", + "signal": { + "direction": "input" + }, + "type": "diffusers_auto_models" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "prompt", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-08", + "position": { + "x": 880, + "y": 42 + }, + "type": "custom" + } + ], + "viewport": { + "x": 207.46700659868026, + "y": 43, + "zoom": 0.32273545290941813 + } +} diff --git a/data/graphs/studio/qwen-image-layered-modular-pipeline/layer-decomposition.json b/data/graphs/studio/qwen-image-layered-modular-pipeline/layer-decomposition.json new file mode 100644 index 0000000..607d560 --- /dev/null +++ b/data/graphs/studio/qwen-image-layered-modular-pipeline/layer-decomposition.json @@ -0,0 +1,1141 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-latents", + "data": { + "connectionType": "latents" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#6366F1", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "latents", + "style": { + "stroke": "#6366F1" + }, + "target": "node-01", + "targetHandle": "latents", + "type": "default" + }, + { + "className": "category-latents", + "data": { + "connectionType": "latents" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#6366F1", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "image_latents", + "style": { + "stroke": "#6366F1" + }, + "target": "node-02", + "targetHandle": "image_latents", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-03", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "scheduler", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-02", + "targetHandle": "scheduler", + "type": "default" + }, + { + "className": "category-diffusers_auto_models", + "data": { + "connectionType": "diffusers_auto_models" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#8B5CF6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "text_encoders", + "style": { + "stroke": "#8B5CF6" + }, + "target": "node-07", + "targetHandle": "text_encoders", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-08", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "unet_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-02", + "targetHandle": "unet", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-09", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "vae_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-01", + "targetHandle": "vae", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-10", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "vae_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-03", + "targetHandle": "vae", + "type": "default" + }, + { + "className": "category-embeddings", + "data": { + "connectionType": "embeddings" + }, + "edgeType": "default", + "id": "edge-11", + "markerEnd": { + "color": "#FBBF24", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "embeddings", + "style": { + "stroke": "#FBBF24" + }, + "target": "node-02", + "targetHandle": "embeddings", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "a6acf30febec0d87cd7e15935a1d70913b64eed657302ccb49e3431f029aa060", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "DecodeLatents", + "cache": false, + "category": "sampler", + "description": "", + "label": "Decode Latents", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "latents": { + "display": "input", + "isConnected": true, + "label": "Latents *", + "type": "latents" + }, + "vae": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "VAE *", + "onSignal": "update_node", + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "decode", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 369 + }, + "type": "custom" + }, + { + "data": { + "action": "Denoise", + "cache": false, + "category": "sampler", + "description": "", + "label": "Denoise", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "embeddings": { + "display": "input", + "isConnected": true, + "label": "Text Embeddings *", + "type": "embeddings" + }, + "guidance_scale": { + "default": 4, + "display": "slider", + "hidden": false, + "label": "Guidance Scale", + "max": 30, + "min": 1, + "step": 0.1, + "type": "float", + "value": 4 + }, + "guider": { + "display": "input", + "isConnected": false, + "label": "Guider", + "onChange": { + "false": [ + "guidance_scale" + ], + "true": [] + }, + "signal": { + "direction": "input", + "origin": "unet" + }, + "type": "custom_guider" + }, + "image_latents": { + "display": "input", + "isConnected": true, + "label": "Image Latents *", + "type": "latents" + }, + "latents": { + "display": "output", + "isConnected": true, + "label": "Latents", + "type": "latents" + }, + "layers": { + "default": 4, + "display": "slider", + "label": "Layers", + "max": 10, + "min": 1, + "type": "int", + "value": 3 + }, + "num_inference_steps": { + "default": 50, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 30 + }, + "scheduler": { + "display": "input", + "isConnected": true, + "label": "Scheduler *", + "type": "diffusers_auto_model" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 7102 + } + }, + "unet": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "Denoise Model *", + "onSignal": [ + "update_node", + { + "action": "signal", + "target": "guider" + }, + { + "action": "signal", + "target": "controlnet_bundle" + } + ], + "required": true, + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "denoise", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 880, + "y": 271 + }, + "type": "custom" + }, + { + "data": { + "action": "ImageEncode", + "cache": false, + "category": "sampler", + "description": "", + "label": "Encode Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "encode_summary": { + "dataSource": "encode_summary_data", + "display": "ui_text", + "hidden": true, + "label": "Encode summary", + "type": "text" + }, + "encode_summary_data": { + "display": "output", + "hidden": true, + "isConnected": false, + "label": "Encode summary", + "type": "str" + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image *", + "type": "image" + }, + "image_latents": { + "display": "output", + "isConnected": true, + "label": "Image Latents", + "type": "latents" + }, + "vae": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "VAE *", + "onSignal": "update_node", + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "imageEncode", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 440, + "y": 500 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [ + "images/qwen_layered_portrait.reference_image_1_yoYnZx.webp" + ] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 626 + }, + "type": "custom" + }, + { + "data": { + "action": "ModelsLoader", + "cache": false, + "category": "loader", + "description": "", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "auto_offload": { + "label": "Enable Auto Offload", + "type": "boolean", + "value": false + }, + "device": { + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "disabled": false, + "label": "dtype", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "value": "bfloat16" + }, + "image_encoder": { + "display": "output", + "isConnected": false, + "label": "Image Encoder", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "lora_list": { + "display": "input", + "isConnected": false, + "label": "Lora", + "type": "custom_lora" + }, + "model_type": { + "disabled": false, + "label": "Model Type", + "onChange": [ + "set_filters", + { + "action": "signal", + "target": "unet_out" + }, + { + "action": "signal", + "target": "text_encoders" + }, + { + "action": "signal", + "target": "vae_out" + }, + { + "action": "signal", + "target": "image_encoder" + } + ], + "options": { + "": "", + "DummyCustomPipeline": "Custom", + "Flux2KleinModularPipeline": "Flux 2 Klein Distilled", + "FluxKontextModularPipeline": "Flux Kontext", + "FluxModularPipeline": "Flux", + "QwenImageEditModularPipeline": "Qwen-Image-Edit", + "QwenImageEditPlusModularPipeline": "Qwen-Image-Edit-2511", + "QwenImageLayeredModularPipeline": "Qwen-Image-Layered", + "QwenImageModularPipeline": "Qwen-Image-2512", + "StableDiffusionXLModularPipeline": "Stable Diffusion XL", + "WanImage2VideoModularPipeline": "WAN2 I2V", + "WanModularPipeline": "WAN2 T2V", + "ZImageModularPipeline": "Z-Image" + }, + "type": "string", + "value": "QwenImageLayeredModularPipeline" + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quant_config": { + "display": "input", + "isConnected": false, + "label": "Quant Config", + "type": "quant_config" + }, + "repo_id": { + "disabled": false, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": { + "className": [ + "QwenImageLayeredModularPipeline" + ] + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Repository ID", + "type": "string", + "value": { + "source": "hub", + "value": "Qwen/Qwen-Image-Layered" + } + }, + "scheduler": { + "display": "output", + "isConnected": true, + "label": "Scheduler", + "type": "diffusers_auto_model" + }, + "text_encoders": { + "display": "output", + "isConnected": true, + "label": "Text Encoders", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_models" + }, + "trust_remote_code": { + "label": "Trust Remote Code", + "type": "boolean", + "value": false + }, + "unet": { + "display": "input", + "isConnected": false, + "label": "Denoise Model", + "type": "diffusers_auto_model" + }, + "unet_out": { + "display": "output", + "isConnected": true, + "label": "Denoise Model", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "vae": { + "display": "input", + "isConnected": false, + "label": "VAE", + "type": "diffusers_auto_model" + }, + "vae_out": { + "display": "output", + "isConnected": true, + "label": "VAE", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "revision": { + "label": "Revision", + "type": "string", + "default": "", + "value": "8f0ca708dfff6ba1dd5f2d85d78f8c108a040bcf" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "models", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-05", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 1760, + "y": 327 + }, + "type": "custom" + }, + { + "data": { + "action": "EncodePrompt", + "cache": false, + "category": "embedding", + "description": "", + "label": "Encode Prompt", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "embeddings": { + "display": "output", + "isConnected": true, + "label": "Text Embeddings", + "type": "embeddings" + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image *", + "type": "image" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "string", + "value": "merged layers, empty layer, jagged alpha, halos, detached telescope fragment, duplicated tripod, person-telescope overlap, wrong relative scale, broken background, flattened depth" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt *", + "type": "string", + "value": "Decompose the supplied image into three ordered editable RGBA layers while faithfully preserving the complete photographed content. Overall image description: at blue hour on an alpine observatory deck, one adult woman astronomer in a navy field jacket and charcoal trousers stands center-right with both empty hands relaxed beside her body. One physically separate brass-and-matte-black refractor telescope on a complete three-leg tripod occupies the lower-left foreground without touching or overlapping the woman. A white observatory dome, a standard-height open doorway, a metal safety rail, mountain silhouettes and a clear deep-blue sky form the background. Occluded-content description: reconstruct the uninterrupted deck, rail, observatory wall, mountains and sky behind the woman and telescope so hiding a layer does not reveal an obvious cutout hole. Keep the source identity, pose, scale, eye-level 50 mm perspective, blue-hour lighting and object placement. Preserve the measured source relationship: the complete telescope assembly is about ninety-four percent of the woman’s visible height, its tube length is about fifty-one percent of her visible height, and a clear gap of about three percent of the image width separates the telescope from her body. Keep the entire telescope and all three grounded tripod feet coherent in one place without a detached tube, mount or tripod fragment elsewhere. Produce useful alpha boundaries and a recomposition that matches the source without bright fringe, dark matte, duplicated feature, missing content or flattened depth." + }, + "text_encoders": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "Text Encoders *", + "onSignal": "update_node", + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_models" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "prompt", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 440, + "y": 182 + }, + "type": "custom" + } + ], + "viewport": { + "x": 0, + "y": 0, + "zoom": 1 + } +} diff --git a/data/graphs/studio/qwen-image-modular-pipeline/control-image.json b/data/graphs/studio/qwen-image-modular-pipeline/control-image.json new file mode 100644 index 0000000..ac0ec35 --- /dev/null +++ b/data/graphs/studio/qwen-image-modular-pipeline/control-image.json @@ -0,0 +1,1518 @@ +{ + "edges": [ + { + "className": "category-custom_controlnet", + "data": { + "connectionType": "custom_controlnet" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FB923C", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "controlnet_bundle", + "style": { + "stroke": "#FB923C" + }, + "target": "node-04", + "targetHandle": "controlnet_bundle", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "model", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-01", + "targetHandle": "controlnet", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-latents", + "data": { + "connectionType": "latents" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#6366F1", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "latents", + "style": { + "stroke": "#6366F1" + }, + "target": "node-03", + "targetHandle": "latents", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-01", + "targetHandle": "control_image", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "scheduler", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-04", + "targetHandle": "scheduler", + "type": "default" + }, + { + "className": "category-diffusers_auto_models", + "data": { + "connectionType": "diffusers_auto_models" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#8B5CF6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "text_encoders", + "style": { + "stroke": "#8B5CF6" + }, + "target": "node-08", + "targetHandle": "text_encoders", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-08", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "unet_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-04", + "targetHandle": "unet", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-09", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "vae_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-01", + "targetHandle": "vae", + "type": "default" + }, + { + "className": "category-diffusers_auto_model", + "data": { + "connectionType": "diffusers_auto_model" + }, + "edgeType": "default", + "id": "edge-10", + "markerEnd": { + "color": "#A78BFA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "vae_out", + "style": { + "stroke": "#A78BFA" + }, + "target": "node-03", + "targetHandle": "vae", + "type": "default" + }, + { + "className": "category-embeddings", + "data": { + "connectionType": "embeddings" + }, + "edgeType": "default", + "id": "edge-11", + "markerEnd": { + "color": "#FBBF24", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "embeddings", + "style": { + "stroke": "#FBBF24" + }, + "target": "node-04", + "targetHandle": "embeddings", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "632a476788fe2dc34da4dd516319a845d7dfa09911be8f1545a36c056fc576a0", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Controlnet", + "cache": false, + "category": "adapters", + "description": "", + "label": "ControlNet", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "control_guidance_end": { + "default": 1, + "label": "Control Guidance End", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1 + }, + "control_guidance_start": { + "default": 0, + "label": "Control Guidance Start", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0 + }, + "control_image": { + "display": "input", + "isConnected": true, + "label": "Control Image *", + "type": "image" + }, + "controlnet": { + "display": "input", + "isConnected": true, + "label": "ControlNet Model *", + "type": "diffusers_auto_model" + }, + "controlnet_bundle": { + "disabled": false, + "display": "output", + "isConnected": true, + "label": "Controlnet", + "onSignal": [ + { + "action": "value", + "data": { + "DummyCustomPipeline": "DummyCustomPipeline", + "FluxKontextModularPipeline": "FluxKontextModularPipeline", + "FluxModularPipeline": "FluxModularPipeline", + "QwenImageEditModularPipeline": "QwenImageEditModularPipeline", + "QwenImageEditPlusModularPipeline": "QwenImageEditPlusModularPipeline", + "QwenImageModularPipeline": "QwenImageModularPipeline", + "StableDiffusionXLModularPipeline": "StableDiffusionXLModularPipeline" + }, + "target": "model_type" + }, + { + "action": "exec", + "data": "update_node" + } + ], + "signal": { + "direction": "input" + }, + "type": "custom_controlnet" + }, + "controlnet_conditioning_scale": { + "default": 0.5, + "label": "Controlnet Conditioning Scale", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 1.2 + }, + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "height": { + "default": 1024, + "label": "Height", + "min": 64, + "step": 8, + "type": "int", + "value": 768 + }, + "model_type": { + "default": "", + "hidden": true, + "label": "Model Type", + "type": "string", + "value": "QwenImageModularPipeline" + }, + "vae": { + "display": "input", + "isConnected": true, + "label": "VAE *", + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_model" + }, + "width": { + "default": 1024, + "label": "Width", + "min": 64, + "step": 8, + "type": "int", + "value": 768 + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "controlnet", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 440, + "y": 631 + }, + "type": "custom" + }, + { + "data": { + "action": "AutoModelLoader", + "cache": false, + "category": "loader", + "description": "", + "label": "Load Model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "auto_offload": { + "label": "Enable Auto Offload", + "type": "boolean", + "value": false + }, + "device": { + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "label": "dtype", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "value": "bfloat16" + }, + "model": { + "display": "output", + "isConnected": true, + "label": "Model", + "type": "diffusers_auto_model" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": { + "className": [ + "" + ] + }, + "local": { + "className": [ + "" + ] + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model ID", + "type": "string", + "value": { + "source": "hub", + "value": "InstantX/Qwen-Image-ControlNet-Union" + } + }, + "model_type": { + "label": "Model Type", + "onChange": [ + "set_filters", + { + "action": "signal", + "target": "model" + } + ], + "options": { + "": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "", + "schemaVersion": 1, + "value": "" + }, + "controlnet": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ControlNet", + "schemaVersion": 1, + "value": "controlnet" + }, + "transformer": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Transformer", + "schemaVersion": 1, + "value": "transformer" + }, + "unet": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "UNet", + "schemaVersion": 1, + "value": "unet" + }, + "vae": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "VAE", + "schemaVersion": 1, + "value": "vae" + } + }, + "type": "string", + "value": "controlnet" + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "subfolder": { + "label": "Subfolder", + "type": "string", + "value": "" + }, + "trust_remote_code": { + "label": "Trust Remote Code", + "type": "boolean", + "value": false + }, + "variant": { + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "", + "schemaVersion": 1, + "value": "" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fp16", + "schemaVersion": 1, + "value": "fp16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bf16", + "schemaVersion": 1, + "value": "bf16" + } + ], + "type": "string", + "value": "" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "controlnetModel", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 0, + "y": 1000 + }, + "type": "custom" + }, + { + "data": { + "action": "DecodeLatents", + "cache": false, + "category": "sampler", + "description": "", + "label": "Decode Latents", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "latents": { + "display": "input", + "isConnected": true, + "label": "Latents *", + "type": "latents" + }, + "vae": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "VAE *", + "onSignal": "update_node", + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_model" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "decode", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 1320, + "y": 584 + }, + "type": "custom" + }, + { + "data": { + "action": "Denoise", + "cache": false, + "category": "sampler", + "description": "", + "label": "Denoise", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "controlnet_bundle": { + "display": "input", + "isConnected": true, + "label": "ControlNet", + "signal": { + "direction": "input", + "origin": "unet" + }, + "type": "custom_controlnet" + }, + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "embeddings": { + "display": "input", + "isConnected": true, + "label": "Text Embeddings *", + "type": "embeddings" + }, + "guidance_scale": { + "default": 4.5, + "display": "slider", + "hidden": false, + "label": "Guidance Scale", + "max": 30, + "min": 1, + "step": 0.1, + "type": "float", + "value": 4 + }, + "guider": { + "display": "input", + "isConnected": false, + "label": "Guider", + "onChange": { + "false": [ + "guidance_scale" + ], + "true": [] + }, + "signal": { + "direction": "input", + "origin": "unet" + }, + "type": "custom_guider" + }, + "height": { + "default": 1024, + "hidden": false, + "label": "Height", + "min": 64, + "step": 8, + "type": "int", + "value": 768 + }, + "image_latents": { + "display": "input", + "isConnected": false, + "label": "Image Latents", + "onChange": { + "false": [ + "height", + "width" + ], + "true": [ + "strength" + ] + }, + "type": "latents" + }, + "latents": { + "display": "output", + "isConnected": true, + "label": "Latents", + "type": "latents" + }, + "num_inference_steps": { + "default": 50, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 36 + }, + "scheduler": { + "display": "input", + "isConnected": true, + "label": "Scheduler *", + "type": "diffusers_auto_model" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 5201 + } + }, + "strength": { + "default": 0.5, + "hidden": true, + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "unet": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "Denoise Model *", + "onSignal": [ + "update_node", + { + "action": "signal", + "target": "guider" + }, + { + "action": "signal", + "target": "controlnet_bundle" + } + ], + "required": true, + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_model" + }, + "width": { + "default": 1024, + "hidden": false, + "label": "Width", + "min": 64, + "step": 8, + "type": "int", + "value": 768 + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "denoise", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 880, + "y": 444 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": "images/qwen_control_image_layout.control_image_e709g8.png" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 626 + }, + "type": "custom" + }, + { + "data": { + "action": "ModelsLoader", + "cache": false, + "category": "loader", + "description": "", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "auto_offload": { + "label": "Enable Auto Offload", + "type": "boolean", + "value": false + }, + "device": { + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "disabled": false, + "label": "dtype", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "value": "bfloat16" + }, + "image_encoder": { + "display": "output", + "isConnected": false, + "label": "Image Encoder", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "lora_list": { + "display": "input", + "isConnected": false, + "label": "Lora", + "type": "custom_lora" + }, + "model_type": { + "disabled": false, + "label": "Model Type", + "onChange": [ + "set_filters", + { + "action": "signal", + "target": "unet_out" + }, + { + "action": "signal", + "target": "text_encoders" + }, + { + "action": "signal", + "target": "vae_out" + }, + { + "action": "signal", + "target": "image_encoder" + } + ], + "options": { + "": "", + "DummyCustomPipeline": "Custom", + "Flux2KleinModularPipeline": "Flux 2 Klein Distilled", + "FluxKontextModularPipeline": "Flux Kontext", + "FluxModularPipeline": "Flux", + "QwenImageEditModularPipeline": "Qwen-Image-Edit", + "QwenImageEditPlusModularPipeline": "Qwen-Image-Edit-2511", + "QwenImageLayeredModularPipeline": "Qwen-Image-Layered", + "QwenImageModularPipeline": "Qwen-Image-2512", + "StableDiffusionXLModularPipeline": "Stable Diffusion XL", + "WanImage2VideoModularPipeline": "WAN2 I2V", + "WanModularPipeline": "WAN2 T2V", + "ZImageModularPipeline": "Z-Image" + }, + "type": "string", + "value": "QwenImageModularPipeline" + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quant_config": { + "display": "input", + "isConnected": false, + "label": "Quant Config", + "type": "quant_config" + }, + "repo_id": { + "disabled": false, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": { + "className": [ + "QwenImageModularPipeline" + ] + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Repository ID", + "type": "string", + "value": { + "source": "hub", + "value": "Qwen/Qwen-Image-2512" + } + }, + "scheduler": { + "display": "output", + "isConnected": true, + "label": "Scheduler", + "type": "diffusers_auto_model" + }, + "text_encoders": { + "display": "output", + "isConnected": true, + "label": "Text Encoders", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_models" + }, + "trust_remote_code": { + "label": "Trust Remote Code", + "type": "boolean", + "value": false + }, + "unet": { + "display": "input", + "isConnected": false, + "label": "Denoise Model", + "type": "diffusers_auto_model" + }, + "unet_out": { + "display": "output", + "isConnected": true, + "label": "Denoise Model", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "vae": { + "display": "input", + "isConnected": false, + "label": "VAE", + "type": "diffusers_auto_model" + }, + "vae_out": { + "display": "output", + "isConnected": true, + "label": "VAE", + "signal": { + "direction": "output", + "origin": "model_type" + }, + "type": "diffusers_auto_model" + }, + "revision": { + "label": "Revision", + "type": "string", + "default": "", + "value": "25468b98e3276ca6700de15c6628e51b7de54a26" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "models", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-06", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 1760, + "y": 542 + }, + "type": "custom" + }, + { + "data": { + "action": "EncodePrompt", + "cache": false, + "category": "embedding", + "description": "", + "label": "Encode Prompt", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.ModularDiffusers", + "params": { + "doc": { + "display": "output", + "isConnected": false, + "label": "Doc", + "type": "string" + }, + "embeddings": { + "display": "output", + "isConnected": true, + "label": "Text Embeddings", + "type": "embeddings" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "string", + "value": "ignored control layout, folded box, rotated dieline, missing flap, merged panel, shifted label block, warped fold, broken symmetry, cropped edge, invented window, misspelled NORTHSTAR FIELD NOTES, unreadable series line, random copy, duplicated badge, muddy yellow, glossy plastic paper, heavy cast shadow, perspective distortion, messy registration, low detail" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt *", + "type": "string", + "value": "Packaging objective: turn the supplied control image into an orthographic premium folding-carton design proof for the fictional expedition notebook brand \"NORTHSTAR FIELD NOTES\". Control contract: preserve every outer flap, central and side-panel proportion, fold boundary, main front label rectangle, lower specification modules, circular feature marks, and yellow footer bars from the control image. Directly below the main label, retain two separate outlined specification boxes in their original positions: one small box on the left containing exactly one circular mark, and one wide box on the right containing exactly three circular marks. Keep all four marks inside those two front-panel boxes, never on the side panels. Keep the layout straight-on and centered; do not fold, rotate, crop, merge, relocate, or invent panels. Front-panel copy: set the exact readable brand \"NORTHSTAR FIELD NOTES\" in a compact black grotesk, with the smaller line \"EXPEDITION SERIES 04\". Use the lower modules for short technical copy and icons only; the side panels may carry restrained grid lines and small vertical product information. Material and finish: warm-white FSC paperboard, fine uncoated fiber, crisp black keylines, muted mustard-yellow technical badges, shallow blind emboss on the brand block, precise print registration, subtle scored fold channels, and physically plausible paper thickness along the outer cut edge. Presentation: neutral warm-gray proofing table, orthographic 90-degree camera, even D50 studio light with gentle relief shadows only at score lines, clean prepress/editorial art direction, generous clear space, and sharp readable print detail across the full board." + }, + "text_encoders": { + "disabled": false, + "display": "input", + "isConnected": true, + "label": "Text Encoders *", + "onSignal": "update_node", + "signal": { + "direction": "output" + }, + "type": "diffusers_auto_models" + } + }, + "resizable": true, + "skipParamsCheck": true, + "studioOwned": true, + "studioRole": "prompt", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-08", + "position": { + "x": 440, + "y": 341 + }, + "type": "custom" + } + ], + "viewport": { + "x": 122.84341637010675, + "y": 43, + "zoom": 0.4786476868327402 + } +} diff --git a/data/graphs/studio/qwen-image-modular-pipeline/text-to-image.json b/data/graphs/studio/qwen-image-modular-pipeline/text-to-image.json new file mode 100644 index 0000000..fa1b0bb --- /dev/null +++ b/data/graphs/studio/qwen-image-modular-pipeline/text-to-image.json @@ -0,0 +1,1774 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-05", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "897ca20c9ef2207a48a0ae2cdd5ac826206919e35cba73e0f159296038fdafbe", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 4 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "misspelled VERDA 03, extra letters, warped typography, unreadable subtitle, deformed can, smeared logo, low contrast, blur" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 50 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Create a square launch poster for the fictional sparkling tea VERDA 03. Render the exact large headline text \"VERDA 03\" at the top in crisp white geometric sans lettering, with the smaller readable line \"YUZU MINT SPARKLING TEA\" below it. Place a slim emerald can on a frosted glass plinth, condensation beads, softbox reflections, lime peel accent, strict centered layout, premium beverage advertising finish, and high contrast between typography and background." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 5101 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Qwen/Qwen-Image-2512" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "QwenImagePipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "25468b98e3276ca6700de15c6628e51b7de54a26" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 83.75249868490266, + "zoom": 0.5560231457127828 + } +} diff --git a/data/graphs/studio/wan-image-to-video-pipeline/image-to-video.json b/data/graphs/studio/wan-image-to-video-pipeline/image-to-video.json new file mode 100644 index 0000000..f2eea41 --- /dev/null +++ b/data/graphs/studio/wan-image-to-video-pipeline/image-to-video.json @@ -0,0 +1,1976 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-06", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "image", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-05", + "targetHandle": "reference_images", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-04", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "3005ea3f54b0af96578a90198a9d0779549769033947d9a2a98571aca7ded38b", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer", + "transformer_2" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "_native_flash" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "transformer,transformer_2" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "model_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "image", + "description": "Load an image from a file", + "label": "Load Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "alpha_channel": { + "default": "ignore", + "label": "Alpha Channel", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ignore", + "schemaVersion": 1, + "value": "ignore" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "add alpha", + "schemaVersion": 1, + "value": "add alpha" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "remove alpha", + "schemaVersion": 1, + "value": "remove alpha" + } + ], + "type": "string", + "value": "ignore" + }, + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "image" + ], + "multiple": true + }, + "label": false, + "type": "str", + "value": [] + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "image": { + "display": "output", + "isConnected": true, + "label": "Image", + "type": "image" + }, + "label": { + "display": "ui_label", + "value": "Load Image" + }, + "mask": { + "display": "output", + "isConnected": false, + "label": "Alpha mask", + "type": "image" + }, + "source_hash": { + "display": "output", + "isConnected": false, + "label": "Source hash", + "type": "str" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadImage", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 1760, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3.5 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 512 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "image_to_video" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "deformity, deformed anatomy, deformed limbs, malformed hands, extra fingers, missing fingers, fused fingers, extra hand, extra arm, detached limb, changing face, identity drift, painting, illustration, animation, cartoon, game render, plastic CGI, low resolution, pixelation, excessive blur, static frame, frozen action, camera freeze, flicker, temporal jitter, warped knife, bending knife, duplicate knife, extra utensil, morphing carrot, fused slices, floating food, impossible contact, unsafe grip, abrupt camera jump, extra person, text, subtitle, logo, watermark" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 81 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 40 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Continue exactly from the supplied fine-dining kitchen keyframe in one continuous five-second photoreal documentary shot. This is the final carrot-garnish cut for a composed root-vegetable course. Preserve the same adult chef, white jacket, dark apron, face, natural two-hand anatomy, one chef knife, one carrot, separated carrot rounds, walnut cutting board, white plate, folded towel, copper pans, stainless pass, camera height, lens and warm service light. Motion starts in the first frame: the chef keeps the guide hand in a safe curled claw grip and completes one slow controlled slicing action—the unchanged knife descends through the carrot, contacts the board, one new round separates cleanly, then the blade lifts slightly and holds. A stabilized close side camera slides slowly along the counter for the entire shot, creating visible foreground parallax while the chef shifts weight naturally. Keep five fingers per visible hand, one unchanged knife, one unchanged carrot, stationary plate, towel and background cookware, physically plausible contact and consistent shadows. No extra person, extra limb, extra utensil, repeated chopping, morphing food, cutaway, flambé, liquid splash, reflection figure, logo or writing." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": true, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 92021 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 3.5 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": true + }, + "video": { + "display": "input", + "isConnected": false, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 768 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 14 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Wan-AI/Wan2.2-I2V-A14B-Diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "model_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "WanImageToVideoPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "596658fd9ca6b7b71d5057529bbf319ecbc61d74" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-06", + "position": { + "x": 880, + "y": 131 + }, + "type": "custom" + } + ], + "viewport": { + "x": 175.9476885644769, + "y": 43, + "zoom": 0.43633414436334145 + } +} diff --git a/data/graphs/studio/wan-ti2-vpipeline/text-to-video.json b/data/graphs/studio/wan-ti2-vpipeline/text-to-video.json new file mode 100644 index 0000000..9e6c744 --- /dev/null +++ b/data/graphs/studio/wan-ti2-vpipeline/text-to-video.json @@ -0,0 +1,3516 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-10", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "output", + "style": { + "stroke": "#F472B6" + }, + "target": "node-03", + "targetHandle": "audio", + "type": "default" + }, + { + "className": "category-audio", + "data": { + "connectionType": "audio" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#F472B6", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "audio", + "style": { + "stroke": "#F472B6" + }, + "target": "node-04", + "targetHandle": "audio", + "type": "default" + }, + { + "className": "category-audio_diffusion_pipeline", + "data": { + "connectionType": "audio_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#E879F9", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "pipeline", + "style": { + "stroke": "#E879F9" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-08", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-06", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-08", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-09", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-03", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-09", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-10", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-09", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "b5d0290ec03e427890d74a3ffaf4f1ff4aeb92173408b8f8480e97d3dc1a8e80", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "_native_flash" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "transformer" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "model_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "ExportWithAudio", + "cache": false, + "category": "Video", + "description": "Export composed frames with generated or loaded audio in one MP4.", + "label": "Export Video with Audio", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "audio": { + "display": "input", + "isConnected": true, + "type": [ + "audio", + "str" + ] + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "type": "float" + }, + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 16, + "label": "FPS", + "max": 120, + "min": 1, + "step": 0.01, + "type": "float", + "value": 24 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 8, + "display": "slider", + "max": 10, + "min": 1, + "type": "int", + "value": 10 + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str" + ] + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "exportWithAudio", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 2200, + "y": 491 + }, + "type": "custom" + }, + { + "data": { + "action": "FitDuration", + "cache": false, + "category": "Audio", + "description": "Fit a source window to an exact timeline without changing pitch.", + "label": "Fit Audio Duration", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Audio", + "params": { + "audio": { + "display": "input", + "isConnected": true, + "label": "Audio", + "type": [ + "audio", + "str" + ] + }, + "delay_seconds": { + "default": 0, + "label": "Delay", + "step": 0.001, + "type": "float", + "value": 0.16666666666666666 + }, + "duration": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "fade_in_seconds": { + "default": 0, + "label": "Fade In", + "min": 0, + "step": 0.001, + "type": "float", + "value": 0.008 + }, + "fade_out_seconds": { + "default": 0, + "label": "Fade Out", + "min": 0, + "step": 0.001, + "type": "float", + "value": 0.12 + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "sample_rate": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + }, + "source_duration_seconds": { + "default": 0, + "label": "Source Duration", + "min": 0, + "step": 0.001, + "type": "float", + "value": 5.041666666666667 + }, + "source_start_seconds": { + "default": 0, + "label": "Source Start", + "min": 0, + "step": 0.001, + "type": "float", + "value": 1 + }, + "stretch_engine": { + "display": "output", + "isConnected": false, + "label": "Stretch Engine", + "type": "str" + }, + "target_duration_seconds": { + "default": 5, + "label": "Target Duration", + "min": 0.001, + "step": 0.001, + "type": "float", + "value": 5.041666666666667 + }, + "target_sample_rate": { + "default": 48000, + "label": "Target SR", + "max": 192000, + "min": 8000, + "type": "int", + "value": 48000 + }, + "tempo_ratio": { + "display": "output", + "isConnected": false, + "label": "Tempo Ratio", + "type": "float" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "soundtrackAudioFit", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 1760, + "y": 435 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Audio", + "description": "Generate audio with a Diffusers audio pipeline.", + "label": "Diffusers Audio Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "audio": { + "display": "output", + "isConnected": true, + "label": "Audio", + "type": "audio" + }, + "audio_cover_strength": { + "default": 0.85, + "display": "slider", + "label": "Cover Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float" + }, + "audio_duration": { + "default": 30, + "label": "Duration", + "max": 240, + "min": 1, + "step": 0.5, + "type": "float", + "value": 12 + }, + "bpm": { + "default": 0, + "label": "BPM", + "max": 400, + "min": 0, + "type": "int", + "value": 84 + }, + "duration_seconds": { + "display": "output", + "isConnected": false, + "label": "Duration", + "type": "float" + }, + "extension_duration": { + "default": 15, + "label": "Extension", + "max": 180, + "min": 1, + "step": 0.5, + "type": "float" + }, + "guidance_scale": { + "default": 1, + "description": "XL Turbo is guidance-distilled; values above 1 are ignored by the Diffusers pipeline.", + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "keyscale": { + "default": "", + "label": "Key", + "type": "string", + "value": "E minor" + }, + "lora_scale": { + "default": 1, + "description": "Per-generation ACE-Step LoRA multiplier from Diffusers attention_kwargs.", + "display": "slider", + "label": "LoRA call strength", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float" + }, + "lyrics": { + "default": "", + "display": "textarea", + "label": "Lyrics", + "type": "text", + "value": "" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "fingerpicking, fingerpicked riff, arpeggio, arpeggiated guitar, lead guitar, lead melody, guitar solo, single-note melody, sparse picking, gentle picking, vocals, singing, speech, drums, percussion, bass guitar, full band, synthesizer, electronic beat, applause, crowd cheering, silence, pause, breakdown, cadence, final chord, early resolution, early decay, early ending, long intro, fade-in, fade-out, clipping, abrupt cutoff" + }, + "num_inference_steps": { + "default": 8, + "description": "ACE-Step v1.5 XL Turbo is designed for 8 denoising steps.", + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "num_waveforms": { + "default": 1, + "label": "Variations", + "max": 8, + "min": 1, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "audio_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Instrumental diegetic solo acoustic-guitar soundtrack matching existing subway footage. For the entire twelve seconds, perform forceful uninterrupted 84 BPM eighth-note down-up chord strumming on one warm steel-string acoustic guitar. Start the established strumming pattern immediately and keep its energy, tempo, and full-chord attack constant past ten seconds. Use a clear alternating downstroke-upstroke pick pattern, realistic pick attack, and natural fret noise, with no pauses or melodic detours. Do not cadence, resolve, decay, slow down, thin out, or stop early; the usable soundtrack will be cut from the middle of this continuous performance. Keep subtle station room ambience far beneath the guitar. No fingerpicking, arpeggio, lead melody, vocals, spoken words, drums, percussion, bass, band, synthesizer, applause, crowd cheering, fade-in, fade-out, or early ending." + }, + "reference_audio": { + "display": "input", + "isConnected": false, + "label": "Reference Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "repainting_end": { + "default": 0, + "label": "Repaint End", + "min": 0, + "step": 0.01, + "type": "float" + }, + "repainting_start": { + "default": 0, + "label": "Repaint Start", + "min": 0, + "step": 0.01, + "type": "float" + }, + "return_continuation_tail": { + "default": true, + "label": "Return Tail Only", + "type": "bool" + }, + "sample_rate": { + "default": 48000, + "label": "Sample Rate", + "options": { + "44100": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "44.1 kHz", + "schemaVersion": 1, + "value": "44100" + }, + "48000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "48 kHz", + "schemaVersion": 1, + "value": "48000" + }, + "88200": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "88.2 kHz", + "schemaVersion": 1, + "value": "88200" + }, + "96000": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "96 kHz", + "schemaVersion": 1, + "value": "96000" + } + }, + "type": "int" + }, + "sample_rate_out": { + "display": "output", + "isConnected": false, + "label": "Sample Rate", + "type": "int" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": 1684710282 + }, + "shift": { + "default": 3, + "display": "slider", + "label": "Shift", + "max": 10, + "min": 0, + "step": 0.1, + "type": "float" + }, + "source_audio": { + "display": "input", + "isConnected": false, + "label": "Source Audio", + "required": false, + "type": [ + "audio", + "str" + ] + }, + "stable_audio_guidance": { + "default": 7, + "label": "Stable Audio Guidance", + "max": 20, + "min": 0, + "type": "float", + "value": 1 + }, + "stable_audio_steps": { + "default": 100, + "label": "Stable Audio Steps", + "max": 300, + "min": 1, + "type": "int", + "value": 8 + }, + "task_type": { + "default": "text2music", + "label": "Task", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text2music", + "schemaVersion": 1, + "value": "text2music" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "repaint", + "schemaVersion": 1, + "value": "repaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "continuation", + "schemaVersion": 1, + "value": "continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "extract", + "schemaVersion": 1, + "value": "extract" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "lego", + "schemaVersion": 1, + "value": "lego" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "complete", + "schemaVersion": 1, + "value": "complete" + } + ], + "type": "string", + "value": "text2music" + }, + "timesignature": { + "default": "4", + "label": "Time", + "type": "string", + "value": "4/4" + }, + "vocal_language": { + "default": "en", + "label": "Language", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "soundtrackGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 692 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Audio", + "description": "Load a generic Diffusers audio pipeline.", + "label": "Load Diffusers Audio Pipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersAudio", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_audio", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_audio", + "schemaVersion": 1, + "value": "text_to_audio" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_variation", + "schemaVersion": 1, + "value": "audio_variation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_continuation", + "schemaVersion": 1, + "value": "audio_continuation" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "audio_repaint", + "schemaVersion": 1, + "value": "audio_repaint" + } + ], + "type": "string", + "value": "text_to_video" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "ACE-Step/acestep-v15-xl-turbo-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "model_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "audio_diffusion_pipeline" + }, + "pipeline_class": { + "default": "AceStepPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "AceStepPipeline", + "schemaVersion": 1, + "value": "AceStepPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "StableAudioPipeline", + "schemaVersion": 1, + "value": "StableAudioPipeline" + } + ], + "type": "string", + "value": "AceStepPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "200ba991ae448051e14b0183157e35c2d27c9fb0" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "soundtrackPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 880, + "y": 664 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "soundtrackQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 0, + "y": 692 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "_native_math" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool" + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool" + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "model_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "soundtrackRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-08", + "position": { + "x": 440, + "y": 692 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 24 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 5 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 704 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "text_to_video" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "deformity, deformed anatomy, deformed limbs, deformed wheels, deformed rigid-body geometry, identity drift, 色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 121 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 50 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Low contrast. In a retro 1970s-style subway station, a street musician plays in dim colors and rough textures. He wears an old jacket, playing guitar with focus. Commuters hurry by, and a small crowd gathers to listen. The camera slowly moves right, capturing the blend of music and city noise, with old subway signs and mottled walls in the background." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": false, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 8 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 898471028164125 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 5 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": false, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1280 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-09", + "position": { + "x": 1320, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Wan-AI/Wan2.2-TI2V-5B-Diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "model_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "WanTI2VPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "b8fff7315c768468a5333511427288870b2e9635" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-10", + "position": { + "x": 880, + "y": 206 + }, + "type": "custom" + } + ], + "viewport": { + "x": 356.344909234412, + "y": 43, + "zoom": 0.2123125493291239 + } +} diff --git a/data/graphs/studio/wan-vacepipeline/control-to-video.json b/data/graphs/studio/wan-vacepipeline/control-to-video.json new file mode 100644 index 0000000..9d19ca7 --- /dev/null +++ b/data/graphs/studio/wan-vacepipeline/control-to-video.json @@ -0,0 +1,2247 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-08", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "video", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-04", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "output", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-07", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-06", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-05", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-07", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "807c5952b0659ddefe1d6505e8facd866afe543913844df3cf38bb14e8d7c0f5", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Video", + "description": "Load a video from a file path.", + "label": "Load Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "video" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "videos/wan_vace_grayscale_control.control_video_Pg_eqh.mp4" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File Name", + "type": "str" + }, + "fps": { + "display": "output", + "isConnected": false, + "label": "FPS", + "type": "float" + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "label": { + "display": "ui_label", + "value": "Load Video" + }, + "video": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadControlVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 505 + }, + "type": "custom" + }, + { + "data": { + "action": "Normalize", + "cache": false, + "category": "Video Conditioning", + "description": "Trim, resize, crop, and normalize a video frame list for video models.", + "label": "Normalize Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.VideoConditioning", + "params": { + "fit": { + "default": "cover", + "label": "Fit", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "contain", + "schemaVersion": 1, + "value": "contain" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "stretch", + "schemaVersion": 1, + "value": "stretch" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "type": "int", + "value": 81 + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Video", + "type": [ + "video", + "image" + ] + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "normalizeVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 692 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 318 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 5 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "control_to_video" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "deformity, deformed anatomy, deformed limbs, deformed wheels, deformed rigid-body geometry, identity drift, ignored control video, motion drift, camera-path deviation, broken rail, unstable silhouette, changing track count, frozen mist, reversed travel, shrinking tunnel, flicker, jitter, frame tear, illustration, animation, synthetic render, text, low detail, watermark" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 81 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 30 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Control task: reconstruct the supplied moving grayscale street segmentation clip as a photoreal protected urban cycle-lane point-of-view while following every curb, paving boundary, parked scooter silhouette, tree, façade, timing and camera movement frame for frame. Control roles: let the grayscale clip determine geometry, scale, forward travel and occlusion. Use text only for real materials and light: pale concrete cycle lane, red-brown brick sidewalk, dark asphalt, a parked black scooter at the right curb, leafy street trees and warm clear-morning sunlight. Camera and motion: preserve the exact low forward point of view. Near paving seams and curb edges move beneath the camera while the distant street grows continuously; the parked scooter passes the right edge according to the control sequence. Lighting and continuity: keep natural exposure, stable horizon, crisp lane boundaries and layered parallax across the full ride. Shadows remain attached to their objects and move only with the source camera. Output contract: one believable real city cycling-lane inspection with obvious forward travel; no static hold, moving parked scooter, changing curb layout, flicker, camera reversal, lettering, illustration or synthetic render." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": false, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8207 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 5 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-07", + "position": { + "x": 1320, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Wan-AI/Wan2.1-VACE-1.3B-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "WanVACEPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "ec4d2cb062b548996b179d493fdd05340de702a1" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-08", + "position": { + "x": 880, + "y": 290 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 57.85003900156005, + "zoom": 0.4122464898595944 + } +} diff --git a/data/graphs/studio/wan-vacepipeline/text-to-video.json b/data/graphs/studio/wan-vacepipeline/text-to-video.json new file mode 100644 index 0000000..5cb5c82 --- /dev/null +++ b/data/graphs/studio/wan-vacepipeline/text-to-video.json @@ -0,0 +1,2008 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-06", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-04", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-03", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "835989de85eaec4a4f81449e7ca8f05d637197c04fa17c66beedc547f86404fc", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "model_cpu" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 1760, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 2200, + "y": 145 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 5 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "text_to_video" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "deformity, deformed rollers, painting, illustration, synthetic render, low resolution, static frame, frozen action, slow motion, motionless paper, locked camera, camera pause, weak parallax, flicker, temporal jitter, warped cylinder, changing roller size, loose paper, torn paper, duplicate hardware, impossible paper contact, reversed travel, abrupt camera jump, text, subtitle, logo, watermark" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 81 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 30 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Photoreal printworks documentary called The First Fold, one continuous five-second process shot with immediate readable action. A wide unprinted white paper web streams continuously from the upper-left feed roller, passes through two large counter-rotating steel cylinders at frame center, and exits as evenly folded blank sections toward the lower-right delivery belt. The cylinders make several visibly complete rotations while a stabilized close side camera trucks briskly beside the press; safety rails and fixed vertical frame posts sweep across the foreground in strong parallax. Keep one rigid press, attached rollers, taut paper, believable contact and the same feed direction from opening to closing. Cool industrial daylight, oily steel and natural motion blur. No people, hands, printed words, newspaper headline, loose sheet, torn paper, duplicate press, moving frame, cut, reverse motion or static hold." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": false, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8231 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 5 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": false, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": true + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Wan-AI/Wan2.1-VACE-1.3B-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "model_cpu" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "WanVACEPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "ec4d2cb062b548996b179d493fdd05340de702a1" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-06", + "position": { + "x": 880, + "y": 117 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 47.64279918864099, + "zoom": 0.4288032454361055 + } +} diff --git a/data/graphs/studio/wan-vacepipeline/video-inpaint.json b/data/graphs/studio/wan-vacepipeline/video-inpaint.json new file mode 100644 index 0000000..9fe2d0f --- /dev/null +++ b/data/graphs/studio/wan-vacepipeline/video-inpaint.json @@ -0,0 +1,2473 @@ +{ + "edges": [ + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "output", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-09", + "targetHandle": "mask", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-03", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-10", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "video", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-01", + "targetHandle": "mask", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "video", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-06", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "output", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-01", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "output", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-09", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-08", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-08", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-09", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-09", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-10", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-10", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-09", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "7554ecbc1c77f0eb7f594c1a7d63d7500811553c7197598721e16a65334ed239", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "AlignMask", + "cache": false, + "category": "Video Conditioning", + "description": "Resize a mask video to match a source video and repeat/sampling frames as needed.", + "label": "Align Video Mask", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.VideoConditioning", + "params": { + "grow_pixels": { + "default": 0, + "label": "Grow generated region", + "max": 256, + "min": 0, + "type": "int", + "value": 96 + }, + "invert": { + "default": false, + "label": "Invert mask", + "type": "bool" + }, + "mask": { + "display": "input", + "isConnected": true, + "label": "Mask", + "type": [ + "video", + "image" + ] + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Mask video", + "type": "video" + }, + "threshold": { + "default": 127, + "display": "slider", + "label": "Threshold", + "max": 255, + "min": 0, + "type": "int", + "value": 127 + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Source video", + "type": "video" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "alignMaskVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 880, + "y": 617 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 440, + "y": 28 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Video", + "description": "Load a video from a file path.", + "label": "Load Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "video" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File Name", + "type": "str" + }, + "fps": { + "display": "output", + "isConnected": false, + "label": "FPS", + "type": "float" + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "label": { + "display": "ui_label", + "value": "Load Video" + }, + "video": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadMaskVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 720 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Video", + "description": "Load a video from a file path.", + "label": "Load Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "video" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File Name", + "type": "str" + }, + "fps": { + "display": "output", + "isConnected": false, + "label": "FPS", + "type": "float" + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "label": { + "display": "ui_label", + "value": "Load Video" + }, + "video": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Normalize", + "cache": false, + "category": "Video Conditioning", + "description": "Trim, resize, crop, and normalize a video frame list for video models.", + "label": "Normalize Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.VideoConditioning", + "params": { + "fit": { + "default": "cover", + "label": "Fit", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "contain", + "schemaVersion": 1, + "value": "contain" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "stretch", + "schemaVersion": 1, + "value": "stretch" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "type": "int", + "value": 161 + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Video", + "type": [ + "video", + "image" + ] + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "normalizeVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 440, + "y": 720 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 1760, + "y": 374 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-08", + "position": { + "x": 2200, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 5 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": true, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "video_inpaint" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "deformity, deformed anatomy, deformed limbs, deformed wheels, deformed rigid-body geometry, identity drift, mask bleed, changed unmasked pixels, edge halo, texture drag, color spill, flicker, camera jitter, changing vessel shape, closed rim, floating vessel, wrong scale, duplicate object, melting corner, reflection popping, text, temporal noise, low detail" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 161 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 30 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Mask task: throughout the supplied product-orbit clip, replace only the square glass vessel inside the mask with one translucent amber vessel; preserve every unmasked stone edge, tabletop, background, cast-shadow direction, camera orbit, crop and timing. Replacement identity: one rigid open-top square vessel with the source rim opening, wall thickness, base thickness, height, width and grounded position. Use warm amber glass with restrained refraction and no label or text. Reference authority: use the supplied amber-vessel image for color, material, rim, corner and base identity while the source video remains authoritative for scale, pose, camera orbit and surrounding scene. Temporal integration: preserve the exact orbit and apparent scale while reflections travel continuously around four stable faces. Keep the same single amber vessel from first frame to last; do not inherit the source green-to-blue color transition. Boundary behavior: feather only the mask edge and prevent halo trails, texture drag, color spill or changes outside the mask. The pale stone slab must remain rigid and fully connected beneath the vessel. End state: one amber square vessel remains grounded with unchanged proportions, rim, base and contact shadow; no mask bleed, duplicate, melting corner, frame-wise redesign or floating object." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": false, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8217 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 5 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-09", + "position": { + "x": 1320, + "y": 201 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Wan-AI/Wan2.1-VACE-1.3B-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "WanVACEPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "ec4d2cb062b548996b179d493fdd05340de702a1" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-10", + "position": { + "x": 880, + "y": 159 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 48.60448666127729, + "zoom": 0.4272433306386419 + } +} diff --git a/data/graphs/studio/wan-vacepipeline/video-outpaint.json b/data/graphs/studio/wan-vacepipeline/video-outpaint.json new file mode 100644 index 0000000..06fbbce --- /dev/null +++ b/data/graphs/studio/wan-vacepipeline/video-outpaint.json @@ -0,0 +1,2473 @@ +{ + "edges": [ + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "output", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-09", + "targetHandle": "mask", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-03", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-10", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "video", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-01", + "targetHandle": "mask", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "video", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-06", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "output", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-01", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "output", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-09", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-08", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-08", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-09", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-09", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-07", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-10", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-10", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-09", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "7554ecbc1c77f0eb7f594c1a7d63d7500811553c7197598721e16a65334ed239", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "AlignMask", + "cache": false, + "category": "Video Conditioning", + "description": "Resize a mask video to match a source video and repeat/sampling frames as needed.", + "label": "Align Video Mask", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.VideoConditioning", + "params": { + "grow_pixels": { + "default": 0, + "label": "Grow generated region", + "max": 256, + "min": 0, + "type": "int", + "value": 0 + }, + "invert": { + "default": false, + "label": "Invert mask", + "type": "bool" + }, + "mask": { + "display": "input", + "isConnected": true, + "label": "Mask", + "type": [ + "video", + "image" + ] + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Mask video", + "type": "video" + }, + "threshold": { + "default": 127, + "display": "slider", + "label": "Threshold", + "max": 255, + "min": 0, + "type": "int", + "value": 127 + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Source video", + "type": "video" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "alignMaskVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 880, + "y": 617 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 0, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 440, + "y": 28 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Video", + "description": "Load a video from a file path.", + "label": "Load Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "video" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "videos/wan_vace_outpaint_reframe.mask_video_z6gwBc.mp4" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File Name", + "type": "str" + }, + "fps": { + "display": "output", + "isConnected": false, + "label": "FPS", + "type": "float" + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "label": { + "display": "ui_label", + "value": "Load Video" + }, + "video": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadMaskVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 0, + "y": 720 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Video", + "description": "Load a video from a file path.", + "label": "Load Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "video" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "videos/wan_vace_outpaint_reframe.source_video_EAg4M7.mp4" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File Name", + "type": "str" + }, + "fps": { + "display": "output", + "isConnected": false, + "label": "FPS", + "type": "float" + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "label": { + "display": "ui_label", + "value": "Load Video" + }, + "video": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 0, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Normalize", + "cache": false, + "category": "Video Conditioning", + "description": "Trim, resize, crop, and normalize a video frame list for video models.", + "label": "Normalize Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.VideoConditioning", + "params": { + "fit": { + "default": "cover", + "label": "Fit", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "contain", + "schemaVersion": 1, + "value": "contain" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "stretch", + "schemaVersion": 1, + "value": "stretch" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "type": "int", + "value": 81 + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Video", + "type": [ + "video", + "image" + ] + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "normalizeVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 440, + "y": 720 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-07", + "position": { + "x": 1760, + "y": 374 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-08", + "position": { + "x": 2200, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 5 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": true, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "video_outpaint" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "deformity, deformed anatomy, deformed limbs, deformed wheels, deformed rigid-body geometry, identity drift, changed central frame, repeated borders, mirrored forest, stretched rail, warped perspective, broken parallax, crawling seam, frozen side panel, flicker, mismatched lighting, exposure pumping, smeared sleepers, text, unstable geometry, low detail" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 81 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 30 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Canvas task: extend the supplied moving orange laboratory-robot clip laterally into a wider workcell view while treating the complete center strip as immutable source content. Preservation contract: keep the exact orange arm, joints, cables, gripper, blue-capped vial, orange rack, black bench, white wall, camera, timing and center perspective unchanged. New side content: continue the same bench and white safety enclosure through both boundaries, adding only plausible fixed cable routing and the cropped edges of the existing workcell. Match lens perspective, scale, neutral light and contact shadows. Temporal behavior: generated side panels remain spatially locked while the preserved robot completes its reach. Both vertical seams remain stable and invisible through the full action. Output contract: one coherent wider robotics workcell; no extra arm, duplicate gripper, duplicated vial, mirrored bench, stretched center, perspective jump, frozen seam, flicker, border crawl, person or text." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": false, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8205 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 5 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-09", + "position": { + "x": 1320, + "y": 201 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Wan-AI/Wan2.1-VACE-1.3B-diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "WanVACEPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "ec4d2cb062b548996b179d493fdd05340de702a1" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-10", + "position": { + "x": 880, + "y": 159 + }, + "type": "custom" + } + ], + "viewport": { + "x": 217.24727793696275, + "y": 43, + "zoom": 0.30830945558739253 + } +} diff --git a/data/graphs/studio/wan-video-pipeline/text-to-video.json b/data/graphs/studio/wan-video-pipeline/text-to-video.json new file mode 100644 index 0000000..b37f0e3 --- /dev/null +++ b/data/graphs/studio/wan-video-pipeline/text-to-video.json @@ -0,0 +1,2008 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-06", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-04", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-03", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-06", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "835989de85eaec4a4f81449e7ca8f05d637197c04fa17c66beedc547f86404fc", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "_native_flash" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "transformer" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 1760, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 2200, + "y": 145 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 4.5 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "text_to_video" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "deformity, deformed anatomy, deformed limbs, deformed wheels, deformed rigid-body geometry, identity drift, static camera, frozen frame, erratic camera, abrupt pan, zoom, cut, moving horizon, terrain geometry drift, warped geology, duplicated ridge, fog popping, flicker, frame tearing, exposure pumping, fake lightning, oversaturated color, illustration, animation, watermark" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 81 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 50 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Create one five-second photographic documentary shot of an approaching storm crossing volcanic highlands. A shoulder-height camera tracks rapidly beside wind-bent silver tussock grass on an exposed upland trail; close stalks sweep both card edges while full-field gust waves and a dark rain curtain advance across stable distant hills. Use restrained natural color, soft storm daylight, realistic motion blur, stable geology and plausible rain and plant dynamics. No people, animals, buildings, vehicles, lightning bolts, fantasy color, corrupted dark bands or static-image hold." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": false, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8201 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.8 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 4.5 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": false, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "WanPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "0fad780a534b6463e45facd96134c9f345acfa5b" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-06", + "position": { + "x": 880, + "y": 117 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 47.64279918864099, + "zoom": 0.4288032454361055 + } +} diff --git a/data/graphs/studio/wan-video-pipeline/video-color-edit.json b/data/graphs/studio/wan-video-pipeline/video-color-edit.json new file mode 100644 index 0000000..eae123c --- /dev/null +++ b/data/graphs/studio/wan-video-pipeline/video-color-edit.json @@ -0,0 +1,2247 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-08", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "video", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-04", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "output", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-07", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-06", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-05", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-07", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "807c5952b0659ddefe1d6505e8facd866afe543913844df3cf38bb14e8d7c0f5", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Video", + "description": "Load a video from a file path.", + "label": "Load Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "video" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "videos/wan_vace_video_color_grade.source_video_zvmdxm.mp4" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File Name", + "type": "str" + }, + "fps": { + "display": "output", + "isConnected": false, + "label": "FPS", + "type": "float" + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "label": { + "display": "ui_label", + "value": "Load Video" + }, + "video": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 505 + }, + "type": "custom" + }, + { + "data": { + "action": "Normalize", + "cache": false, + "category": "Video Conditioning", + "description": "Trim, resize, crop, and normalize a video frame list for video models.", + "label": "Normalize Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.VideoConditioning", + "params": { + "fit": { + "default": "cover", + "label": "Fit", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "contain", + "schemaVersion": 1, + "value": "contain" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "stretch", + "schemaVersion": 1, + "value": "stretch" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "type": "int", + "value": 81 + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Video", + "type": [ + "video", + "image" + ] + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "normalizeVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 692 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 318 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.2 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 5 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "video_color_edit" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "deformity, deformed anatomy, deformed limbs, deformed wheels, deformed rigid-body geometry, identity drift, content regeneration, changed press geometry, changed platen diameter, duplicate cylinder, moved machinery housing, invented gauge, hand, person, label, changed camera path, flicker, exposure pumping, color crawling, temporal halo, warped motion, frame tearing, cyan steel, oversaturated red, crushed shadows, clipped metal highlights, plastic texture, watermark" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 81 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 50 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Task: apply a cool high-speed materials-laboratory grade to the complete supplied hydraulic compression test; this is a color-and-tone finish only, not content regeneration. Preservation contract: keep the exact steel press, red test cylinder, circular platens, green safety wall, compression timing, deformation, fragments, locked camera, crop and motion blur unchanged from first frame to last. Grade design: render machined steel neutral and crisp, keep the specimen safety red, mute the green wall, cool the shadows slightly, protect bright metal highlights and retain detail inside the crushed material. Do not invent gauges, hands, labels, tools or another specimen. Temporal behavior: exposure, white balance, saturation and local contrast must remain continuous as the press descends and the specimen fails; prevent pumping, edge halos or color crawling around fragments. Output contract: the identical compression action with a clearly cooler premium laboratory finish, stable machine geometry and natural material color; no geometry changes, crushed blacks, clipped steel, cyan metal or oversaturation." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": false, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8203 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.2 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 5 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-07", + "position": { + "x": 1320, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "WanVideoToVideoPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "0fad780a534b6463e45facd96134c9f345acfa5b" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-08", + "position": { + "x": 880, + "y": 290 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 57.85003900156005, + "zoom": 0.4122464898595944 + } +} diff --git a/data/graphs/studio/wan-video-pipeline/video-to-video.json b/data/graphs/studio/wan-video-pipeline/video-to-video.json new file mode 100644 index 0000000..a0505e9 --- /dev/null +++ b/data/graphs/studio/wan-video-pipeline/video-to-video.json @@ -0,0 +1,2247 @@ +{ + "edges": [ + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-02", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-08", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "video", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-04", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "output", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-07", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-union-image-video", + "data": { + "connectionType": [ + "image", + "video" + ] + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "hsl(312 74% 67%)", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "hsl(312 74% 67%)" + }, + "target": "node-06", + "targetHandle": "video", + "type": "default" + }, + { + "className": "category-video", + "data": { + "connectionType": "video" + }, + "edgeType": "default", + "id": "edge-06", + "markerEnd": { + "color": "#06B6D4", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-07", + "sourceHandle": "video_out", + "style": { + "stroke": "#06B6D4" + }, + "target": "node-05", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-video_diffusion_pipeline", + "data": { + "connectionType": "video_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-07", + "markerEnd": { + "color": "#F97316", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-08", + "sourceHandle": "pipeline", + "style": { + "stroke": "#F97316" + }, + "target": "node-07", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "807c5952b0659ddefe1d6505e8facd866afe543913844df3cf38bb14e8d7c0f5", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [ + "transformer" + ] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-01", + "position": { + "x": 0, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-02", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Load", + "cache": false, + "category": "Video", + "description": "Load a video from a file path.", + "label": "Load Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "filebrowser", + "fieldOptions": { + "fileTypes": [ + "video" + ], + "multiple": false + }, + "label": false, + "type": "str", + "value": "videos/wan_vace_video_to_video.source_video_AZXU4V.mp4" + }, + "filename": { + "display": "output", + "isConnected": false, + "label": "File Name", + "type": "str" + }, + "fps": { + "display": "output", + "isConnected": false, + "label": "FPS", + "type": "float" + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "label": { + "display": "ui_label", + "value": "Load Video" + }, + "video": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loadVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 505 + }, + "type": "custom" + }, + { + "data": { + "action": "Normalize", + "cache": false, + "category": "Video Conditioning", + "description": "Trim, resize, crop, and normalize a video frame list for video models.", + "label": "Normalize Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.VideoConditioning", + "params": { + "fit": { + "default": "cover", + "label": "Fit", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cover", + "schemaVersion": 1, + "value": "cover" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "contain", + "schemaVersion": 1, + "value": "contain" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "stretch", + "schemaVersion": 1, + "value": "stretch" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "type": "int", + "value": 81 + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Video", + "type": "video" + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Video", + "type": [ + "video", + "image" + ] + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "normalizeVideo", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 692 + }, + "type": "custom" + }, + { + "data": { + "action": "Upscaler", + "cache": false, + "category": "upscaler", + "description": "", + "label": "Upscale with model", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Spandrel", + "params": { + "device": { + "default": "cuda:0", + "label": "Device", + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string", + "value": "cuda:0" + }, + "downscale": { + "default": 1, + "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor.", + "display": "slider", + "label": "Downscale", + "max": 1, + "min": 0.1, + "step": 0.01, + "type": "float", + "value": 1 + }, + "image": { + "display": "input", + "isConnected": true, + "label": "Image or video frames", + "required": true, + "type": [ + "image", + "video" + ] + }, + "model_id": { + "default": { + "source": "local", + "value": "" + }, + "display": "modelselect", + "fieldOptions": { + "filter": { + "hub": {}, + "local": { + "id": "^upscalers/" + } + }, + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "nateraw/real-esrgan/RealESRGAN_x2plus.pth" + } + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Upscaled frames", + "type": [ + "image", + "video" + ] + }, + "tile_overlap": { + "default": 32, + "description": "Context overlap cropped from each tile boundary before CPU-side stitching.", + "label": "Tile overlap", + "max": 256, + "min": 0, + "step": 8, + "type": "int" + }, + "tile_size": { + "default": 256, + "description": "Input tile size. Use 0 only when full-frame inference is known to fit.", + "label": "Tile size", + "max": 2048, + "min": 0, + "step": 32, + "type": "int" + } + }, + "resizable": false, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "upscaler", + "style": "", + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 346 + }, + "type": "custom" + }, + { + "data": { + "action": "Export", + "cache": false, + "category": "Video", + "description": "Save/Re-encode a video", + "label": "Export Video", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Video", + "params": { + "file": { + "display": "output", + "isConnected": false, + "type": "video" + }, + "filename": { + "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4", + "label": "File", + "type": "str" + }, + "fps": { + "default": 24, + "label": "FPS", + "max": 240, + "min": 1, + "step": 0.01, + "type": "float", + "value": 16 + }, + "frames": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "height": { + "display": "output", + "isConnected": false, + "type": "int" + }, + "preview": { + "dataSource": "file", + "display": "ui_video", + "type": "url" + }, + "quality": { + "default": 5, + "display": "slider", + "max": 10, + "min": 1, + "type": "int" + }, + "video": { + "display": "input", + "isConnected": true, + "type": [ + "video", + "str", + "image" + ] + }, + "width": { + "display": "output", + "isConnected": false, + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "videoExport", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 318 + }, + "type": "custom" + }, + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Video", + "description": "Generate or condition video through the selected family adapter.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "adain_factor": { + "default": 0.25, + "label": "Long Color Consistency", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "attention_kwargs_json": { + "default": "", + "display": "textarea", + "label": "Attention kwargs JSON", + "type": "text", + "value": "" + }, + "background_video": { + "display": "input", + "isConnected": false, + "label": "Background Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "callback_on_step_end_tensor_inputs": { + "default": "latents", + "label": "Callback tensors", + "type": "string" + }, + "conditioning_scale": { + "default": 1, + "display": "slider", + "label": "Conditioning Scale", + "max": 2, + "min": 0, + "step": 0.05, + "type": "float", + "value": 1 + }, + "denoise_strength": { + "default": 1, + "label": "Denoise strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.7 + }, + "face_video": { + "display": "input", + "isConnected": false, + "label": "Face Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "frame_rate": { + "default": 25, + "label": "Frame rate", + "max": 60, + "min": 1, + "type": "int", + "value": 16 + }, + "framepack_sampling": { + "default": "inverted_anti_drifting", + "label": "FramePack Sampling", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inverted_anti_drifting", + "schemaVersion": 1, + "value": "inverted_anti_drifting" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vanilla", + "schemaVersion": 1, + "value": "vanilla" + } + ], + "type": "string" + }, + "frames_out": { + "display": "output", + "isConnected": false, + "label": "Frames", + "type": "int" + }, + "guidance_scale": { + "default": 5, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 5 + }, + "guidance_scale_2": { + "default": 0, + "display": "slider", + "label": "Guidance 2", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 0 + }, + "height": { + "default": 480, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 480 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "last_image": { + "display": "input", + "isConnected": false, + "label": "Optional Last Image", + "required": false, + "type": "image" + }, + "latent_window_size": { + "default": 9, + "label": "FramePack Window", + "max": 32, + "min": 1, + "type": "int" + }, + "latents": { + "display": "input", + "isConnected": false, + "label": "Latents", + "required": false, + "type": "tensor" + }, + "mask": { + "display": "input", + "isConnected": false, + "label": "Mask video", + "required": false, + "type": "video" + }, + "max_sequence_length": { + "default": 512, + "label": "Max sequence length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "mode": { + "default": "text_to_video", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_animate", + "schemaVersion": 1, + "value": "character_animate" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "character_replace", + "schemaVersion": 1, + "value": "character_replace" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_to_video", + "schemaVersion": 1, + "value": "control_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "image_to_video", + "schemaVersion": 1, + "value": "image_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reference_to_video", + "schemaVersion": 1, + "value": "reference_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_video", + "schemaVersion": 1, + "value": "text_to_video" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_color_edit", + "schemaVersion": 1, + "value": "video_color_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_inpaint", + "schemaVersion": 1, + "value": "video_inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_outpaint", + "schemaVersion": 1, + "value": "video_outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "video_to_video", + "schemaVersion": 1, + "value": "video_to_video" + } + ], + "type": "string", + "value": "video_to_video" + }, + "motion_encode_batch_size": { + "default": 1, + "label": "Motion Batch", + "max": 32, + "min": 1, + "type": "int" + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "deformity, deformed anatomy, deformed limbs, deformed wheels, deformed rigid-body geometry, identity drift, changed camera path, changed motion timing, geometry drift, illustration, synthetic render, invented train, people, lettering, flicker, exposure pumping, texture crawl, frozen side region, broken rail, duplicate sleepers, unrequested cuts, watermark" + }, + "negative_prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Negative prompt embeds", + "required": false, + "type": "tensor" + }, + "num_frames": { + "default": 81, + "label": "Frames", + "max": 241, + "min": 1, + "step": 4, + "type": "int", + "value": 81 + }, + "num_inference_steps": { + "default": 30, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 50 + }, + "num_videos_per_prompt": { + "default": 1, + "label": "Videos per prompt", + "max": 1, + "min": 1, + "type": "int" + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "video_diffusion_pipeline" + }, + "pose_video": { + "display": "input", + "isConnected": false, + "label": "Pose Video", + "required": false, + "type": [ + "video", + "str" + ] + }, + "previous_conditioning_frames": { + "default": 1, + "label": "Previous Frames", + "max": 16, + "min": 1, + "type": "int" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Transform the supplied daylight clip of one cat walking left to right across grass into a photoreal rain-dark stone courtyard at blue hour. Preserve the exact same cat, coat markings, body proportions, four-leg gait, head direction, tail motion, screen path, camera, crop, timing and frame cadence. Change the environment and light clearly: replace the green lawn with wet charcoal cobblestones, add restrained warm window reflections behind the cat and a thin natural sheen beneath its paws, while keeping every paw grounded and the animal fully visible. No illustration, different cat, extra limb, missing paw, sliding foot, changing coat, puddle splash, person, text, geometry morph, flicker, frame tearing, frozen region or crawling texture." + }, + "prompt_embeds": { + "display": "input", + "isConnected": false, + "label": "Prompt embeds", + "required": false, + "type": "tensor" + }, + "prompt_segments_json": { + "default": "", + "display": "textarea", + "label": "Timed Prompt Segments", + "type": "text" + }, + "reference_images": { + "display": "input", + "isConnected": false, + "label": "Reference images", + "required": false, + "type": "image" + }, + "scheduler_flow_shift": { + "default": 0, + "label": "Flow Shift", + "max": 32, + "min": 0, + "step": 0.1, + "type": "float", + "value": 3 + }, + "secondary_guidance_scale": { + "default": 3.5, + "label": "Low-noise Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float" + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 9007199254740991, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 8412 + } + }, + "segment_frame_length": { + "default": 77, + "label": "Segment Frames", + "max": 241, + "min": 5, + "type": "int" + }, + "strength": { + "default": 1, + "label": "Condition strength", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float", + "value": 0.7 + }, + "temporal_overlap": { + "default": 24, + "label": "Temporal Overlap", + "max": 128, + "min": 1, + "type": "int" + }, + "temporal_overlap_condition_strength": { + "default": 0.5, + "label": "Overlap Preservation", + "max": 1, + "min": 0, + "step": 0.05, + "type": "float" + }, + "temporal_tile_size": { + "default": 80, + "label": "Temporal Window", + "max": 257, + "min": 17, + "type": "int" + }, + "true_cfg_scale": { + "default": 1, + "label": "True CFG", + "max": 20, + "min": 0, + "type": "float", + "value": 5 + }, + "use_guidance_scale_2": { + "default": false, + "label": "Use guidance 2", + "type": "bool", + "value": false + }, + "video": { + "display": "input", + "isConnected": true, + "label": "Source/control video", + "required": false, + "type": "video" + }, + "video_out": { + "display": "output", + "isConnected": true, + "label": "Video frames", + "type": "video" + }, + "width": { + "default": 832, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 832 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-07", + "position": { + "x": 1320, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Video", + "description": "Load a registered Diffusers video pipeline through a stable facade.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersVideo", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "video_diffusion_pipeline" + }, + "pipeline_class": { + "default": "WanVACEPipeline", + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVACEPipeline", + "schemaVersion": 1, + "value": "WanVACEPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanVideoToVideoPipeline", + "schemaVersion": 1, + "value": "WanVideoToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanPipeline", + "schemaVersion": 1, + "value": "WanPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Wan22Pipeline", + "schemaVersion": 1, + "value": "Wan22Pipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanTI2VPipeline", + "schemaVersion": 1, + "value": "WanTI2VPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanImageToVideoPipeline", + "schemaVersion": 1, + "value": "WanImageToVideoPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "WanAnimatePipeline", + "schemaVersion": 1, + "value": "WanAnimatePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXConditionPipeline", + "schemaVersion": 1, + "value": "LTXConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTXI2VLongMultiPromptPipeline", + "schemaVersion": 1, + "value": "LTXI2VLongMultiPromptPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "LTX2ConditionPipeline", + "schemaVersion": 1, + "value": "LTX2ConditionPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "HunyuanVideoFramepackPipeline", + "schemaVersion": 1, + "value": "HunyuanVideoFramepackPipeline" + } + ], + "type": "string", + "value": "WanVideoToVideoPipeline" + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "0fad780a534b6463e45facd96134c9f345acfa5b" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "wanPipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-08", + "position": { + "x": 880, + "y": 290 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 57.85003900156005, + "zoom": 0.4122464898595944 + } +} diff --git a/data/graphs/studio/zimage-modular-pipeline/text-to-image--fast-lora.json b/data/graphs/studio/zimage-modular-pipeline/text-to-image--fast-lora.json new file mode 100644 index 0000000..8f247c8 --- /dev/null +++ b/data/graphs/studio/zimage-modular-pipeline/text-to-image--fast-lora.json @@ -0,0 +1,1894 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "c59246fe4bfd6442dd261622acc38dfebeb6f72ee7167090a10531826563fe1a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "contact sheet, multiple helmets, duplicate product, cropped helmet, changing shell proportions, extra visor, extra vents, invented controls, text, logo, label, overprocessed texture, clutter, low detail, broken visor, floating product, noisy adapter artifacts" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Create one premium industrial-design hero photograph of a fictional courier helmet named Relay Nine. Show one complete helmet only, centered in a confident helmet-left three-quarter view on a low warm-gray museum plinth. Product identity: compact graphite shell, a continuous narrow amber visor strip, brushed-titanium lower rim, three small rear cooling slots, and one flush circular comms port on the visible side. Keep the helmet physically plausible, wearable, and free of branding, text, or decorative controls. Use a chest-height 70 mm product camera, a soft overhead rectangular key, a narrow cool rim, realistic graphite microtexture, restrained amber transmission, titanium anisotropy, precise seams, a grounded contact shadow, and the pinned realism-LoRA finish. Keep the background quiet and the full silhouette unobstructed." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 7423 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Tongyi-MAI/Z-Image-Turbo" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "ZImagePipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "f332072aa78be7aecdf3ee76d5c247082da564a6" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "youknownothing/v1-realism-v1-adapter-ZIT-lora" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "1fe0487cfe69b31f6d93ec1a1a6e49f75e9ff77adc8d845380ba7352a3931190" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": true + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 0.65 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "v1-realism.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 127.2050681431005, + "zoom": 0.45017035775127767 + } +} diff --git a/data/graphs/studio/zimage-modular-pipeline/text-to-image--z-image-lora-style.json b/data/graphs/studio/zimage-modular-pipeline/text-to-image--z-image-lora-style.json new file mode 100644 index 0000000..671b011 --- /dev/null +++ b/data/graphs/studio/zimage-modular-pipeline/text-to-image--z-image-lora-style.json @@ -0,0 +1,1894 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-06", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-05", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-05", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-05", + "sourceHandle": "output", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "c59246fe4bfd6442dd261622acc38dfebeb6f72ee7167090a10531826563fe1a", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Create a four-panel photoreal documentary style study of the same coastal rescue engineer at a working lifeboat station, using the selected realism LoRA consistently. Identity lock: the same weathered woman in her early forties, short dark curls, small scar above the left eyebrow, navy waterproof jacket with one orange shoulder yoke, gray knit layer and no logo in every panel. Panel plan: waist-up portrait beside the open boathouse door; wide view checking a real orange rescue boat; close hands fastening a steel radio clip; three-quarter portrait in wind-driven spray. Use genuinely different camera distances while preserving face, clothing and station identity. Natural overcast daylight, wet fabric, believable skin pores, salt-stained steel, documentary 35 mm grain and grounded real-location backgrounds. This must look photographed, never like a toy, mascot, illustration, animation or glossy 3D render." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 4210 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1760, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Tongyi-MAI/Z-Image-Turbo" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "ZImagePipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "f332072aa78be7aecdf3ee76d5c247082da564a6" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadAdapter", + "cache": false, + "category": "Diffusers Image", + "description": "Load a LoRA or adapter into a Diffusers image pipeline.", + "label": "Load Diffusers Image Adapter", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "adapter_name": { + "default": "default", + "label": "Adapter name", + "type": "string" + }, + "adapter_path": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Adapter", + "type": "string", + "value": { + "source": "hub", + "value": "youknownothing/v1-realism-v1-adapter-ZIT-lora" + } + }, + "expected_sha256": { + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + "label": "Expected SHA-256", + "type": "string", + "value": "1fe0487cfe69b31f6d93ec1a1a6e49f75e9ff77adc8d845380ba7352a3931190" + }, + "output": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "replace_existing": { + "default": true, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + "label": "Replace existing adapters", + "type": "boolean", + "value": true + }, + "scale": { + "default": 1, + "display": "slider", + "label": "Scale", + "max": 2, + "min": -2, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "weight_name": { + "default": "", + "label": "Weight name", + "type": "string", + "value": "v1-realism.safetensors" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "loraAdapter", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1320, + "y": 159 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-06", + "position": { + "x": 2200, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 127.2050681431005, + "zoom": 0.45017035775127767 + } +} diff --git a/data/graphs/studio/zimage-modular-pipeline/text-to-image.json b/data/graphs/studio/zimage-modular-pipeline/text-to-image.json new file mode 100644 index 0000000..3db25f6 --- /dev/null +++ b/data/graphs/studio/zimage-modular-pipeline/text-to-image.json @@ -0,0 +1,1774 @@ +{ + "edges": [ + { + "className": "category-image", + "data": { + "connectionType": "image" + }, + "edgeType": "default", + "id": "edge-01", + "markerEnd": { + "color": "#60A5FA", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-01", + "sourceHandle": "images", + "style": { + "stroke": "#60A5FA" + }, + "target": "node-05", + "targetHandle": "image", + "type": "default" + }, + { + "className": "category-image_diffusion_pipeline", + "data": { + "connectionType": "image_diffusion_pipeline" + }, + "edgeType": "default", + "id": "edge-02", + "markerEnd": { + "color": "#FB7185", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-02", + "sourceHandle": "pipeline", + "style": { + "stroke": "#FB7185" + }, + "target": "node-01", + "targetHandle": "pipeline", + "type": "default" + }, + { + "className": "category-quantization_config", + "data": { + "connectionType": "quantization_config" + }, + "edgeType": "default", + "id": "edge-03", + "markerEnd": { + "color": "#FDE68A", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-03", + "sourceHandle": "quantization_config", + "style": { + "stroke": "#FDE68A" + }, + "target": "node-04", + "targetHandle": "quantization_config", + "type": "default" + }, + { + "className": "category-diffusers_execution_recipe", + "data": { + "connectionType": "diffusers_execution_recipe" + }, + "edgeType": "default", + "id": "edge-04", + "markerEnd": { + "color": "#C4B5FD", + "height": 18, + "type": "arrowclosed", + "width": 18 + }, + "source": "node-04", + "sourceHandle": "execution_recipe", + "style": { + "stroke": "#C4B5FD" + }, + "target": "node-02", + "targetHandle": "execution_recipe", + "type": "default" + } + ], + "layout": { + "algorithm": "modiff-layered-v1", + "horizontalGap": 140, + "positionHash": "897ca20c9ef2207a48a0ae2cdd5ac826206919e35cba73e0f159296038fdafbe", + "verticalGap": 72 + }, + "nodes": [ + { + "data": { + "action": "Generate", + "cache": false, + "category": "Diffusers Image", + "description": "Generate images from text with a Diffusers image pipeline.", + "label": "Diffusers.Generate", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "guidance_scale": { + "default": 0, + "display": "slider", + "label": "Guidance", + "max": 20, + "min": 0, + "step": 0.1, + "type": "float", + "value": 1 + }, + "height": { + "default": 1024, + "label": "Height", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "height_out": { + "display": "output", + "isConnected": false, + "label": "Height", + "type": "int" + }, + "images": { + "display": "output", + "isConnected": true, + "label": "Images", + "type": "image" + }, + "max_sequence_length": { + "default": 256, + "label": "Max Sequence Length", + "max": 2048, + "min": 1, + "type": "int", + "value": 512 + }, + "negative_prompt": { + "default": "", + "display": "textarea", + "label": "Negative Prompt", + "type": "text", + "value": "" + }, + "num_inference_steps": { + "default": 4, + "display": "slider", + "label": "Steps", + "max": 100, + "min": 1, + "type": "int", + "value": 8 + }, + "output_type": { + "default": "pil", + "label": "Output type", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pil", + "schemaVersion": 1, + "value": "pil" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "np", + "schemaVersion": 1, + "value": "np" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pt", + "schemaVersion": 1, + "value": "pt" + } + ], + "type": "string", + "value": "pil" + }, + "padding_mask_crop": { + "default": 0, + "label": "Padding Mask Crop", + "max": 512, + "min": 0, + "step": 8, + "type": "int" + }, + "pipeline": { + "display": "input", + "isConnected": true, + "label": "Pipeline", + "required": true, + "type": "image_diffusion_pipeline" + }, + "prompt": { + "default": "", + "display": "textarea", + "label": "Prompt", + "type": "text", + "value": "Create a realistic documentary product photograph of one unbranded compact rescue-equipment case used inside a mountain storm lookout. The compact tabletop case has a rigid wet-graphite rectangular shell with softly rounded corners, two small black mechanical latches, one flat burnt-orange silicone pull tab, a continuous dark gasket seam, four tiny rubber feet and no other parts. It is a closed passive case: no lamp, lens, display, dial, antenna, speaker, button, glow or electronic component. Use a natural eye-level 50 mm three-quarter view with the complete case on a scratched timber table, sharp latch and gasket detail, correct feet and contact shadow, and a rain-streaked window with blue-gray mountain weather softly out of focus behind it. Light the scene only with cool overcast window light and weak warm room bounce. Preserve dark graphite material detail, realistic rain beads, restrained highlights and fine film grain, with no cross symbol, branding, lettering, labels, typography or cinematic fantasy glow." + }, + "seed": { + "default": 0, + "display": "random", + "label": "Seed", + "max": 4294967295, + "min": 0, + "type": "int", + "value": { + "isRandom": false, + "value": 4201 + } + }, + "strength": { + "default": 0.8, + "display": "slider", + "label": "Strength", + "max": 1, + "min": 0, + "step": 0.01, + "type": "float", + "value": 0.8 + }, + "width": { + "default": 1024, + "label": "Width", + "max": 2048, + "min": 16, + "step": 16, + "type": "int", + "value": 1024 + }, + "width_out": { + "display": "output", + "isConnected": false, + "label": "Width", + "type": "int" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImageGenerate", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-01", + "position": { + "x": 1320, + "y": 61 + }, + "type": "custom" + }, + { + "data": { + "action": "LoadPipeline", + "cache": false, + "category": "Diffusers Image", + "description": "Load a generic Diffusers image pipeline.", + "label": "Diffusers.LoadPipeline", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersImage", + "params": { + "auto_offload": { + "default": true, + "label": "Auto offload", + "type": "bool", + "value": false + }, + "device": { + "default": "cuda:0", + "label": "Device", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Map", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + } + ], + "type": "string" + }, + "dtype": { + "default": "bfloat16", + "label": "DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "enable_vae_slicing": { + "default": true, + "label": "VAE slicing", + "type": "bool" + }, + "enable_vae_tiling": { + "default": true, + "label": "VAE tiling", + "type": "bool" + }, + "execution_recipe": { + "display": "input", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "low_cpu_mem_usage": { + "default": true, + "label": "Low CPU memory", + "type": "bool" + }, + "mode": { + "default": "text_to_image", + "label": "Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_to_image", + "schemaVersion": 1, + "value": "text_to_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "edit_image", + "schemaVersion": 1, + "value": "edit_image" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "multi_image_reference_edit", + "schemaVersion": 1, + "value": "multi_image_reference_edit" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "inpaint", + "schemaVersion": 1, + "value": "inpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "outpaint", + "schemaVersion": 1, + "value": "outpaint" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "control_image", + "schemaVersion": 1, + "value": "control_image" + } + ], + "type": "string", + "value": "text_to_image" + }, + "model_id": { + "display": "modelselect", + "fieldOptions": { + "noValidation": true, + "sources": [ + "hub", + "local" + ] + }, + "label": "Model", + "type": "string", + "value": { + "source": "hub", + "value": "Tongyi-MAI/Z-Image-Turbo" + } + }, + "offload_mode": { + "default": "model_cpu", + "label": "Offload Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto_cpu", + "schemaVersion": 1, + "value": "auto_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "pipeline": { + "display": "output", + "isConnected": true, + "label": "Pipeline", + "type": "image_diffusion_pipeline" + }, + "pipeline_class": { + "default": "FluxPipeline", + "fieldOptions": { + "noValidation": true + }, + "label": "Pipeline Class", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImagePipeline", + "schemaVersion": 1, + "value": "QwenImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "ZImagePipeline", + "schemaVersion": 1, + "value": "ZImagePipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxPipeline", + "schemaVersion": 1, + "value": "FluxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "Flux2KleinPipeline", + "schemaVersion": 1, + "value": "Flux2KleinPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxImg2ImgPipeline", + "schemaVersion": 1, + "value": "FluxImg2ImgPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxInpaintPipeline", + "schemaVersion": 1, + "value": "FluxInpaintPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxFillPipeline", + "schemaVersion": 1, + "value": "FluxFillPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlPipeline", + "schemaVersion": 1, + "value": "FluxControlPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxControlNetPipeline", + "schemaVersion": 1, + "value": "FluxControlNetPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxKontextPipeline", + "schemaVersion": 1, + "value": "FluxKontextPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "FluxReduxPipeline", + "schemaVersion": 1, + "value": "FluxReduxPipeline" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "QwenImageEditInpaintPipeline", + "schemaVersion": 1, + "value": "QwenImageEditInpaintPipeline" + } + ], + "type": "string", + "value": "ZImagePipeline" + }, + "quantization_mode": { + "default": "none", + "label": "Quantization", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "quantized_components": { + "default": [], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Quantized Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "resolved_artifact": { + "display": "output", + "isConnected": false, + "label": "Resolved Artifact", + "type": "string" + }, + "revision": { + "default": "", + "label": "Revision", + "type": "string", + "value": "f332072aa78be7aecdf3ee76d5c247082da564a6" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersImagePipeline", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom", + "uiState": {} + }, + "id": "node-02", + "position": { + "x": 880, + "y": 33 + }, + "type": "custom" + }, + { + "data": { + "action": "PipelineQuantizationConfigV2", + "cache": false, + "category": "Diffusers Runtime", + "description": "Create a component-selective Diffusers quantization configuration.", + "label": "Pipeline Quantization Config V2", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "backend": { + "default": "none", + "label": "Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_4bit", + "schemaVersion": 1, + "value": "bnb_4bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff has not qualified this backend on the active platform", + "installationState": "unavailable", + "label": "bnb_8bit", + "schemaVersion": 1, + "value": "bnb_8bit" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_float8", + "schemaVersion": 1, + "value": "quanto_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Quanto package is not installed", + "installationState": "unavailable", + "label": "quanto_int8", + "schemaVersion": 1, + "value": "quanto_int8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + "installationState": "unavailable", + "label": "torchao_float8", + "schemaVersion": 1, + "value": "torchao_float8" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "TorchAO package is not installed", + "installationState": "unavailable", + "label": "torchao_int8_weight_only", + "schemaVersion": 1, + "value": "torchao_int8_weight_only" + } + ], + "type": "string", + "value": "none" + }, + "component_overrides": { + "default": "{}", + "display": "textarea", + "label": "Per-component Overrides (JSON)", + "type": "text" + }, + "components": { + "default": [ + "transformer" + ], + "display": "select", + "fieldOptions": { + "multiple": true + }, + "label": "Components", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer", + "schemaVersion": 1, + "value": "transformer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "transformer_2", + "schemaVersion": 1, + "value": "transformer_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder", + "schemaVersion": 1, + "value": "text_encoder" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_encoder_2", + "schemaVersion": 1, + "value": "text_encoder_2" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "vae", + "schemaVersion": 1, + "value": "vae" + } + ], + "type": "string", + "value": [] + }, + "dtype": { + "default": "bfloat16", + "label": "Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + } + ], + "type": "string", + "value": "bfloat16" + }, + "excluded_modules": { + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + "display": "textarea", + "label": "Preserve Modules", + "type": "text" + }, + "quantization_config": { + "display": "output", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersQuantization", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-03", + "position": { + "x": 0, + "y": 173 + }, + "type": "custom" + }, + { + "data": { + "action": "DiffusersExecutionRecipe", + "cache": false, + "category": "Diffusers Runtime", + "description": "Combine load-time and runtime choices into one reusable pipeline recipe.", + "label": "Diffusers Execution Recipe", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.DiffusersRuntime", + "params": { + "attention_backend": { + "default": "auto", + "label": "Attention Backend", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "native", + "schemaVersion": 1, + "value": "native" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_flash", + "schemaVersion": 1, + "value": "_native_flash" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_efficient", + "schemaVersion": 1, + "value": "_native_efficient" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "_native_math", + "schemaVersion": 1, + "value": "_native_math" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires NVIDIA CUDA", + "installationState": "unavailable", + "label": "_native_cudnn", + "schemaVersion": 1, + "value": "_native_cudnn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "flex", + "schemaVersion": 1, + "value": "flex" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "flash-attn package is not installed", + "installationState": "unavailable", + "label": "flash", + "schemaVersion": 1, + "value": "flash" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_hub", + "schemaVersion": 1, + "value": "flash_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires flash-attn", + "installationState": "unavailable", + "label": "flash_varlen", + "schemaVersion": 1, + "value": "flash_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "flash_varlen_hub", + "schemaVersion": 1, + "value": "flash_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels and NVIDIA compute capability 9.0 or newer", + "installationState": "unavailable", + "label": "flash_4_hub", + "schemaVersion": 1, + "value": "flash_4_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3", + "schemaVersion": 1, + "value": "_flash_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the FlashAttention 3 interface on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_varlen_3", + "schemaVersion": 1, + "value": "_flash_varlen_3" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_hub", + "schemaVersion": 1, + "value": "_flash_3_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires Hub kernels on NVIDIA Hopper", + "installationState": "unavailable", + "label": "_flash_3_varlen_hub", + "schemaVersion": 1, + "value": "_flash_3_varlen_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "AITER package is not installed", + "installationState": "unavailable", + "label": "aiter", + "schemaVersion": 1, + "value": "aiter" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "SageAttention package is not installed", + "installationState": "unavailable", + "label": "sage", + "schemaVersion": 1, + "value": "sage" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires the kernels package on NVIDIA CUDA", + "installationState": "unavailable", + "label": "sage_hub", + "schemaVersion": 1, + "value": "sage_hub" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "Requires SageAttention", + "installationState": "unavailable", + "label": "sage_varlen", + "schemaVersion": 1, + "value": "sage_varlen" + }, + { + "availability": "unavailable", + "compatibility": "incompatible", + "disabledReason": "MoDiff only enables xFormers on NVIDIA CUDA", + "installationState": "unavailable", + "label": "xformers", + "schemaVersion": 1, + "value": "xformers" + } + ], + "type": "string", + "value": "auto" + }, + "attention_components": { + "default": "", + "label": "Attention Components", + "type": "string", + "value": "" + }, + "cache_options": { + "default": "{}", + "display": "textarea", + "label": "Cache Options (JSON)", + "type": "text" + }, + "cache_threshold": { + "default": 0.05, + "label": "Cache Threshold", + "min": 0, + "type": "float" + }, + "channels_last": { + "default": false, + "label": "Channels Last", + "type": "bool", + "value": false + }, + "channels_last_components": { + "default": "unet,vae", + "label": "Channels Last Components", + "type": "string" + }, + "compile_backend": { + "default": "inductor", + "label": "Compile Backend", + "type": "string" + }, + "compile_components": { + "default": "transformer", + "label": "Compile Components", + "type": "string" + }, + "compile_dynamic": { + "default": false, + "label": "Compile Dynamic Shapes", + "type": "bool" + }, + "compile_fullgraph": { + "default": false, + "label": "Compile Full Graph", + "type": "bool" + }, + "compile_mode": { + "default": "default", + "label": "Compile Mode", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "default", + "schemaVersion": 1, + "value": "default" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "reduce-overhead", + "schemaVersion": 1, + "value": "reduce-overhead" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "max-autotune", + "schemaVersion": 1, + "value": "max-autotune" + } + ], + "type": "string" + }, + "denoiser_cache": { + "default": "none", + "label": "Denoiser Cache", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "first_block", + "schemaVersion": 1, + "value": "first_block" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "magcache", + "schemaVersion": 1, + "value": "magcache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "taylorseer", + "schemaVersion": 1, + "value": "taylorseer" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "pab", + "schemaVersion": 1, + "value": "pab" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "fastercache", + "schemaVersion": 1, + "value": "fastercache" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "text_kv", + "schemaVersion": 1, + "value": "text_kv" + } + ], + "type": "string", + "value": "none" + }, + "device": { + "default": "cuda:0", + "label": "Execution Device", + "type": "string", + "value": "cuda:0" + }, + "device_map": { + "default": "none", + "label": "Device Placement", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda", + "schemaVersion": 1, + "value": "cuda" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "auto", + "schemaVersion": 1, + "value": "auto" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced", + "schemaVersion": 1, + "value": "balanced" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "balanced_low_0", + "schemaVersion": 1, + "value": "balanced_low_0" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "manual", + "schemaVersion": 1, + "value": "manual" + } + ], + "type": "string", + "value": "none" + }, + "device_map_overrides": { + "default": "{}", + "description": "Used only with manual placement, for example {\"transformer\": 0, \"text_encoder_2\": \"cpu\"}.", + "display": "textarea", + "label": "Manual Device Map (JSON)", + "type": "text" + }, + "execution_recipe": { + "display": "output", + "isConnected": true, + "label": "Execution Recipe", + "type": "diffusers_execution_recipe" + }, + "layerwise_casting": { + "default": false, + "label": "Layerwise Casting", + "type": "bool", + "value": false + }, + "layerwise_casting_components": { + "default": "transformer", + "label": "Layerwise Casting Components", + "type": "string" + }, + "layerwise_compute_dtype": { + "default": "bfloat16", + "label": "Layerwise Compute DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "bfloat16", + "schemaVersion": 1, + "value": "bfloat16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float16", + "schemaVersion": 1, + "value": "float16" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float32", + "schemaVersion": 1, + "value": "float32" + } + ], + "type": "string" + }, + "layerwise_storage_dtype": { + "default": "float8_e4m3fn", + "label": "Layerwise Storage DType", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e4m3fn", + "schemaVersion": 1, + "value": "float8_e4m3fn" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "float8_e5m2", + "schemaVersion": 1, + "value": "float8_e5m2" + } + ], + "type": "string" + }, + "max_memory": { + "default": "{}", + "description": "Optional device limits, for example {\"0\": \"16GiB\", \"cpu\": \"48GiB\"}.", + "display": "textarea", + "label": "Max Memory (JSON)", + "type": "text" + }, + "offload_mode": { + "default": "none", + "label": "Runtime Offload", + "options": [ + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "none", + "schemaVersion": 1, + "value": "none" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "model_cpu", + "schemaVersion": 1, + "value": "model_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "sequential_cpu", + "schemaVersion": 1, + "value": "sequential_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_cpu", + "schemaVersion": 1, + "value": "group_cpu" + }, + { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "group_disk", + "schemaVersion": 1, + "value": "group_disk" + } + ], + "type": "string", + "value": "none" + }, + "quantization_config": { + "display": "input", + "isConnected": true, + "label": "Quant Config", + "type": "quantization_config" + }, + "regional_compile": { + "default": false, + "label": "Regional Compile", + "type": "bool", + "value": false + }, + "summary": { + "display": "output", + "isConnected": false, + "label": "Summary", + "type": "string" + }, + "vae_slicing": { + "default": true, + "label": "VAE Slicing", + "type": "bool", + "value": true + }, + "vae_tiling": { + "default": true, + "label": "VAE Tiling", + "type": "bool", + "value": true + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "diffusersRecipe", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-04", + "position": { + "x": 440, + "y": 0 + }, + "type": "custom" + }, + { + "data": { + "action": "Preview", + "cache": false, + "category": "image", + "description": "Preview an image", + "label": "Preview Image", + "memory": [ + 0, + 0, + 0 + ], + "module": "modules.Image", + "params": { + "device": { + "default": "cuda:0", + "hidden": true, + "options": { + "cpu": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu", + "schemaVersion": 1, + "value": "cpu" + }, + "cpu:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cpu:0", + "schemaVersion": 1, + "value": "cpu:0" + }, + "cuda:0": { + "availability": "installed", + "compatibility": "compatible", + "installationState": "installed", + "label": "cuda:0", + "schemaVersion": 1, + "value": "cuda:0" + } + }, + "type": "string" + }, + "export": { + "default": "0", + "description": "Export the image at the given index. Leave empty to export all images.", + "type": "str" + }, + "filtered": { + "display": "output", + "isConnected": false, + "label": "Selected image", + "type": "image" + }, + "image": { + "display": "input", + "isConnected": true, + "onChange": { + "action": "show", + "condition": { + "type": "latent" + }, + "data": { + "false": [], + "true": [ + "vae", + "device" + ] + } + }, + "type": [ + "image", + "latent" + ] + }, + "output": { + "display": "output", + "isConnected": false, + "label": "All images", + "type": "image" + }, + "preview": { + "dataSource": "output", + "display": "ui_image", + "type": "url" + }, + "vae": { + "display": "input", + "hidden": true, + "isConnected": false, + "label": "VAE", + "type": "pipeline" + } + }, + "resizable": true, + "skipParamsCheck": false, + "studioOwned": true, + "studioRole": "preview", + "style": {}, + "time": [ + 0, + 0, + 0 + ], + "type": "custom" + }, + "id": "node-05", + "position": { + "x": 1760, + "y": 173 + }, + "type": "custom" + } + ], + "viewport": { + "x": 84, + "y": 83.75249868490266, + "zoom": 0.5560231457127828 + } +} diff --git a/data/model-artifact-catalog.json b/data/model-artifact-catalog.json new file mode 100644 index 0000000..81de116 --- /dev/null +++ b/data/model-artifact-catalog.json @@ -0,0 +1,217 @@ +{ + "schemaVersion": 1, + "checkedAt": "2026-07-18T15:24:00+05:30", + "selectionPolicy": { + "popularityMayChangeAutoSelection": false, + "communityRequiresConfirmation": true, + "unpinnedRevisionMayBeAutoSelected": false + }, + "artifactDefaults": { + "revision": null, + "license": "inherit-from-source-model", + "supportedPlatforms": ["windows", "linux", "macos"], + "supportedArchitectures": ["x86_64", "amd64", "arm64", "aarch64"], + "supportedBackends": ["cuda", "rocm", "mps", "directml", "cpu"], + "resourceRequirements": {"source": "studio-profile-and-live-component-inventory"}, + "qualificationEvidence": {"status": "unqualified", "source": "catalog-review-required"} + }, + "repositoryPins": [ + {"repo": "diffusers/FLUX.2-klein-4B-modular", "revision": "62ac375aa5308588f111fcd12115f5c54a8b1f4f", "license": "not-declared", "purpose": "reviewed-dynamic-modular-block", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "lllyasviel/FramePackI2V_HY", "revision": "86cef4396041b6002c957852daac4c91aaa47c79", "license": "not-declared", "purpose": "framepack-transformer", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "hunyuanvideo-community/HunyuanVideo", "revision": "e8c2aaa66fe3742a32c11a6766aecbf07c56e773", "license": "not-declared", "purpose": "framepack-base-components", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "lllyasviel/flux_redux_bfl", "revision": "45b801affc54ff2af4e5daf1b282e0921901db87", "license": "not-declared", "purpose": "framepack-vision-components", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "Runware/acestep-v15-turbo-diffusers", "revision": "be23effe449c5957947f3020fd63bee23c64abe4", "license": "not-declared", "purpose": "shipped-ace-step-lora-base", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "stabilityai/stable-audio-open-1.0", "revision": "f21265c1e2710b3bd2386596943f0007f55f802e", "license": "other", "purpose": "built-in-diffusers-audio-default", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "stabilityai/stable-diffusion-xl-base-1.0", "revision": "462165984030d82259a11f4367a4eed129e94a7b", "license": "openrail++", "purpose": "built-in-modular-default", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers", "revision": "b184e23a8a16b20f108f727c902e769e873ffc73", "license": "apache-2.0", "purpose": "built-in-modular-default", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "Wan-AI/Wan2.2-T2V-A14B-Diffusers", "revision": "5be7df9619b54f4e2667b2755bc6a756675b5cd7", "license": "apache-2.0", "purpose": "built-in-diffusers-video-default", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "Wan-AI/Wan2.2-Animate-14B-Diffusers", "revision": "6f4df10861c758af86ac3c979aacc1bf5c03eff0", "license": "apache-2.0", "purpose": "built-in-diffusers-video-default", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "Lightricks/LTX-2", "revision": "47da56e2ad66ce4125a9922b4a8826bf407f9d0a", "license": "other", "purpose": "built-in-diffusers-video-default", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "Lightricks/LTX-Video", "revision": "8984fa25007f376c1a299016d0957a37a2f797bb", "license": "other", "purpose": "verified-diffusers-fallback", "verifiedAt": "2026-08-03T14:34:36+05:30"}, + {"repo": "fuliucansheng/FLUX.1-Canny-dev-diffusers", "revision": "24df2ba1c46a8c735589cc8f81302678797c9821", "license": "other", "purpose": "verified-diffusers-repair-source", "verifiedAt": "2026-08-03T14:34:36+05:30"} + ], + "models": [ + { + "modelType": "ZImageModularPipeline", + "baseRepo": "Tongyi-MAI/Z-Image-Turbo", + "baseRevision": "f332072aa78be7aecdf3ee76d5c247082da564a6", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "T5B/Z-Image-Turbo-FP8", "revision": "e523072dbf1bcc797fcd1908d331a4905e278ab8", "license": "apache-2.0", "format": "fp8", "bits": 8, "components": ["transformer"], "provenance": "community", "trust": "community", "downloads": 66354, "likes": 172} + ] + }, + { + "modelType": "QwenImageModularPipeline", + "baseRepo": "Qwen/Qwen-Image-2512", + "baseRevision": "25468b98e3276ca6700de15c6628e51b7de54a26", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "unsloth/Qwen-Image-2512-unsloth-bnb-4bit", "revision": "f50b8c24fe21e9265509b15113b7cca82d0a4443", "license": "apache-2.0", "format": "diffusers-bnb", "bits": 4, "components": ["transformer", "text_encoder"], "provenance": "community", "trust": "modiff_qualified", "downloads": 975, "likes": 16} + ] + }, + { + "modelType": "QwenImageEditModularPipeline", + "baseRepo": "Qwen/Qwen-Image-Edit", + "baseRevision": "ac7f9318f633fc4b5778c59367c8128225f1e3de", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "ovedrive/qwen-image-edit-4bit", "revision": "f1895ae1de2ce005a4c96576a900778ec99899de", "license": "apache-2.0", "format": "diffusers-bnb", "bits": 4, "components": ["transformer", "text_encoder"], "provenance": "community", "trust": "community", "downloads": 664, "likes": 37} + ] + }, + { + "modelType": "QwenImageEditPlusModularPipeline", + "baseRepo": "Qwen/Qwen-Image-Edit-2511", + "baseRevision": "6f3ccc0b56e431dc6a0c2b2039706d7d26f22cb9", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "unsloth/Qwen-Image-Edit-2511-GGUF", "revision": "0d33d9692b4b26212297240d87b0d4719aa4fd06", "license": "apache-2.0", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "popular_unverified", "downloads": 181156, "likes": 535}, + {"repo": "1038lab/Qwen-Image-Edit-2511-FP8", "revision": "f0487a2fd231fbf91b5267650cde228aeb4441b4", "license": "apache-2.0", "format": "fp8", "bits": 8, "components": ["transformer"], "provenance": "community", "trust": "popular_unverified", "downloads": 21317, "likes": 50} + ] + }, + { + "modelType": "QwenImageLayeredModularPipeline", + "baseRepo": "Qwen/Qwen-Image-Layered", + "baseRevision": "8f0ca708dfff6ba1dd5f2d85d78f8c108a040bcf", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "unsloth/Qwen-Image-Layered-GGUF", "revision": "762a51bed1f76aec1d8fc9546ca9f705454393fa", "license": "apache-2.0", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "popular_unverified", "downloads": 11829, "likes": 59} + ] + }, + { + "modelType": "WanVACEPipeline", + "baseRepo": "Wan-AI/Wan2.1-VACE-1.3B-diffusers", + "baseRevision": "ec4d2cb062b548996b179d493fdd05340de702a1", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "samuelchristlie/Wan2.1-VACE-1.3B-GGUF", "revision": "839f67aa27bb81ce7f0dc6005132d32df64e14df", "license": "apache-2.0", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "community", "downloads": 888, "likes": 13} + ] + }, + { + "modelType": "WanVideoPipeline", + "baseRepo": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + "baseRevision": "0fad780a534b6463e45facd96134c9f345acfa5b", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "samuelchristlie/Wan2.1-T2V-1.3B-GGUF", "revision": "5a512b15fc35d1b67a074cfe55a591be9e9ef9b5", "license": "apache-2.0", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "popular_unverified", "downloads": 4288, "likes": 24} + ] + }, + { + "modelType": "WanImageToVideoPipeline", + "baseRepo": "Wan-AI/Wan2.2-I2V-A14B-Diffusers", + "baseRevision": "596658fd9ca6b7b71d5057529bbf319ecbc61d74", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "QuantStack/Wan2.2-I2V-A14B-GGUF", "revision": "6c6717459277b9cd1f72579d78a0fd62a79e57dc", "license": "apache-2.0", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "popular_unverified", "downloads": 336115, "likes": 368} + ] + }, + { + "modelType": "WanTI2VPipeline", + "baseRepo": "Wan-AI/Wan2.2-TI2V-5B-Diffusers", + "baseRevision": "b8fff7315c768468a5333511427288870b2e9635", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "QuantStack/Wan2.2-TI2V-5B-GGUF", "revision": "57437632ddd08bdcbd1508c866aa22e126ed51d2", "license": "apache-2.0", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "popular_unverified", "downloads": 29508, "likes": 206} + ] + }, + { + "modelType": "LTXVideoPipeline", + "baseRepo": "Lightricks/LTX-Video-0.9.8-13B-distilled", + "baseRevision": "7c64400e1861cc0d7b98d570a1926d5408ec60cd", + "baseLicense": "other", + "artifacts": [ + {"repo": "QuantStack/LTXV-13B-0.9.8-distilled-GGUF", "revision": "78b86e5a8fb04a3867296a35b83bf1d5c62927c4", "license": "other", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "community", "downloads": 1149, "likes": 24} + ] + }, + { + "modelType": "AceStepAudioPipeline", + "baseRepo": "ACE-Step/acestep-v15-xl-turbo-diffusers", + "baseRevision": "200ba991ae448051e14b0183157e35c2d27c9fb0", + "baseLicense": "mit", + "artifacts": [ + {"repo": "Serveurperso/ACE-Step-1.5-GGUF", "revision": "9b3707625776cc4cf775e9b12ab82f9fe48335ff", "license": "mit", "format": "gguf", "bits": 5, "components": ["language_model"], "provenance": "community", "trust": "popular_unverified", "downloads": 43025, "likes": 80} + ] + }, + { + "modelType": "FluxSchnellPipeline", + "baseRepo": "black-forest-labs/FLUX.1-schnell", + "baseRevision": "741f7c3ce8b383c54771c7003378a50191e9efe9", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "city96/FLUX.1-schnell-gguf", "revision": "f495746ed9c5efcf4661f53ef05401dceadc17d2", "license": "apache-2.0", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "popular_unverified", "downloads": 69959, "likes": 343} + ] + }, + { + "modelType": "FluxDevPipeline", + "baseRepo": "black-forest-labs/FLUX.1-dev", + "baseRevision": "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21", + "baseLicense": "other", + "artifacts": [ + {"repo": "black-forest-labs/FLUX.1-dev-FP8", "revision": "2fcc6a7ddee78972c8834226b37a09cebff1b6de", "license": "other", "format": "fp8", "bits": 8, "components": ["transformer", "text_encoder_2"], "provenance": "official", "trust": "official", "downloads": 0, "likes": 77} + ] + }, + { + "modelType": "FluxKreaPipeline", + "baseRepo": "black-forest-labs/FLUX.1-Krea-dev", + "baseRevision": "8162a9c7b05a641be098422bf2fcf335615c2f28", + "baseLicense": "other", + "artifacts": [ + {"repo": "QuantStack/FLUX.1-Krea-dev-GGUF", "revision": "b17b5df266ee23dcdea70e027173002aac36a1c8", "license": "other", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "popular_unverified", "downloads": 8586, "likes": 141} + ] + }, + { + "modelType": "FluxKontextPipeline", + "baseRepo": "black-forest-labs/FLUX.1-Kontext-dev", + "baseRevision": "24e9dedc4ef646698dc8eb4e18ae2cec3c9fea0d", + "baseLicense": "other", + "artifacts": [ + {"repo": "black-forest-labs/FLUX.1-Kontext-dev-NVFP4", "revision": "7e9dee453a3454251216a394697a6dcea44f54f8", "license": "other", "format": "nvfp4", "bits": 4, "components": ["transformer", "text_encoder_2"], "provenance": "official", "trust": "official", "downloads": 126, "likes": 10} + ] + }, + { + "modelType": "FluxFillPipeline", + "baseRepo": "black-forest-labs/FLUX.1-Fill-dev", + "baseRevision": "358293da0354175698b67ec8299acf928313a78a", + "baseLicense": "other", + "artifacts": [ + {"repo": "YarvixPA/FLUX.1-Fill-dev-GGUF", "revision": "78b83f1da140a4dcd6466580516544e1f6effe3e", "license": "other", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "popular_unverified", "downloads": 38458, "likes": 147} + ] + }, + { + "modelType": "FluxDepthPipeline", + "baseRepo": "black-forest-labs/FLUX.1-Depth-dev", + "baseRevision": "fb5e9b1bae41b8c8adcea4ea2a87b74dd298f07a", + "baseLicense": "other", + "artifacts": [ + {"repo": "SporkySporkness/FLUX.1-Depth-dev-GGUF", "revision": "2a77cd34e8bcb690b3ffce71d3a3239590614ee7", "license": "other", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "community", "downloads": 217, "likes": 25} + ] + }, + { + "modelType": "FluxCannyPipeline", + "baseRepo": "black-forest-labs/FLUX.1-Canny-dev", + "baseRevision": "27c3d8bdc17509b47cf4fd9ba25ab1c7508a69a2", + "baseLicense": "other", + "artifacts": [ + {"repo": "SporkySporkness/FLUX.1-Canny-dev-GGUF", "revision": "584725aadc00e380550eb984f57f66ecb49d10d1", "license": "other", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "community", "downloads": 375, "likes": 14} + ] + }, + { + "modelType": "FluxReduxPipeline", + "baseRepo": "black-forest-labs/FLUX.1-Redux-dev", + "baseRevision": "c95859fbf7703ca4d6824b4da4407d7cd0434f81", + "baseLicense": "other", + "artifacts": [ + {"repo": "second-state/FLUX.1-Redux-dev-GGUF", "revision": "c7e36ea59a409eaa553b9744b53aa350099d5d51", "license": "other", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "community", "downloads": 225, "likes": 14} + ] + }, + { + "modelType": "Flux2KleinPipeline", + "baseRepo": "black-forest-labs/FLUX.2-klein-4B", + "baseRevision": "e7b7dc27f91deacad38e78976d1f2b499d76a294", + "baseLicense": "apache-2.0", + "artifacts": [ + {"repo": "black-forest-labs/FLUX.2-klein-4b-fp8", "revision": "5b4408e59397a4a37ccb46afe426d8ed86379441", "license": "apache-2.0", "format": "fp8", "bits": 8, "components": ["transformer"], "provenance": "official", "trust": "official", "downloads": 86722, "likes": 54}, + {"repo": "unsloth/FLUX.2-klein-4B-GGUF", "revision": "0084d1df98e2e2137fe776d55170bc4792ec1d66", "license": "apache-2.0", "format": "gguf", "bits": 4, "components": ["transformer"], "provenance": "community", "trust": "popular_unverified", "downloads": 153603, "likes": 182} + ] + } + ] +} diff --git a/data/workflow-library-manifest.json b/data/workflow-library-manifest.json new file mode 100644 index 0000000..a1a1279 --- /dev/null +++ b/data/workflow-library-manifest.json @@ -0,0 +1,1479 @@ +{ + "schemaVersion": 1, + "generatedAt": "2026-08-03T09:13:39Z", + "capabilitySchemaVersion": 2, + "capabilitySource": "modiff-backend", + "supportedCanonicalPairCount": 39, + "supportedPairCount": 51, + "workflows": [ + { + "id": "AceStepAudioPipeline:audio_continuation", + "modelType": "AceStepAudioPipeline", + "modelFamily": "ACE Audio", + "mode": "audio_continuation", + "mediaKind": "audio", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "ACE-Step/acestep-v15-xl-turbo-diffusers" + ], + "requiredInputs": { + "requiredAudio": [ + "sourceAudio" + ], + "note": "Requires a source audio clip to continue." + }, + "pipelineClasses": [ + "AceStepPipeline" + ], + "sourceTemplateId": "ace_step_audio_continuation", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/ace-step-audio-pipeline/audio-continuation.json", + "graphHash": "608019534542c088213f7731371dbd66dea934a7e674b459fb1761312d8120a5", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "AceStepAudioPipeline:audio_repaint", + "modelType": "AceStepAudioPipeline", + "modelFamily": "ACE Audio", + "mode": "audio_repaint", + "mediaKind": "audio", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "ACE-Step/acestep-v15-xl-turbo-diffusers" + ], + "requiredInputs": { + "requiredAudio": [ + "sourceAudio" + ], + "note": "Requires source audio plus repaint timing." + }, + "pipelineClasses": [ + "AceStepPipeline" + ], + "sourceTemplateId": "ace_step_audio_repaint", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/ace-step-audio-pipeline/audio-repaint.json", + "graphHash": "f7f833936df140b8359ac9c4196da77bea45ed0745f856cba2512197b738e6aa", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "AceStepAudioPipeline:audio_variation", + "modelType": "AceStepAudioPipeline", + "modelFamily": "ACE Audio", + "mode": "audio_variation", + "mediaKind": "audio", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "ACE-Step/acestep-v15-xl-turbo-diffusers" + ], + "requiredInputs": { + "requiredAudio": [ + "sourceAudio" + ], + "note": "Requires a source audio clip." + }, + "pipelineClasses": [ + "AceStepPipeline" + ], + "sourceTemplateId": "ace_step_audio_variation", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/ace-step-audio-pipeline/audio-variation.json", + "graphHash": "15bd8c5a42e71187a58f316b0d2d8da4b13723e332b543771a4b787bd3b98fc3", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "AceStepAudioPipeline:text_to_audio", + "modelType": "AceStepAudioPipeline", + "modelFamily": "ACE Audio", + "mode": "text_to_audio", + "mediaKind": "audio", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "ACE-Step/acestep-v15-xl-turbo-diffusers" + ], + "requiredInputs": [], + "pipelineClasses": [ + "AceStepPipeline" + ], + "sourceTemplateId": "ace_step_text_to_audio", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/ace-step-audio-pipeline/text-to-audio.json", + "graphHash": "873de53752af7d72b5f2dad14b69d9ad2849184d1b113e5609d1ea5094387027", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "AceStepAudioPipeline:text_to_audio:ace_step_chinese_new_year_lora", + "modelType": "AceStepAudioPipeline", + "modelFamily": "ACE Audio", + "mode": "text_to_audio", + "mediaKind": "audio", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "ACE-Step/acestep-v15-xl-turbo-diffusers" + ], + "requiredInputs": [], + "pipelineClasses": [ + "AceStepPipeline" + ], + "sourceTemplateId": "ace_step_chinese_new_year_lora", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/ace-step-audio-pipeline/text-to-audio--ace-step-chinese-new-year-lora.json", + "graphHash": "049f1fe23c6512ab2df2c1d6ceb35c090bcaeab60e4fcd0a50b8b9f707cf38e8", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "AceStepAudioPipeline:text_to_audio:ace_step_custom_lora", + "modelType": "AceStepAudioPipeline", + "modelFamily": "ACE Audio", + "mode": "text_to_audio", + "mediaKind": "audio", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "ACE-Step/acestep-v15-xl-turbo-diffusers" + ], + "requiredInputs": [], + "pipelineClasses": [ + "AceStepPipeline" + ], + "sourceTemplateId": "ace_step_custom_lora", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/ace-step-audio-pipeline/text-to-audio--ace-step-custom-lora.json", + "graphHash": "d2e0a8a4b1f163fdd47d6114db20d39befd37409df11be1cad5f162f26856452", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "Flux2KleinPipeline:edit_image", + "modelType": "Flux2KleinPipeline", + "modelFamily": "FLUX Image", + "mode": "edit_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.2-klein-4B" + ], + "requiredInputs": { + "requiredImages": [ + "referenceImages" + ], + "note": "Requires one source/reference image." + }, + "pipelineClasses": [ + "Flux2KleinPipeline" + ], + "sourceTemplateId": "flux2_klein_edit", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux2-klein-pipeline/edit-image.json", + "graphHash": "bde529490c40503c1fbd028eb027f45b81926311082e3dcc70d83f1741031b4f", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "Flux2KleinPipeline:multi_image_reference_edit", + "modelType": "Flux2KleinPipeline", + "modelFamily": "FLUX Image", + "mode": "multi_image_reference_edit", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.2-klein-4B" + ], + "requiredInputs": { + "requiredImages": [ + "referenceImages" + ], + "note": "Requires two or more reference images." + }, + "pipelineClasses": [ + "Flux2KleinPipeline" + ], + "sourceTemplateId": "flux2_klein_multi_reference", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux2-klein-pipeline/multi-image-reference-edit.json", + "graphHash": "11d5643693fc264d5c27bc796335a06b6a6f9963cec2324ae33a3d281266dc58", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "Flux2KleinPipeline:text_to_image", + "modelType": "Flux2KleinPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.2-klein-4B" + ], + "requiredInputs": [], + "pipelineClasses": [ + "Flux2KleinPipeline" + ], + "sourceTemplateId": "flux2_klein_text_to_image", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux2-klein-pipeline/text-to-image.json", + "graphHash": "a54ff2b1193ed17823ca6ab8399780e1537a31fbb6c56881b6482145f799ecfd", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxCannyPipeline:control_image", + "modelType": "FluxCannyPipeline", + "modelFamily": "FLUX Image", + "mode": "control_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-Canny-dev" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxControlPipeline" + ], + "sourceTemplateId": "flux_control_canny", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-canny-pipeline/control-image.json", + "graphHash": "bf57016dbf6fd7a310fac137d9713bf3d6521391357b838b321b6de86c2ab8ab", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxDepthPipeline:control_image", + "modelType": "FluxDepthPipeline", + "modelFamily": "FLUX Image", + "mode": "control_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-Depth-dev" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxControlPipeline" + ], + "sourceTemplateId": "flux_depth_control", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-depth-pipeline/control-image.json", + "graphHash": "432f0e01a11d7bfa6cd2f0894dbb47d41551cde59ae4d4ae4ae8c37fb6584a86", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxDevPipeline:text_to_image", + "modelType": "FluxDevPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-dev" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_dev_expert_text_to_image", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-dev-pipeline/text-to-image.json", + "graphHash": "186c7fb68d364cd6fcf6e4e01f0a6f3d913a7d28463cf82eb8779b7303543c2e", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxDevPipeline:text_to_image:flux_lora_cinematic_octane_3d", + "modelType": "FluxDevPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-dev", + "prithivMLmods/3D-Render-Flux-LoRA" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_lora_cinematic_octane_3d", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-dev-pipeline/text-to-image--flux-lora-cinematic-octane-3d.json", + "graphHash": "58d9abf91967ffff73929675c7283fd6f0aff653512f457b4a3d78488468bfdf", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxDevPipeline:text_to_image:flux_lora_film_noir", + "modelType": "FluxDevPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-dev", + "dvyio/flux-lora-film-noir" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_lora_film_noir", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-dev-pipeline/text-to-image--flux-lora-film-noir.json", + "graphHash": "0d92e0e3d02176879416e29520b7cdc8c997d449b0e30e4652f551027087d13f", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxDevPipeline:text_to_image:flux_lora_ghibli_story", + "modelType": "FluxDevPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-dev", + "alvarobartt/ghibli-characters-flux-lora" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_lora_ghibli_story", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-dev-pipeline/text-to-image--flux-lora-ghibli-story.json", + "graphHash": "e6ef11f7cadcec474024cb07a948343fa34cb1a54d4f2e3b7d4c5e9f8b2e895a", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxDevPipeline:text_to_image:flux_lora_oil_painting", + "modelType": "FluxDevPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-dev", + "dtthanh/flux_oil_painting_lora" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_lora_oil_painting", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-dev-pipeline/text-to-image--flux-lora-oil-painting.json", + "graphHash": "488578d3ca4d7b0d0da7f6503ceffb8b14fd51c0c81cb22a0ab43a5254001ba3", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxDevPipeline:text_to_image:flux_lora_paper_cutout", + "modelType": "FluxDevPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-dev", + "Norod78/Flux_1_Dev_LoRA_Paper-Cutout-Style" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_lora_paper_cutout", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-dev-pipeline/text-to-image--flux-lora-paper-cutout.json", + "graphHash": "3bf412d2ceeab67b4f590d0abb546896b7ec545e26cabe2e1d1c35a1b7e016ee", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxDevPipeline:text_to_image:flux_lora_photoreal_documentary", + "modelType": "FluxDevPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-dev", + "XLabs-AI/flux-RealismLora" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_lora_photoreal_documentary", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-dev-pipeline/text-to-image--flux-lora-photoreal-documentary.json", + "graphHash": "e7771c98041ead543c76268abe7b416c062f3ed6da4cadf290b3cc9872113f03", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxDevPipeline:text_to_image:flux_lora_retro_comic", + "modelType": "FluxDevPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-dev", + "renderartist/retrocomicflux" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_lora_retro_comic", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-dev-pipeline/text-to-image--flux-lora-retro-comic.json", + "graphHash": "59388664fbb42f11cad923cc75667d141c94a1ac21edb8e2f9c5b52ac1958256", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxDevPipeline:text_to_image:flux_lora_watercolor", + "modelType": "FluxDevPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-dev", + "SebastianBodza/Flux_Aquarell_Watercolor_v2" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_lora_watercolor", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-dev-pipeline/text-to-image--flux-lora-watercolor.json", + "graphHash": "7aa3399589a63d0e07513e65da642c34e689966c12ad0f9fa6dca8c152d4629d", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxFillPipeline:inpaint", + "modelType": "FluxFillPipeline", + "modelFamily": "FLUX Image", + "mode": "inpaint", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-Fill-dev" + ], + "requiredInputs": { + "requiredImages": [ + "referenceImages", + "maskImage" + ], + "note": "Requires source and mask images." + }, + "pipelineClasses": [ + "FluxFillPipeline" + ], + "sourceTemplateId": "flux_fill_inpaint", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-fill-pipeline/inpaint.json", + "graphHash": "9e27d41ba44ccff39ca7f16dffe8ba31b64eb3a20eb049f86ea92806be8eebcf", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxFillPipeline:outpaint", + "modelType": "FluxFillPipeline", + "modelFamily": "FLUX Image", + "mode": "outpaint", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-Fill-dev" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxFillPipeline" + ], + "sourceTemplateId": "flux_fill_outpaint", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-fill-pipeline/outpaint.json", + "graphHash": "c8daf126ef2427f2af201d861ca2ec9243afb01b65d77a487f7b79abdde1f87d", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxKontextPipeline:edit_image", + "modelType": "FluxKontextPipeline", + "modelFamily": "FLUX Image", + "mode": "edit_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-Kontext-dev" + ], + "requiredInputs": { + "requiredImages": [ + "referenceImages" + ], + "note": "Requires a source image." + }, + "pipelineClasses": [ + "FluxKontextPipeline" + ], + "sourceTemplateId": "flux_kontext_edit", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-kontext-pipeline/edit-image.json", + "graphHash": "1e416cb65315ca8fec6251349b1f813abb77afcf427d02a2dddb35bd21eb38f7", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxKontextPipeline:multi_image_reference_edit", + "modelType": "FluxKontextPipeline", + "modelFamily": "FLUX Image", + "mode": "multi_image_reference_edit", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-Kontext-dev" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxKontextPipeline" + ], + "sourceTemplateId": "flux_kontext_multi_reference", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-kontext-pipeline/multi-image-reference-edit.json", + "graphHash": "1de254ffce2824c7acfd4607abf89adeca272d1804a536a85aaa7db0e2a2596a", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxKreaPipeline:text_to_image", + "modelType": "FluxKreaPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-Krea-dev" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_krea_text_to_image", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-krea-pipeline/text-to-image.json", + "graphHash": "8859c1475ac637dc460fc0f167068bf36ffd47587fd96e10dda69ad5fb719de2", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxReduxPipeline:edit_image", + "modelType": "FluxReduxPipeline", + "modelFamily": "FLUX Image", + "mode": "edit_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-Redux-dev" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxReduxPipeline" + ], + "sourceTemplateId": "flux_redux_edit", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-redux-pipeline/edit-image.json", + "graphHash": "5fd70a683f92d097bc3b3ea23d6922b016b00b939fffe7da573887b379081f03", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "FluxSchnellPipeline:text_to_image", + "modelType": "FluxSchnellPipeline", + "modelFamily": "FLUX Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "black-forest-labs/FLUX.1-schnell" + ], + "requiredInputs": [], + "pipelineClasses": [ + "FluxPipeline" + ], + "sourceTemplateId": "flux_schnell_text_to_image", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/flux-schnell-pipeline/text-to-image.json", + "graphHash": "32f0640393b78e98ef3ce62d3a4cb207865506dfbad8b74be809c5bb06e3c994", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "LTXVideoPipeline:image_to_video", + "modelType": "LTXVideoPipeline", + "modelFamily": "LTX Video", + "mode": "image_to_video", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "execution-qualified-gallery-review-pending", + "requiredArtifacts": [ + "Lightricks/LTX-Video-0.9.8-13B-distilled" + ], + "requiredInputs": { + "requiredImages": [ + "referenceImages" + ], + "note": "Requires one starting image." + }, + "pipelineClasses": [ + "LTXConditionPipeline" + ], + "sourceTemplateId": "ltx_video_image_to_video", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/ltxvideo-pipeline/image-to-video.json", + "graphHash": "e0ef8413c56d897dd1fd7b3043835d504e6f8b557d1a95077ee13b4e30678f42", + "graphQualificationStatus": "execution-qualified-gallery-review-pending", + "runtimeQualificationStatus": "observed-on-recorded-platform", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "LTXVideoPipeline:reference_to_video", + "modelType": "LTXVideoPipeline", + "modelFamily": "LTX Video", + "mode": "reference_to_video", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "execution-qualified-gallery-review-pending", + "requiredArtifacts": [ + "Lightricks/LTX-Video-0.9.8-13B-distilled" + ], + "requiredInputs": { + "requiredImages": [ + "referenceImages" + ], + "note": "Requires one or more frame references." + }, + "pipelineClasses": [ + "LTXConditionPipeline" + ], + "sourceTemplateId": "ltx_video_multi_reference", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/ltxvideo-pipeline/reference-to-video.json", + "graphHash": "e22704ba8b8a2bcf1de5b799ec75f2b75a2164c956fdcaff5a5bae9afe16fbd6", + "graphQualificationStatus": "execution-qualified-gallery-review-pending", + "runtimeQualificationStatus": "observed-on-recorded-platform", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "LTXVideoPipeline:text_to_video", + "modelType": "LTXVideoPipeline", + "modelFamily": "LTX Video", + "mode": "text_to_video", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "execution-qualified-gallery-review-pending", + "requiredArtifacts": [ + "Lightricks/LTX-Video-0.9.8-13B-distilled" + ], + "requiredInputs": [], + "pipelineClasses": [ + "LTXConditionPipeline" + ], + "sourceTemplateId": "ltx_video_text_to_video", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/ltxvideo-pipeline/text-to-video.json", + "graphHash": "5ef74af8c6586ed5f848560d77217eacea493dc2bd18be73fbc852eb714be5c0", + "graphQualificationStatus": "execution-qualified-gallery-review-pending", + "runtimeQualificationStatus": "observed-on-recorded-platform", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "LTXVideoPipeline:video_to_video", + "modelType": "LTXVideoPipeline", + "modelFamily": "LTX Video", + "mode": "video_to_video", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "execution-qualified-gallery-review-pending", + "requiredArtifacts": [ + "Lightricks/LTX-Video-0.9.8-13B-distilled" + ], + "requiredInputs": { + "requiredVideos": [ + "sourceVideo" + ], + "note": "Requires one source video." + }, + "pipelineClasses": [ + "LTXConditionPipeline" + ], + "sourceTemplateId": "ltx_video_video_to_video", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/ltxvideo-pipeline/video-to-video.json", + "graphHash": "4d2cb48c0df39bff2fd60b67b21957e93202b84535a326587925ab7aa7046732", + "graphQualificationStatus": "execution-qualified-gallery-review-pending", + "runtimeQualificationStatus": "observed-on-recorded-platform", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "QwenImageEditModularPipeline:edit_image", + "modelType": "QwenImageEditModularPipeline", + "modelFamily": "Qwen Image", + "mode": "edit_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "Qwen/Qwen-Image-Edit" + ], + "requiredInputs": [], + "pipelineClasses": [ + "QwenImageEditInpaintPipeline", + "QwenImageEditModularPipeline" + ], + "sourceTemplateId": "qwen_character_angles", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/qwen-image-edit-modular-pipeline/edit-image.json", + "graphHash": "613bf30faeea00fa4f95e486500f748ca69d380ca2f6446bf7568605e50e8847", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "QwenImageEditModularPipeline:inpaint", + "modelType": "QwenImageEditModularPipeline", + "modelFamily": "Qwen Image", + "mode": "inpaint", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "Qwen/Qwen-Image-Edit" + ], + "requiredInputs": { + "requiredImages": [ + "referenceImages", + "maskImage" + ], + "note": "Requires one source image and one mask image." + }, + "pipelineClasses": [ + "QwenImageEditInpaintPipeline", + "QwenImageEditModularPipeline" + ], + "sourceTemplateId": "qwen_inpaint_object_replace", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/qwen-image-edit-modular-pipeline/inpaint.json", + "graphHash": "c89011cdc817e2dcad3e25505f17b96265146644331bff3ebb639fd50951bfe9", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "QwenImageEditModularPipeline:outpaint", + "modelType": "QwenImageEditModularPipeline", + "modelFamily": "Qwen Image", + "mode": "outpaint", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "Qwen/Qwen-Image-Edit" + ], + "requiredInputs": { + "requiredImages": [ + "referenceImages" + ], + "note": "Requires one source image; MoDiff builds the expanded canvas and boundary mask." + }, + "pipelineClasses": [ + "QwenImageEditInpaintPipeline", + "QwenImageEditModularPipeline" + ], + "sourceTemplateId": "qwen_outpaint_aspect_template", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/qwen-image-edit-modular-pipeline/outpaint.json", + "graphHash": "20cd001608f95f77f998bdd82e694844faddeed06cd992b2c1b93df4bd14cfda", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "QwenImageEditPlusModularPipeline:edit_image", + "modelType": "QwenImageEditPlusModularPipeline", + "modelFamily": "Qwen Image", + "mode": "edit_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "Qwen/Qwen-Image-Edit-2511" + ], + "requiredInputs": [], + "pipelineClasses": [ + "QwenImageEditPlusModularPipeline" + ], + "sourceTemplateId": "character_edit", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/qwen-image-edit-plus-modular-pipeline/edit-image.json", + "graphHash": "b05acbd7c0aad0d12e7a27dca09d8ee74ed4041e97b475c0b890ca83bd37bc88", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "QwenImageEditPlusModularPipeline:multi_image_reference_edit", + "modelType": "QwenImageEditPlusModularPipeline", + "modelFamily": "Qwen Image", + "mode": "multi_image_reference_edit", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "Qwen/Qwen-Image-Edit-2511" + ], + "requiredInputs": [], + "pipelineClasses": [ + "QwenImageEditPlusModularPipeline" + ], + "sourceTemplateId": "qwen_product_ad_composite", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/qwen-image-edit-plus-modular-pipeline/multi-image-reference-edit.json", + "graphHash": "2d274f95571187c456d6a84e91e1998a89ec4e282e0fb98543951ee4c5c70e0e", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "QwenImageLayeredModularPipeline:layer_decomposition", + "modelType": "QwenImageLayeredModularPipeline", + "modelFamily": "Qwen Image", + "mode": "layer_decomposition", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "Qwen/Qwen-Image-Layered" + ], + "requiredInputs": [], + "pipelineClasses": [ + "QwenImageLayeredModularPipeline" + ], + "sourceTemplateId": "qwen_layered_portrait", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/qwen-image-layered-modular-pipeline/layer-decomposition.json", + "graphHash": "5530228b2848aef21a6b2dc6e7f07a4788d5831674fe1b6ae23433df205d3018", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "QwenImageModularPipeline:control_image", + "modelType": "QwenImageModularPipeline", + "modelFamily": "Qwen Image", + "mode": "control_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "Qwen/Qwen-Image-2512" + ], + "requiredInputs": { + "modelRequirements": [ + { + "id": "qwen-controlnet-union", + "label": "Qwen ControlNet Union", + "repo": "InstantX/Qwen-Image-ControlNet-Union", + "kind": "controlnet", + "requiredForModes": [ + "control_image" + ], + "description": "Required for Qwen Image Control image workflows." + } + ], + "requiredImages": [ + "controlImage" + ], + "note": "Requires the Qwen ControlNet Union model plus one control image." + }, + "pipelineClasses": [ + "QwenImageModularPipeline", + "QwenImagePipeline" + ], + "sourceTemplateId": "qwen_control_image_layout", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/qwen-image-modular-pipeline/control-image.json", + "graphHash": "250adc9a5d838246e2ea1a842a6cd341780fdae88db86aaa17bfa2fb84f3778f", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "QwenImageModularPipeline:text_to_image", + "modelType": "QwenImageModularPipeline", + "modelFamily": "Qwen Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "Qwen/Qwen-Image-2512" + ], + "requiredInputs": [], + "pipelineClasses": [ + "QwenImageModularPipeline", + "QwenImagePipeline" + ], + "sourceTemplateId": "qwen_text_rendering", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/qwen-image-modular-pipeline/text-to-image.json", + "graphHash": "471152f31c710eb83ffea256eab1cc371ac11ecdbafb3f1a8f55595defba7a4f", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "WanImageToVideoPipeline:image_to_video", + "modelType": "WanImageToVideoPipeline", + "modelFamily": "Wan Video", + "mode": "image_to_video", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-execution-pending", + "requiredArtifacts": [ + "Wan-AI/Wan2.2-I2V-A14B-Diffusers" + ], + "requiredInputs": { + "requiredImages": [ + "referenceImages" + ], + "note": "The story workflow requires one ordered opening keyframe per shot." + }, + "pipelineClasses": [ + "WanImageToVideoPipeline" + ], + "sourceTemplateId": "wan_22_i2v_seed_vault", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/wan-image-to-video-pipeline/image-to-video.json", + "graphHash": "1e978474f172eb053902eed7ef40888fea9961c98cc47264d960db17954138e6", + "graphQualificationStatus": "graph-qualified-execution-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "WanTI2VPipeline:text_to_video", + "modelType": "WanTI2VPipeline", + "modelFamily": "Wan Video", + "mode": "text_to_video", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-execution-pending", + "requiredArtifacts": [ + "Wan-AI/Wan2.2-TI2V-5B-Diffusers" + ], + "requiredInputs": [], + "pipelineClasses": [ + "WanTI2VPipeline" + ], + "sourceTemplateId": "wan_22_ti2v_5b_seed_vault", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/wan-ti2-vpipeline/text-to-video.json", + "graphHash": "f5136f2c381fa8ed2eb5c6a34faa4fa181106d94b9ebe945a8dda71879d75eb0", + "graphQualificationStatus": "graph-qualified-execution-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "WanVACEPipeline:control_to_video", + "modelType": "WanVACEPipeline", + "modelFamily": "Wan Video", + "mode": "control_to_video", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "execution-qualified-gallery-review-pending", + "requiredArtifacts": [ + "Wan-AI/Wan2.1-VACE-1.3B-diffusers" + ], + "requiredInputs": { + "requiredVideos": [ + "controlVideo" + ], + "note": "Requires a prepared control video." + }, + "pipelineClasses": [ + "WanVACEPipeline" + ], + "sourceTemplateId": "wan_vace_grayscale_control", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/wan-vacepipeline/control-to-video.json", + "graphHash": "2c0f4eee6f4ff8ca44da6f73b27e58116008d0a84ee84c27c6c320bf81764b58", + "graphQualificationStatus": "execution-qualified-gallery-review-pending", + "runtimeQualificationStatus": "observed-on-recorded-platform", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "WanVACEPipeline:text_to_video", + "modelType": "WanVACEPipeline", + "modelFamily": "Wan Video", + "mode": "text_to_video", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "execution-qualified-gallery-review-pending", + "requiredArtifacts": [ + "Wan-AI/Wan2.1-VACE-1.3B-diffusers" + ], + "requiredInputs": [], + "pipelineClasses": [ + "WanVACEPipeline" + ], + "sourceTemplateId": "wan_vace_direct_text_to_video", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/wan-vacepipeline/text-to-video.json", + "graphHash": "9370114396109aa817f1a004f4292bff404bfdad8f0ee66e59e4248170a35930", + "graphQualificationStatus": "execution-qualified-gallery-review-pending", + "runtimeQualificationStatus": "observed-on-recorded-platform", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "WanVACEPipeline:video_inpaint", + "modelType": "WanVACEPipeline", + "modelFamily": "Wan Video", + "mode": "video_inpaint", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "execution-qualified-gallery-review-pending", + "requiredArtifacts": [ + "Wan-AI/Wan2.1-VACE-1.3B-diffusers" + ], + "requiredInputs": { + "requiredVideos": [ + "sourceVideo", + "maskVideo" + ], + "note": "Requires source video and matching mask video." + }, + "pipelineClasses": [ + "WanVACEPipeline" + ], + "sourceTemplateId": "wan_vace_masked_object_replace", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/wan-vacepipeline/video-inpaint.json", + "graphHash": "10cd8a70818a3a0f8bc56cf2a9a142697a14cc437aed86dfadf9cb22b3ce2fa4", + "graphQualificationStatus": "execution-qualified-gallery-review-pending", + "runtimeQualificationStatus": "observed-on-recorded-platform", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "WanVACEPipeline:video_outpaint", + "modelType": "WanVACEPipeline", + "modelFamily": "Wan Video", + "mode": "video_outpaint", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "execution-qualified-gallery-review-pending", + "requiredArtifacts": [ + "Wan-AI/Wan2.1-VACE-1.3B-diffusers" + ], + "requiredInputs": { + "requiredVideos": [ + "sourceVideo", + "maskVideo" + ], + "note": "Requires source video and boundary/generation mask video." + }, + "pipelineClasses": [ + "WanVACEPipeline" + ], + "sourceTemplateId": "wan_vace_outpaint_reframe", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/wan-vacepipeline/video-outpaint.json", + "graphHash": "735f84160d0334624bc5a9c44d5030bc1ddfd50749f26718ffb5e296cb780631", + "graphQualificationStatus": "execution-qualified-gallery-review-pending", + "runtimeQualificationStatus": "observed-on-recorded-platform", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "WanVideoPipeline:text_to_video", + "modelType": "WanVideoPipeline", + "modelFamily": "Wan Video", + "mode": "text_to_video", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "execution-qualified-gallery-review-pending", + "requiredArtifacts": [ + "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" + ], + "requiredInputs": [], + "pipelineClasses": [ + "WanPipeline", + "WanVideoToVideoPipeline" + ], + "sourceTemplateId": "wan_vace_cinematic_text_to_video", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/wan-video-pipeline/text-to-video.json", + "graphHash": "1724f370e065a739752a0ece4c64d69641e8d4daa6aa5f0586ee973b8a2924ea", + "graphQualificationStatus": "execution-qualified-gallery-review-pending", + "runtimeQualificationStatus": "observed-on-recorded-platform", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "WanVideoPipeline:video_color_edit", + "modelType": "WanVideoPipeline", + "modelFamily": "Wan Video", + "mode": "video_color_edit", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-execution-pending", + "requiredArtifacts": [ + "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" + ], + "requiredInputs": { + "requiredVideos": [ + "sourceVideo" + ], + "note": "Requires one source video." + }, + "pipelineClasses": [ + "WanPipeline", + "WanVideoToVideoPipeline" + ], + "sourceTemplateId": "wan_vace_video_color_grade", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/wan-video-pipeline/video-color-edit.json", + "graphHash": "85b086ccc2a11e05c3e75d516719f1b719f055e210e97850c6115519ac66ae49", + "graphQualificationStatus": "graph-qualified-execution-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "WanVideoPipeline:video_to_video", + "modelType": "WanVideoPipeline", + "modelFamily": "Wan Video", + "mode": "video_to_video", + "mediaKind": "video", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-execution-pending", + "requiredArtifacts": [ + "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" + ], + "requiredInputs": { + "requiredVideos": [ + "sourceVideo" + ], + "note": "Requires one source video." + }, + "pipelineClasses": [ + "WanPipeline", + "WanVideoToVideoPipeline" + ], + "sourceTemplateId": "wan_vace_video_to_video", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/wan-video-pipeline/video-to-video.json", + "graphHash": "7d136e9073a84595d8c5154394168f86b663f06d23af17ec25a4cdf09c625403", + "graphQualificationStatus": "graph-qualified-execution-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "ZImageModularPipeline:text_to_image", + "modelType": "ZImageModularPipeline", + "modelFamily": "Z-Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified", + "requiredArtifacts": [ + "Tongyi-MAI/Z-Image-Turbo" + ], + "requiredInputs": [], + "pipelineClasses": [ + "ZImageModularPipeline" + ], + "sourceTemplateId": "z_image_quick_concept", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/zimage-modular-pipeline/text-to-image.json", + "graphHash": "451b0f0c8900137b1510b3adbdbcfc7613e4c9229a1faf15a654239c01b44878", + "graphQualificationStatus": "graph-qualified", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "ZImageModularPipeline:text_to_image:fast_lora", + "modelType": "ZImageModularPipeline", + "modelFamily": "Z-Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "Tongyi-MAI/Z-Image-Turbo", + "youknownothing/v1-realism-v1-adapter-ZIT-lora" + ], + "requiredInputs": [], + "pipelineClasses": [ + "ZImageModularPipeline" + ], + "sourceTemplateId": "fast_lora", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/zimage-modular-pipeline/text-to-image--fast-lora.json", + "graphHash": "979c5a1f191415d7880537d573c957d945d0c3545ec768171c59c3c41666d91c", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + }, + { + "id": "ZImageModularPipeline:text_to_image:z_image_lora_style", + "modelType": "ZImageModularPipeline", + "modelFamily": "Z-Image", + "mode": "text_to_image", + "mediaKind": "image", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-gallery-review-pending", + "variant": "lora-theme", + "requiredArtifacts": [ + "Tongyi-MAI/Z-Image-Turbo", + "youknownothing/v1-realism-v1-adapter-ZIT-lora" + ], + "requiredInputs": [], + "pipelineClasses": [ + "ZImageModularPipeline" + ], + "sourceTemplateId": "z_image_lora_style", + "minimumAppVersion": "0.2.0", + "minimumBackendVersion": "0.2.0", + "graphPath": "studio/zimage-modular-pipeline/text-to-image--z-image-lora-style.json", + "graphHash": "135600b9beeb31a2b04be53133513f1cd0ec7356be58401d7102c9a4e03e3645", + "graphQualificationStatus": "graph-qualified-gallery-review-pending", + "runtimeQualificationStatus": "unqualified", + "optimizationQualificationStatus": "unqualified", + "qualifiedRuntimeProfiles": [], + "qualificationScope": "exact-model-recipe-runtime-hardware" + } + ], + "experimentalWorkflowCount": 0, + "experimentalWorkflows": [] +} diff --git a/docs/README.md b/docs/README.md index 39d045c..e9e4c7b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,10 +6,14 @@ This directory contains the durable technical guides for the MoDiff backend. Sta | Goal | Guide | | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| Install, configure, launch, or update MoDiff | [Project README](../README.md) and [`config.example.ini`](../config.example.ini) | +| Install, verify, run a first workflow, configure, or update MoDiff | [Project README](../README.md) and [`config.example.ini`](../config.example.ini) | | Understand HTTP and WebSocket surfaces | [API reference](api-reference.md) | -| Diagnose startup, ports, devices, downloads, media, or stale UI | [Troubleshooting](troubleshooting.md) | +| Diagnose startup, ports, slow/stalled runs, devices, downloads, or media | [Troubleshooting](troubleshooting.md) | +| Compare the qualified accelerator profiles and their proof levels | [Runtime support matrix](runtime-support-matrix.md) | +| Review optional attention, quantization, and compilation capabilities | [Optional runtime optimizations](optional-runtime-optimizations.md) | | Build Modular Diffusers graphs and understand experimental compatibility | [Modular Diffusers guide](../modules/ModularDiffusers/README.md) | +| Review the Hugging Face-derived engineering and runtime requirements | [Hugging Face engineering alignment](hugging-face-standards.md) | +| Review inherited source baselines and per-file modification notices | [Source provenance map](source-provenance.md) | | Contribute code, nodes, dependencies, or client-facing changes | [Contributing](../CONTRIBUTING.md) | | Understand the local-only trust boundary or report a vulnerability | [Security policy](../SECURITY.md) | | Understand expected conduct in project spaces | [Code of conduct](../CODE_OF_CONDUCT.md) | diff --git a/docs/accelerator-installation.md b/docs/accelerator-installation.md index a166b45..33a21b0 100644 --- a/docs/accelerator-installation.md +++ b/docs/accelerator-installation.md @@ -2,7 +2,7 @@ MoDiff owns Python/Torch profile selection. The browser shows the same setup checklist but never installs drivers or mutates Python. -Run `./install.sh` on Linux/macOS or `.\install.ps1` on Windows. The guided installer explains every action before it runs, installs hash-verified app-local uv/Python 3.12, stages the backend, validates a real device tensor, and preserves the previous environment for rollback. When a sibling client is present and the build is not skipped, it downloads the verified Node 24 toolchain on demand before installing and building that client. Hybrid NVIDIA/AMD machines must explicitly choose a profile. +Run `./install.sh` on Linux/macOS or `.\install.ps1` on Windows. The guided installer explains every action before it runs, installs hash-verified app-local uv/Python 3.12, stages the backend, validates a real device tensor, and preserves the previous environment for rollback. When a sibling client is present and the build is not skipped, it downloads the verified Node 24 toolchain, downloads and SHA-256 verifies the complete pinned Template Gallery, and bundles those assets into the local client build. Hybrid NVIDIA/AMD machines must explicitly choose a profile. Useful modes: @@ -14,8 +14,40 @@ Useful modes: - `--guide`: print help for every stable setup error code. - `--json`: return the same phases and structured steps for automation. -Profiles are `nvidia-cuda`, `amd-rocm-linux`, `amd-pytorch-windows`, `apple-mps`, and `cpu`. Strix Halo on Ubuntu 24.04.3 uses the AMD ROCm 7.2/PyTorch 2.9.1 profile. Ubuntu 26.04 is experimental and requires `--allow-experimental`; non-interactive Auto otherwise selects CPU. WSL and unqualified accelerators fall back to CPU. +Executable profiles are `nvidia-cuda`, `amd-rocm-linux`, `intel-xpu`, `apple-mps`, and `cpu`. Choose Intel explicitly with `--accelerator intel`; Auto selects it when a supported Intel GPU is detected and no higher-priority NVIDIA/AMD profile applies. The XPU profile is preview-only and requires a successful `xpu:0` tensor before launch. The manifest retains `amd-pytorch-windows` as a conditional target, but installation remains blocked until MoDiff pins the complete official Windows SDK wheel set instead of guessing dependencies. Strix Halo on Ubuntu 24.04.3 uses the AMD ROCm 7.2/PyTorch 2.9.1 profile. Ubuntu 26.04 is experimental and requires `--allow-experimental`; non-interactive Auto otherwise selects CPU. WSL and unqualified accelerators fall back to CPU. + +Integrated AMD and Intel devices use shared system memory. Hardware discovery records accessible, dedicated, shared, and planning memory separately; Auto budgets from local/planning capacity instead of treating a large GTT or unified-memory aperture as equivalent discrete VRAM. MPS and XPU use direct device residency because CUDA-oriented Diffusers CPU-offload hooks are not portable to those backends. Missing `video`/`render` membership, `/dev/kfd`, DRM render nodes, a successful `rocminfo` GPU agent, or the minimum kernel is blocking. Allowlisted administrator actions show their exact effect and require immediate confirmation. The installer saves state before offering a reboot and always prints the resume command. The installer never changes firmware/BIOS or memory settings. It never executes commands received from the browser or backend API. Declined or unqualified GPU preparation offers the supported CPU command without deleting the GPU setup journal. + +The selected file under `requirements/profiles/`, `pyproject.toml`, and the +accelerator compatibility manifest jointly form the saved runtime contract. +Preflight recomputes that contract on every launch. If any input changed, is +missing, or the saved profile predates the contract digest, the runtime status +is `repair-required` and startup remains blocked even when the currently +installed packages still import. Run the reported managed `--repair` command; +do not bypass the check with a generic dependency sync. + +## Maintaining direct-wheel profiles + +`scripts/lock_accelerator_wheels.py` recalculates SHA-256 hashes only for a +profile whose requirements file consists of direct HTTPS wheel URLs. It +rejects index-based, ordinary pinned, placeholder, and unknown profiles before +writing. Run it from the repository root, review every upstream URL first, and +then synchronize the reviewed digests in +`modiff/compatibility/accelerators.v1.json`: + +```bash +python scripts/lock_accelerator_wheels.py --profile amd-rocm-linux +``` + +Changing a wheel URL or digest is a runtime-contract change. Rebuild that +managed profile and rerun the accelerator manifest, installer, preflight, and +physical-device qualification checks before release. + +On Linux, invoke developer preflight and test commands through +`./scripts/with-runtime-env.sh`. The wrapper reads the installed profile and +applies the same native runtime-library environment used by `run.sh`; this is +required for ROCm wheels whose shared libraries live below `/opt/rocm`. diff --git a/docs/api-reference.md b/docs/api-reference.md index 6a04757..584c75d 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -4,19 +4,32 @@ MoDiff serves its bundled client and backend API from the same origin, normally This API is unversioned, unauthenticated, and intended for the trusted local MoDiff client. It is not a public multi-user API. Several routes mutate files, download models, import Python code, or execute graphs; read [SECURITY.md](../SECURITY.md) before writing another client or changing the bind address. +Every route enforces the local request boundary before its handler runs, +including read-only workflow, queue, and media metadata. The request Host and +connected peer must be literal loopback addresses (or `localhost`). A browser +request that supplies an Origin must use a loopback HTTP(S) Origin; the Vite +development origin on `localhost` is supported even when the backend URL uses +`127.0.0.1`. Originless native clients and ordinary browser navigations are +accepted only over loopback. Do not use an arbitrary DNS name that happens to +resolve to loopback. + ## Route groups | Area | Routes | Purpose | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| Bundled client | `GET /`, `/favicon.ico`, `/assets/*`, `/template-gallery/*`, `/user/*`, `/static/{module}/{file}` | Serve the generated frontend, proof gallery, and module UI assets. | +| Bundled client | `GET /`, `/favicon.ico`, `/assets/*`, optional `/template-gallery/*`, `/user/*`, `/static/{module}/{file}` | Serve the generated frontend and module UI assets. The Gallery route exists only for an explicit offline/local asset build; normal releases use an immutable public Hugging Face Dataset. | | WebSocket | `GET /ws` | Session handshake, queue restoration, progress/events, field signals, and node updates. | | Registry | `GET /nodes` | Return the live registered node contracts used by the bundled client. | -| Execution | `POST /graph`, `GET /queue`, `DELETE /queue/{task_id}`, `GET /stop` | Queue, inspect, remove, or interrupt graph work. `GET /stop` is a legacy state-changing route. | +| Execution | `POST /graph`, `GET /queue`, `GET /runs/{task_id}`, `DELETE /queue/{task_id}`, `POST /stop` | Queue, inspect, remove, or interrupt graph work. A normally supervised backend replaces its worker when a blocking model call misses the cancellation grace period. | | Node state | `POST /fields/action`, `GET /cache/{node}/{field}[/{index}]`, `DELETE /cache` | Run dynamic field actions and access/clear node cache values. | | Files and graphs | `GET /listdir`, `GET /listgraphs`, `GET /file`, `POST /file`, `GET /preview`, `GET /stream` | Browse the configured working directory, load/save graph files, upload media, and stream previews. | -| Runtime | `GET /health`, `GET /runtime/status`, `GET /system_stats`, `GET /runtime/gpu_processes`, `POST /runtime/gpu_cleanup` | Read readiness/hardware state and request best-effort runtime cleanup. | +| Saved workflows | `GET /workflows`, `GET/PUT/DELETE /workflows/{workflow_id}` | List, read, replace, or delete versioned workflow records below the configured data directory. | +| Media I/O | `GET /media/capabilities`, `/media/probe`, `/media/export`, `/media/preview` | Inspect a managed media identifier or return a cached, converted download/browser preview through the built-in deterministic media tools. | +| Runtime | `GET /health`, `/runtime/status`, `/runtime/resources`, `/runtime/options`, `/system_stats`, `/runtime/gpu_processes`; `POST /runtime/gpu_cleanup` | Read readiness, resource, option, and hardware state or request best-effort runtime cleanup. | +| Optimizations | `GET /runtime/optimizations`, `/jobs/{job_id}`, `/receipts`; `POST /runtime/optimizations/install`, `/activate`, `/rollback`, `/enable`, `/probe`, `/qualify` | Stage, validate, select, roll back, and qualify optional app-managed runtime packages and record their local evidence. | | Auto resource | `POST /auto_resource/plan`, `POST /auto_resource/plans`, `GET /auto_resource/history`, `DELETE /auto_resource/history` | Plan hardware-aware model recipes and manage local planner history. | -| Models | `GET /model_capabilities`, `/model_fingerprints`, `/local_models`, `/hf_cache`, `/model_cache/diagnostics`, `/hf_hub`, `/hf_download`; `POST /hf_token`; `DELETE /hf_cache/{hash}` | Discover, diagnose, download, authenticate, fingerprint, and delete model artifacts. `GET /hf_download` performs a state-changing download. | +| Models | `GET /model_capabilities`, `/model_artifact_catalog`, `/model_fingerprints`, `/local_models`, `/hf_cache`, `/model_cache/diagnostics`, `/hf_hub`; `POST /hf_download`, `/hf_token`; `DELETE /hf_cache/{hash}` | Discover, diagnose, download, authenticate, fingerprint, and delete model artifacts. | +| Media lifecycle | `GET /media_assets`, `DELETE /media_assets` | Inspect temporary media records or remove exact unpinned, task-scoped, or age-scoped files while no generation is active. | | Custom modules | `GET /custom_modules`; `POST /custom_modules/refresh`, `/install`, `/{name}/update`, `/{name}/disable`, `/{name}/enable` | Clone/copy and import trusted custom Python modules or change their enabled state. | | Studio outputs | `GET/POST /studio_outputs`, `PATCH/DELETE /studio_outputs/{output_id}` | Persist and manage local Studio output metadata and copied media. | | Studio blocks | `GET/POST /studio/blocks`, `GET/DELETE /studio/blocks/{block_id}` | Persist reusable local graph blocks. | @@ -24,6 +37,18 @@ This API is unversioned, unauthenticated, and intended for the trusted local MoD ## Core response contracts +### Managed file identifiers + +`POST /file` stores uploads below the configured data directory and returns a +portable `@data/` identifier, such as +`@data/images/source.png`. Pass that opaque identifier unchanged to graph +inputs and to `/file`, `/preview`, `/stream`, and `/media/*` routes. It is +data-root-relative, contains no absolute host path, and works when `data_dir` +is outside `work_dir` or on another Windows drive. The backend rejects `..`, +malformed namespace values, and symlink escapes. Existing work-root-relative +values such as `data/images/legacy.png` remain readable when they resolve +inside a configured root. + ### Runtime status `GET /health` and `GET /runtime/status` use the same readiness handler. The response includes: @@ -38,6 +63,76 @@ This API is unversioned, unauthenticated, and intended for the trusted local MoD `GET /system_stats` returns the normalized hardware snapshot directly. The current schema includes `schema_version`, `system`, `torch`, `devices`, `default_device`, and `disk`. Callers should tolerate additive fields and individual probe errors. +`GET /runtime/resources` returns a short-lived schema-versioned sample of the +MoDiff process, storage roots, detected accelerators, active device, and current +run. Storage capacity remains available as `usedBytes`, `freeBytes`, +`totalBytes`, and `percent`; `activePercent` is interval I/O active time for the +disk backing the MoDiff data path. On Windows it is sampled from the physical +disk idle/query counters used by the operating-system performance telemetry. +Unsupported or inaccessible activity counters return `null` rather than +substituting capacity used. `GET /runtime/options` returns the live node option descriptors after +device and package compatibility filtering. Both are observations, not proof +that a real model workload completed. + +### Auto resource compatibility + +`POST /auto_resource/plan` and every item returned by +`POST /auto_resource/plans` use `schemaVersion: 2`. The top-level +`compatibility` object is the authoritative UI assessment for the current +model, operation, runtime, installed artifacts, accelerator, accessible +memory, and supported placement recipe. It contains: + +- `state`: `ready`, `needs_model`, `needs_setup`, `expert_only`, or + `unsuitable`. +- `severity`, stable `code`, concise `summary`, and explanatory `detail`. +- An optional structured `action` such as model install/repair, environment + repair, Setup, or Expert review. +- `source: backend_auto_planner`. + +Clients must not override this assessment with separate GPU-vendor, OS, +dedicated-VRAM, or shared-memory thresholds. A client waiting for this response +should report a pending compatibility check. Existing plan fields remain for +execution and backward compatibility. + +### Saved workflows and media + +The `/workflows/{workflow_id}` store is separate from the legacy file browser. +`PUT` validates the identifier and JSON payload, writes below the configured +data directory, and emits `workflow_updated`; `DELETE` emits +`workflow_deleted`. A `404` means the requested identifier has no saved record. + +The `/media/*` routes accept the same opaque managed file identifier returned +by `POST /file`. Probe returns normalized media metadata. Export and preview +may run bundled FFmpeg/image conversion and populate the ignored +`data/.media-exports` cache before returning a representation; callers should +treat them as potentially expensive even though the representation endpoints +use `GET`. They reject paths outside configured roots and return `422` for an +unsupported format or conversion failure. + +`GET /media_assets` lists temporary media tracked by the runtime. `DELETE +/media_assets` accepts `scope` equal to `all_unpinned`, `task`, or +`older_than`; task cleanup also requires `taskId`, and age cleanup accepts +`olderThanHours`. Cleanup is refused while a graph is active and is not a +secure-erasure guarantee. + +### Optional runtime optimizations + +The optimization catalog is app-owned and compatibility-filtered; it is not a +generic package installer. `POST /runtime/optimizations/install` stages one +known capability in an isolated optional environment and returns a job with +HTTP `202`. Activation or rollback can select an environment and request a +supervised worker restart. Enablement changes local opt-in state; probing +records only a compatibility result; qualification additionally asserts that +the exact workload output was reviewed. Installation, activation, rollback, +enablement, probing, and qualification all mutate local state and must be +treated as trusted operator actions. See [Optional runtime +optimizations](optional-runtime-optimizations.md) for the support boundary. + +`GET /model_artifact_catalog` returns the checked immutable Hugging Face model +catalog. `?refresh=1` performs live Hub metadata lookup for the optional +`modelType` or `repo` filter; it does not turn an unreviewed repository into a +supported model. + ### Node registry `GET /nodes` returns: @@ -62,27 +157,55 @@ The actual `params` schema is node-defined and can include display metadata, sup `POST /graph` accepts the API graph exported by the client. A submitted `sid` associates WebSocket events with the initiating session. A successful response includes a generated `task_id`; it means the graph was queued, not that execution succeeded. -`GET /queue` is the reconnect-safe task snapshot. It includes queued work, the current task, structured node/phase progress when available, and a bounded set of recent terminal receipts. Completion, cancellation, and failure are distinct terminal states. +Workflow-owned asynchronous requests should include `workflowTabId` and the +non-negative integer `workflowCanvasEpoch` in graph `runtimeHints` and in +`POST /fields/action`. Dynamic `node_definition`, `set_field_visibility`, +`set_field_value`, and `set_field_params` events echo these as +`workflow_tab_id` and `workflow_canvas_epoch`; graph events also carry their +task/client-run identity when available. Queued field-action completion uses +the same envelope. These messages are sent only to the originating WebSocket +session. Clients must ignore a field/schema mutation when its session, +workflow, run identity, or canvas epoch no longer owns the visible document. +The extra fields are additive so older single-document clients remain wire +compatible. + +`GET /queue` is the reconnect-safe task snapshot. It includes queued work, the +current task, structured node/phase progress when available, and a bounded set +of compact recent terminal receipts. Current and queued graph runs retain the +complete workflow snapshot needed for immediate restoration. Completed +workflow snapshots and run outputs are loaded on demand through +`GET /runs/{task_id}` instead of being repeated in every queue poll. +Completion, cancellation, and failure are distinct terminal states. Use the WebSocket for live progress and `GET /queue` to restore state after reconnect. Do not infer success only from an HTTP `200` returned by `POST /graph`. +### Studio preview state + +Studio output history and the current preview are separate persisted concepts. `GET /studio_outputs` returns `outputs`, a monotonic `revision`, and `previewSlots`. A slot is scoped by `workflowTabId`, `nodeId`, and `fieldKey`; it carries `currentOutputId`, pending run identity, generation, status, and update time. Clients must use `currentOutputId` as the current-preview authority and treat the other matching records as history. They must not infer the current output from list order, a browser tab transition, or a saved canvas value. + +Successful `POST /graph` admission marks only generated preview fields present in that submitted graph as pending and returns `preview_slots` with `preview_state_revision`. The matching `task_queued` WebSocket event carries the same state for other connected clients. A generated `update_value` atomically persists its output and promotes it through `preview_slot`; a newer pending task cannot be displaced by a late output from the task ahead of it. Terminal events carry any failed, cancelled, or completed-without-output slot changes. + +Deleting the current output clears its slot and never promotes an older history record. The backend retains every output referenced by a current slot even when applying normal history bounds. Version-1 output files are read by choosing the most recent scoped output as a one-time legacy current value; the next mutation writes the version-2 state. + ### Errors Most JSON failures include an `error` value and may include `message`, `category`, `error_code`, recovery guidance, task/node identity, runtime hints, memory state, or loader diagnostics. Error payloads are richer for graph execution than for older utility routes. Clients should preserve unknown fields and fall back to HTTP status plus human-readable text. ## File boundary -Relative file operations resolve against configured `[paths] work_dir`; runtime persistence normally lives under `[paths] data`. The server attempts to reject browsing and previews outside the working boundary, but this is not an authorization system. Configure a dedicated narrow directory and do not expose the service to untrusted clients. +Relative file operations resolve against configured `[paths] work_dir`; runtime persistence normally lives under `[paths] data`. Browsing, previews, and streams resolve canonical paths and reject traversal, sibling-prefix tricks, and symlinks that escape their configured root. This containment is not user authentication; configure dedicated narrow directories and do not expose the service to untrusted clients. -Uploads are written under configured data subdirectories. Studio outputs, blocks, shares, planner history, and downloaded models are persistent local mutations even when initiated through the browser. +Uploads are written under configured data subdirectories and share the configured HTTP request cap, which defaults to 1 GiB. A copied workflow-share preview is limited to 256 MiB. Public share responses expose their media URL, hash, filename, and content type without leaking backend absolute paths. Studio outputs, blocks, shares, planner history, and downloaded models are persistent local mutations even when initiated through the browser. ## Model and code trust - `POST /hf_token` validates a token and writes it in plaintext to ignored `config.ini`. -- `GET /hf_download` can consume substantial network, disk, RAM, and accelerator resources. +- `POST /hf_download` accepts a JSON object with `repo_id`, optional `sid`, `repair`, `repair_source_repo_id`, and a `files` string list. It can consume substantial network, disk, RAM, and accelerator resources. - `DELETE /hf_cache/{hash}` deletes selected cached model revisions. - `POST /custom_modules/install` accepts a Git URL or local directory, places it under `custom/`, and refreshes the live registry. Imported custom code has the backend process's permissions. -- Modular Diffusers nodes may expose `trust_remote_code`. Enable it only for reviewed, revision-pinned repositories. +- Modular Diffusers nodes may expose `trust_remote_code`. Remote custom pipelines/blocks require explicit trust metadata and an exact 40-character commit revision; moving branches and tags are rejected. + +HTTP reads and mutations require a literal loopback destination and peer. Browser requests with an `Origin` header must also use a loopback `http` or `https` origin; CLI HTTP clients without an `Origin` header remain supported over loopback. WebSocket upgrades use the same destination and peer boundary, browser clients must send a loopback Origin, and native clients without one are accepted only over a loopback connection. The initial `welcome.recent` list uses the same compact receipts as `GET /queue`; full completed workflow snapshots remain available through `GET /runs/{task_id}`. The separate supervisor control server binds to `127.0.0.1` and likewise rejects non-loopback browser origins. ## Compatibility diff --git a/docs/hugging-face-standards.md b/docs/hugging-face-standards.md new file mode 100644 index 0000000..1442e17 --- /dev/null +++ b/docs/hugging-face-standards.md @@ -0,0 +1,81 @@ +# Hugging Face Engineering Alignment + +MoDiff is not part of the Hugging Face organization, but its model runtime is deliberately built on Hugging Face Diffusers and Modular Diffusers. This document records which upstream engineering expectations are project requirements and how contributors verify them. + +## Upstream references + +The project uses these maintained upstream sources as guidance: + +- [Diffusers contribution guide](https://huggingface.co/docs/diffusers/main/en/conceptual/contribution), including its AI-assisted contribution rules +- [Diffusers design philosophy](https://huggingface.co/docs/diffusers/main/en/conceptual/philosophy) +- [Diffusers review rules](https://github.com/huggingface/diffusers/blob/main/.ai/review-rules.md) +- [Diffusers agent guide](https://github.com/huggingface/diffusers/blob/main/.ai/AGENTS.md) +- [Modular pipeline conventions](https://github.com/huggingface/diffusers/blob/main/.ai/modular.md) +- [Diffusers testing conventions](https://github.com/huggingface/diffusers/blob/main/.ai/testing.md) +- [Modular Diffusers overview](https://huggingface.co/docs/diffusers/en/modular_diffusers/overview) +- [Diffusers documentation source](https://github.com/huggingface/diffusers/tree/main/docs) +- [huggingface_hub contribution guide](https://github.com/huggingface/huggingface_hub/blob/main/CONTRIBUTING.md) +- [Transformers contribution guide](https://github.com/huggingface/transformers/blob/main/CONTRIBUTING.md) +- [Hugging Face Dataset Cards](https://huggingface.co/docs/hub/en/datasets-cards) + +Upstream repository layouts and release processes are not copied mechanically. MoDiff is an application with a Python server, a separate web client, hardware-specific environments, and persistent local workflows. The rules below adapt the common principles to those constraints. + +## Required design principles + +### Usability and explicit behavior + +- Prefer a clear, composable path over a marginally faster but opaque implementation. +- Fail with an actionable error when a model, revision, artifact, input, package, or device is unsupported. Do not silently select a semantically different pipeline. +- Keep public graph, HTTP, WebSocket, and persistence contracts stable. A deliberate migration must update both repositories, tests, and documentation. +- Keep optional dependencies optional. Registry discovery and diagnostics must not import every model stack or require accelerator hardware. + +### One model-execution boundary + +- Diffusers and Modular Diffusers are the only supported model execution layer. +- Transformers, Accelerate, PEFT, quantization libraries, and accelerator kernels may support a Diffusers pipeline; they must not become an independent application or alternate workflow driver. +- Ordinary deterministic image, audio, video, tensor, and file operations are allowed when they do not load another model runtime. +- Do not add alternate graph executors, hosted inference providers, arbitrary Python model modules, or another model-execution layer. +- Pin the reviewed Diffusers revision in the executable installer contract. Model and adapter references used by curated workflows must also use immutable revisions where the Hub supports them. + +### Modular Diffusers + +- Reuse upstream blocks, components, schedulers, loaders, adapters, and offload hooks instead of copying their behavior. +- Keep block inputs, outputs, and dependencies explicit and parser-friendly. +- Keep blocks composable and free of hidden cross-block state. Runtime caches belong to the existing memory/resource layer, not a second pipeline representation. +- Build executable pipelines through the upstream `init_pipeline` contract and cover the pinned upstream API with a no-download compatibility test. +- Require an explicit trust decision before executing repository-supplied Python. Never silently enable `trust_remote_code` or follow a moving revision. + +### Focused and reviewable changes + +- Diagnose the actual call path and compare similar implementations before editing. +- Keep a change focused on one outcome and remove unrelated generated files, proof scripts, and formatting churn from its diff. +- Add a regression test for the reported behavior and scan for other instances of the same pattern. +- Public functions and non-obvious contracts need concise documentation. Comments should explain constraints, not narrate the edit. + +## Documentation and media + +- Durable documentation lives under `docs/` and is linked from `docs/README.md`. Generated documentation output, dated work logs, host inventories, and implementation handoffs are not source documentation. +- Commands and examples must be runnable from a clean checkout and use repository-relative or placeholder paths. +- Documentation must distinguish registry/schema visibility, mocked behavior, local contract tests, HTTP smoke tests, and real model output. +- Large images, audio, and video do not belong in Git. Public template media is stored in the versioned public Hugging Face Dataset described by the client asset guide; Git stores only source descriptors, hashes, and small contracts. +- The Dataset card must describe contents, creation/provenance, intended use, limitations, licensing, privacy review, and versioning. A model license and the MoDiff Apache-2.0 code license do not automatically license generated media. + +## AI-assisted contributions + +AI tools may assist, but the human submitter owns the result. A change description must state the agreed scope, summarize the human self-review, and list exact validation commands and results. The submitter must understand every changed line, remove speculative or unrelated work, and must not treat an agent-generated claim as test evidence. + +The repository-level instructions in [AGENTS.md](../AGENTS.md), contributor guide, pull-request template, and CI checks make these expectations visible to both people and tools. + +## Evidence expected for a change + +| Change | Minimum evidence | +| --- | --- | +| Pure documentation | Link/command review and repository policy checks | +| Backend logic | Focused regression test plus the complete backend test gate | +| HTTP, WebSocket, graph, or persistence contract | Backend contract tests and compatible client tests | +| Installer or dependency contract | Plan/dry-run tests for affected platforms, package validation, and a clean-profile smoke test where available | +| Modular Diffusers integration | Pinned upstream contract test, graph/schema round trip, and focused runtime tests | +| Template or public media | Gallery integrity/coverage checks, redacted provenance, Dataset manifest verification, and the applicable live proof level | +| Accelerator-specific behavior | Hardware-free contract coverage plus clearly identified live hardware evidence; unsupported hosts remain unclaimed | + +No single evidence level substitutes for the others. diff --git a/docs/optional-runtime-optimizations.md b/docs/optional-runtime-optimizations.md new file mode 100644 index 0000000..3e6b34c --- /dev/null +++ b/docs/optional-runtime-optimizations.md @@ -0,0 +1,103 @@ +# Optional runtime optimizations + +Last reviewed: 2026-07-29 + +MoDiff treats accelerator extensions as optional runtime capabilities, not as +uncontrolled additions to the main Python environment. An optional package is +installed into an app-owned staged overlay, validated in a fresh process +against the active Python, Torch, Diffusers, and accelerator profile, and only +then offered for activation. Activation requires a worker restart. The last +validated overlay remains available for rollback. + +## Product contract + +1. Setup shows packages and runtime features supported by the current managed + profile. +2. Package installation never mutates the active interpreter. A failed build + or import probe leaves the running environment unchanged. +3. ABI-sensitive packages are installed without resolving another copy of + Torch. Source builds receive an app-local build toolchain. +4. A successful import/capability probe only proves that the feature can load. + It never authorizes Auto. +5. Auto may select an optimization only after: + - the user explicitly enables the capability; + - the exact runtime, model artifact, mode, and result-affecting workload + match a receipt; + - an unchanged baseline exists; + - the optimized run improves elapsed time or peak accelerator allocation by + at least 2%; and + - the user reviews and accepts the output. +6. Qualified choices are combined only when each choice has its own matching + receipt. A package or feature update changes the runtime fingerprint and + invalidates the old Auto eligibility. + +The Setup panel exposes install progress, activation, rollback, opt-in, +compatibility probes, qualification actions, and the upstream documentation. + +## Reviewed package pins + +| Capability | Reviewed version | App-managed | Auto eligibility | +| --- | ---: | --- | --- | +| Hugging Face Hub kernels | `kernels 0.16.0` | NVIDIA/Linux | Exact qualified workload only | +| FlashAttention 2 | `flash-attn 2.8.3.post1` | CUDA/ROCm source build | Exact qualified workload only | +| TorchAO | `torchao 0.17.0` | Supported profiles | Exact qualified workload only | +| Optimum Quanto | `optimum-quanto 0.2.7` | Supported profiles | Exact qualified workload only | +| bitsandbytes | `bitsandbytes 0.50.0` | NVIDIA profiles | Exact qualified workload only | +| SageAttention | `sageattention 1.0.6` | NVIDIA/Linux | Manual experiment; never Auto | +| xFormers | `0.0.32.post2` for the Torch 2.8 CUDA profile | Base NVIDIA profile | Exact qualified workload only | +| AMD AITER | No universal pin | No generic installer | Manual, qualified Instinct/ABI combinations only | + +xFormers releases are tied to a specific PyTorch ABI. MoDiff therefore pins +the Torch-2.8-compatible release rather than resolving the newest xFormers +package. FlashAttention is source-built because upstream does not publish one +wheel that safely covers every supported MoDiff CUDA/ROCm combination. + +AITER is not presented as a one-click install on general AMD systems. Its +published builds target specific ROCm, Torch, and Instinct combinations. +Showing a generic install action would risk replacing the managed Torch ABI. +Setup links to the official build instructions for an administrator evaluating +a qualified deployment. + +## Runtime features + +The following features have concrete runtime implementations and remain +disabled until explicitly selected or applied by an exact Auto receipt: + +- Diffusers attention dispatcher backends, including native SDPA, xFormers, + FlashAttention, Hub FlashAttention variants, SageAttention, and AITER when + their capability probes pass. +- Diffusers regional compilation of repeated blocks. +- Diffusers denoiser caches with model/workload output review. +- Diffusers layerwise casting with float8 storage and an explicit compute + dtype. The runtime prevents stacking incompatible casting hooks on a cached + pipeline. +- Channels-last layout for explicitly selected convolutional UNet/VAE + components. +- Existing model-specific quantization and offload recipes. + +The following upstream features are deliberately visible but not enableable: + +- generic quantization combined with offload; +- multi-GPU context parallelism; and +- generic fused QKV projection. + +They require model- and ordering-specific execution contracts. A documentation +entry is not treated as proof that an arbitrary MoDiff graph can use the +feature safely. + +## Official sources reviewed + +- [Diffusers attention backends](https://huggingface.co/docs/diffusers/optimization/attention_backends) +- [Hugging Face kernels installation](https://huggingface.co/docs/kernels/main/installation) +- [FlashAttention repository and platform requirements](https://github.com/Dao-AILab/flash-attention) +- [TorchAO inference workflows](https://docs.pytorch.org/ao/stable/workflows/inference.html) +- [Diffusers memory optimization](https://huggingface.co/docs/diffusers/optimization/memory) +- [Diffusers quantization API](https://huggingface.co/docs/diffusers/main/api/quantization) +- [Diffusers optimization CLI](https://huggingface.co/docs/diffusers/main/using-diffusers/cli) +- [xFormers releases](https://github.com/facebookresearch/xformers/releases) +- [AMD AITER](https://github.com/ROCm/aiter) +- [bitsandbytes](https://huggingface.co/docs/bitsandbytes/main/index) + +These are reviewed pins. MoDiff does not resolve “latest” at runtime. A version +change requires a new source review, staged validation, and qualification +receipts. diff --git a/docs/runtime-support-matrix.md b/docs/runtime-support-matrix.md index 500ef31..c331c94 100644 --- a/docs/runtime-support-matrix.md +++ b/docs/runtime-support-matrix.md @@ -4,9 +4,10 @@ |---|---|---|---| | NVIDIA CUDA (Windows/Ubuntu x64) | Supported | Manifest, resolver, CPU-host contract | Required for release | | Apple MPS (Apple Silicon) | Supported installer | Manifest and contract | Required per model | +| Intel XPU (Linux/Windows x64) | Preview | Manifest, installer, and XPU tensor contract | Required per model/device/driver recipe | | AMD ROCm Linux, Ubuntu 24.04.3, gfx1150/gfx1151 | Supported stack | Detector fixtures | MoDiff model proof required | | AMD ROCm Linux, Ubuntu 26.04 | Experimental | Detector fixtures | Local tensor and model proof required | -| AMD PyTorch Windows | Preview | Detector fixtures | Supported Ryzen/Radeon required | +| AMD PyTorch Windows | Conditional, install blocked | Official-platform manifest and explicit guidance | Complete MoDiff SDK wheel lock and physical model proof required | | CPU | Supported | Install and tensor smoke | Reference host required | -“Supported” describes installation/runtime qualification. `/model_capabilities` remains the source of per-model qualification. +“Supported” describes installation/runtime qualification, not model performance. `/model_capabilities` remains the source of graph capability. Exact model, dtype, placement, optimization, driver, and hardware qualification is receipt-specific; an unqualified recipe may be runnable with a warning but must not be described as optimized. diff --git a/docs/source-provenance.md b/docs/source-provenance.md new file mode 100644 index 0000000..5cbef6c --- /dev/null +++ b/docs/source-provenance.md @@ -0,0 +1,38 @@ +# Source Provenance Map + +This map supplements [the third-party notices](../THIRD_PARTY_NOTICES.md). It +records inherited path families and the engineering treatment used to make +MoDiff modifications visible; it does not replace the repository license or +legal review. + +## Comparison baselines + +- Backend: [`cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f`](https://github.com/cubiq/Mellon/tree/5fd242921d13bff9fb03f4de405fdd39c2335e1f), Copyright 2024 Matteo Spinelli. +- Historical generated client bundle: [`cubiq/Mellon-client@af0c5801f843453a1700733596e99fe6589b2e86`](https://github.com/cubiq/Mellon-client/tree/af0c5801f843453a1700733596e99fe6589b2e86), Copyright 2024 Matteo Spinelli. + +The commits above are evidence-based pre-import comparison baselines selected +from repository history and file similarity. They are not proven Git ancestors +and are not claimed to be the exact revisions used for the original import. + +## Adapted paths + +| Treatment | Paths | +| --- | --- | +| Prominent in-file modification comment | `README.md`, `run.sh`, `main.py`, `modiff/{NodeBase,client,config,modelstore,server}.py`, `config.example.ini`, `pyproject.toml`, `modules/__init__.py`, retained adapted sources under `modules/{Color,Image,ImageFilters,ModularDiffusers,Primitive,Spandrel,Tensor,Text,Video}/`, and `utils/{huggingface,memory_menager,paths,torch_utils}.py` | +| Project-level notice because JSON has no comments | `data/graphs/modular_diffusers/{dynamic_node,image_to_image,multiple_image_edit,quantization,text_to_image}.json` | +| Project-level notice because files are generated or binary | historical files under `web/`; these must be regenerated from the compatible client rather than hand-edited | +| No MoDiff-modification header because the current file matched the comparison baseline byte-for-byte during the audit | `.python-version`, `LICENSE`, `custom/.gitkeep`, `utils/image.py`, and `modules/ModularDiffusers/main.py` | + +New MoDiff-only files are outside the inherited path list. The separately +adapted Hugging Face Diffusers helper is documented in +[the third-party notices](../THIRD_PARTY_NOTICES.md) and in its own source +header. + +## Commentless formats + +Adding comments to JSON or generated bundle files would invalidate their +format, schema, integrity, or reproducibility. The repository-level notice and +this map make those modifications visible without changing the artifacts. +Whether that project-level treatment alone satisfies Apache-2.0 section 4(b) +for every commentless modified file remains a legal-review question; it is not +resolved by this engineering documentation. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 961ee94..4f4e354 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -3,24 +3,34 @@ Start with the backend preflight. It checks the runtime without importing every model node: ```bash -uv run python -m modiff.preflight --json --check-port 8088 --fail-on-error +./.venv/bin/python -m modiff.preflight --json --check-port 8088 --fail-on-error ``` +On Windows, use `.\.venv\Scripts\python.exe` in place of `./.venv/bin/python`. + For a human-readable summary, omit `--json`. Do not treat a successful preflight as proof that a particular model is installed, licensed, compatible with the host, or able to finish a generation. +If preflight reports `runtimeProfile.status: repair-required` with +`runtime-contract-drift`, the checked profile requirements, `pyproject.toml`, +or accelerator manifest changed after `.venv` was installed. Run the exact +`runtimeProfile.repair_command` shown in the report, then rerun preflight. Do +not edit `modiff-profile.json` or use a generic resolver to silence the check; +the installer recreates and validates the saved contract atomically. + ## The backend does not start 1. Confirm Python 3.12 and the active executable reported by preflight. -2. Restore the locked environment: +2. Repair the managed environment, then verify its installed packages: ```bash - uv lock --check - uv sync --frozen - uv pip check + ./install.sh --accelerator auto --backend-only --repair + uv pip check --python .venv/bin/python ``` + On Windows, run `.\install.ps1 -Accelerator auto -BackendOnly -Repair`, then point `uv pip check --python` at `.venv/Scripts/python.exe`. + 3. Look at the first error in the backend console. MoDiff logs to the console by default. -4. If an optional node package fails, install its documented extra and restart so registry discovery runs again. +4. If an optional node package fails, follow that feature's documented managed-package guidance and restart so registry discovery runs again. Do not repair an accelerator profile with a generic `uv sync`. ### Port 8088 is already in use @@ -45,49 +55,69 @@ If the listener is an intended MoDiff backend, open it instead of launching a se ## The UI is missing, stale, or incomplete -The backend serves generated frontend files from `web/`. Confirm these files exist together: +The backend serves generated frontend files from `web/`. Every build requires: ```text web/index.html web/assets/index.js web/assets/index.css web/favicon.ico -web/template-gallery/manifest.json ``` +A normal remote-asset release intentionally has no +`web/template-gallery/`. An explicit offline/local Gallery build additionally +requires `web/template-gallery/manifest.json` and its referenced files. + With the backend running: ```bash curl --fail http://127.0.0.1:8088/ curl --fail http://127.0.0.1:8088/assets/index.js +``` + +For an offline/local Gallery build, also run: + +```bash curl --fail http://127.0.0.1:8088/template-gallery/manifest.json ``` If one is missing or the UI does not match the separate client repository, rebuild MoDiff-client and mirror the **contents** of `dist/` into backend `web/` as described in [the root README](../README.md#updating-the-bundled-client). Copying the directory itself can accidentally create `web/dist/`, which the server does not use. Delete stale generated bundle files through the documented mirror, preserve backend-owned `web/user/` custom fields, restart the backend, and hard-refresh the browser. -An empty or `unverified` gallery manifest is not proof that template examples were generated. Keep the manifest and referenced media from one validated client build. +For a remote build, verify that Gallery requests use the configured immutable +public Hugging Face Dataset revision. For a local build, an empty or +`unverified` Gallery manifest is not proof that template examples were +generated; keep the manifest and referenced media from one validated client +build. ## CUDA is not detected - Check `hardware.devices` and `hardware.torch` in preflight or `GET /system_stats`. - Verify the NVIDIA driver can see the GPU with `nvidia-smi`. -- Linux/Windows lock resolution targets PyTorch CUDA 12.8 wheels; the driver must support that runtime. -- Run `uv pip check` after changing PyTorch or optional CUDA packages. -- Install `--extra cuda` only when its optional packages are wanted; it does not repair a missing/incompatible NVIDIA driver. +- The managed NVIDIA profile targets reviewed PyTorch CUDA 12.8 wheels; the driver must support that runtime. +- Run `uv pip check --python .venv/bin/python` after repairing or deliberately changing the managed environment. +- Re-run `./install.sh --accelerator nvidia --repair` (or `.\install.ps1 -Accelerator nvidia -Repair`) to restore the reviewed NVIDIA profile; it cannot repair a missing or incompatible driver. - A failed CUDA probe intentionally falls back to CPU. Read the recorded probe errors instead of assuming the fallback is accelerator-backed. If CUDA reports an illegal memory access or poisoned context, clearing the cache may not be enough. Stop work and restart the backend process before retrying with a safer resource plan. -## Apple MPS is unavailable or a model is blocked +The normal `./run.sh` or `.\run.ps1` entrypoint keeps a lightweight supervisor outside +the model-owning worker. Stop first requests cooperative cancellation and +removes queued runs. If a third-party model call does not return within the +bounded grace period, the worker is replaced so the operating system releases +its RAM/VRAM before another run is accepted. + +## Apple MPS or Intel XPU is unavailable -Install the Apple profile: +Install or repair the Apple profile: ```bash -uv sync --frozen --extra apple-silicon +./install.sh --accelerator mps --repair ``` Preflight should list an `mps` device when both the host and PyTorch build support it. Current MPS support is not CUDA parity. Large Qwen Image, Wan Video, quantized CUDA, Nunchaku, xformers, FlashAttention, and SageAttention paths may be unavailable or impractical. A contract test or visible MPS device is not evidence that a large model completed on Apple hardware. +For supported Intel graphics on x86-64 Linux or Windows, install or repair the preview profile with `./install.sh --accelerator intel --repair` or `.\install.ps1 -Accelerator intel -Repair`. Preflight must report `xpu:0` and pass a real XPU tensor. Integrated Intel graphics share system memory; MoDiff does not enable CUDA-only CPU-offload hooks on XPU. A visible device makes the path available, but only an exact model/recipe receipt qualifies it. + ## A model is missing, gated, or repeatedly downloads - Open model/cache diagnostics and confirm the selected repository and revision are complete. @@ -109,6 +139,38 @@ Download progress is derived partly from cache materialization and may remain in Cleanup can release MoDiff's node cache, managed Diffusers components, memory-manager entries, and accelerator cache. It cannot free memory owned by another process, and it does not guarantee that the same workflow fits afterward. +## A run is taking much longer than expected + +First distinguish slow progress from a stalled worker. A step counter that +continues to advance, an active node/phase in Queue, websocket heartbeats, or +sustained accelerator activity indicates that the run is alive even when the +ETA is long. A static counter with no task update, heartbeat, log activity, or +resource activity for an extended period needs investigation. + +The visible phase matters: + +- **Download/validation** can wait on repository metadata, network transfer, + hashing, or cache materialization. +- **Loading/placement** can move tens of gigabytes of weights before the first + denoising step. Shared system memory is much slower than equivalent-capacity + discrete VRAM and may make a technically runnable plan impractical. +- **Denoising** normally repeats similar work for every requested step; use the + observed seconds per completed step for a rough remaining-time estimate. +- **Decode/export** can be expensive for high-resolution images or long video + and audio outputs and may depend on local FFmpeg performance. + +Auto chooses a qualified runnable recipe, not a guaranteed performance tier. +Without changing the prompt or generation parameters, a later run may be +faster with a qualified pre-quantized artifact, supported attention backend, +compile/cache option, or improved device placement. Those changes require the +pipeline to be loaded again and cannot safely accelerate an active run. Do not +enable an unqualified quantizer or optional kernel as a recovery experiment. + +For a first smoke test, choose a lightweight Auto-ready image recipe. If input +settings may be changed, lower resolution, steps, frames, or duration before +testing large final settings. If progress is still advancing, stopping is a +user tradeoff rather than a crash-recovery requirement. + ## Video or audio export fails - Install FFmpeg and ensure it is available on `PATH`. @@ -139,6 +201,6 @@ These are disposable when the backend and tests are stopped: - `.pytest_cache/`, `.ruff_cache/`, and `__pycache__/` directories. - Repository-local log and smoke-test artifacts. -- A local `.venv/`, if you are prepared to recreate it with `uv sync --frozen`. +- A local `.venv/`, if you are prepared to recreate it with `./install.sh --accelerator auto --repair` or the equivalent Windows installer command. Do not broadly delete `data/`, Hugging Face caches, or `config.ini` while troubleshooting. They may contain models, generated media, prompts, blocks, workflow shares, planner history, or tokens. Review specific paths and back up anything important first. diff --git a/install.ps1 b/install.ps1 index 20c8bb8..bcc0e2c 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,5 +1,5 @@ param( - [ValidateSet("auto", "nvidia", "amd", "mps", "cpu")][string]$Accelerator = "auto", + [ValidateSet("auto", "nvidia", "amd", "intel", "mps", "cpu")][string]$Accelerator = "auto", [switch]$DryRun, [switch]$NonInteractive, [switch]$Repair, [switch]$SystemCheck, [switch]$Resume, [switch]$Json, [switch]$AllowExperimental, [switch]$BackendOnly ) diff --git a/install.sh b/install.sh old mode 100755 new mode 100644 diff --git a/main.py b/main.py index 1330deb..749ae9a 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,19 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. + import os +from modiff.optimization_packages import activate_runtime_overlay + +# Optional accelerator packages are staged and validated out-of-process. Make +# only the explicitly activated environment visible, before importing Torch or +# any MoDiff module that can transitively import it. +activate_runtime_overlay() + os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") +# Diffusers reads this once while its modules are imported. Configure it before +# the worker imports any node packages so large sharded pipelines can load +# their weight files concurrently. An explicit deployment setting still wins. +os.environ.setdefault("HF_ENABLE_PARALLEL_LOADING", "YES") from modiff.config import CONFIG, ColorCodes @@ -15,13 +28,13 @@ import logging import asyncio import signal +import subprocess import sys +from pathlib import Path logger = logging.getLogger('modiff') -from modules import MODULE_MAP -from modiff.modelstore import modelstore -from modiff.server import server +SUPERVISED_RESTART_EXIT_CODE = 75 def handle_loop_exception(loop, context): @@ -42,14 +55,11 @@ def handle_loop_exception(loop, context): loop.default_exception_handler(context) -# welcome message -logger.info(f"""{ColorCodes.BLUE} -╭──────────────────────╮ -│ Welcome to MoDiff! │ -╰──────────────────────╯ -Speak Friend and Enter: {CONFIG.server['scheme']}://{CONFIG.server['ip']}:{CONFIG.server['port']}""") +async def worker_main(): + # Import heavyweight model/runtime modules only inside the replaceable + # worker. The small parent supervisor must never own accelerator state. + from modiff.server import server -async def main(): await server.run() try: await asyncio.Future() @@ -60,12 +70,18 @@ async def main(): await server.cleanup() -if __name__ == "__main__": +def run_worker(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.set_exception_handler(handle_loop_exception) - main_task = loop.create_task(main()) + logger.info(f"""{ColorCodes.BLUE} +╭──────────────────────╮ +│ Welcome to MoDiff! │ +╰──────────────────────╯ +Speak Friend and Enter: {CONFIG.server['scheme']}://{CONFIG.server['ip']}:{CONFIG.server['port']}""") + + main_task = loop.create_task(worker_main()) try: for sig in (signal.SIGINT, signal.SIGTERM): @@ -79,3 +95,64 @@ async def main(): finally: loop.close() logger.info(f"{ColorCodes.BLUE}Namárië!") + + +def run_supervisor(): + from modiff.supervisor_control import SupervisorController, SupervisorControlServer + + worker = None + shutting_down = False + control_port = int(os.environ.get("MODIFF_SUPERVISOR_CONTROL_PORT", str(int(CONFIG.server["port"]) + 1))) + requested_control_host = str(os.environ.get("MODIFF_SUPERVISOR_CONTROL_HOST", "127.0.0.1")) + if requested_control_host not in {"127.0.0.1", "localhost"}: + logger.warning( + "Ignoring non-loopback supervisor control host %s; privileged control is local-only.", + requested_control_host, + ) + control_host = "127.0.0.1" + queue_state_path = Path(CONFIG.paths["data"]) / "runtime" / "supervisor-queue.json" + controller = SupervisorController(queue_state_path) + control_server = SupervisorControlServer(controller, control_host, control_port) + control_server.start() + logger.info( + "Supervisor control plane listening at http://%s:%s", + control_host, + control_port, + ) + + def forward_signal(signum, _frame): + nonlocal shutting_down + shutting_down = True + controller.set_shutting_down() + if worker is not None and worker.poll() is None: + worker.send_signal(signum) + + for sig in (signal.SIGINT, signal.SIGTERM): + signal.signal(sig, forward_signal) + + try: + while True: + worker_env = os.environ.copy() + worker_env["MODIFF_WORKER_SUPERVISED"] = "1" + worker_env["MODIFF_SUPERVISOR_QUEUE_STATE"] = str(queue_state_path) + worker = subprocess.Popen([sys.executable, os.path.abspath(__file__), "--worker"], env=worker_env) + controller.set_worker(worker) + return_code = worker.wait() + restart_requested = controller.consume_restart_request() + controller.set_worker(None) + if shutting_down: + return return_code + if return_code == SUPERVISED_RESTART_EXIT_CODE or restart_requested: + logger.warning("Replacing the backend worker after a forced run cancellation.") + continue + return return_code + finally: + controller.set_shutting_down() + control_server.close() + + +if __name__ == "__main__": + if "--worker" in sys.argv: + run_worker() + else: + raise SystemExit(run_supervisor()) diff --git a/modiff/NodeBase.py b/modiff/NodeBase.py index a3eefd4..b35c9ed 100644 --- a/modiff/NodeBase.py +++ b/modiff/NodeBase.py @@ -1,13 +1,189 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging logger = logging.getLogger('modiff') -from modiff.config import CONFIG +from contextlib import contextmanager +from contextvars import ContextVar from modiff.modelstore import modelstore from utils.memory_menager import memory_manager import numpy as np import torch import sys +import threading import time -from huggingface_hub.utils import LocalEntryNotFoundError + +_DIFFUSERS_PROGRESS_PATCH_LOCK = threading.RLock() +_DIFFUSERS_PROGRESS_STACK = threading.local() +_NODE_MESSAGE_IDENTITY = ContextVar("modiff_node_message_identity", default=None) + + +@contextmanager +def node_message_context(identity=None): + """Bind workflow ownership to dynamic node messages in this invocation. + + Field actions may run concurrently with the serialized graph worker. A + context variable keeps their browser/workflow identity local to the + executor invocation instead of reading an unrelated global current task. + """ + + normalized = dict(identity) if isinstance(identity, dict) else {} + token = _NODE_MESSAGE_IDENTITY.set(normalized) + try: + yield + finally: + _NODE_MESSAGE_IDENTITY.reset(token) + + +def _node_message_identity(): + explicit = _NODE_MESSAGE_IDENTITY.get() + if explicit is not None: + return dict(explicit) + + current_server = _server() + describe = getattr(current_server, "_current_dynamic_message_identity_payload", None) + return dict(describe()) if callable(describe) else {} + + +def _loading_progress_stack(): + stack = getattr(_DIFFUSERS_PROGRESS_STACK, "value", None) + if stack is None: + stack = [] + _DIFFUSERS_PROGRESS_STACK.value = stack + return stack + + +def _loading_item_label(item): + if isinstance(item, tuple) and item and isinstance(item[0], str): + return item[0] + if isinstance(item, str): + return item + return None + + +class _StructuredLoadingProgress: + """Proxy a Hugging Face tqdm bar into MoDiff's structured node progress.""" + + def __init__(self, bar, report, description=None, total=None): + self._bar = bar + self._report = report + self._description = str(description or getattr(bar, "desc", "") or "").strip().rstrip(".") + self._total = total if isinstance(total, (int, float)) and total > 0 else getattr(bar, "total", None) + self._manual_current = int(getattr(bar, "n", 0) or 0) + self._last_report_at = 0.0 + self._last_report_progress = None + + def __getattr__(self, name): + return getattr(self._bar, name) + + def _emit(self, current, *, item=None, starting=False): + total = self._total + if not isinstance(total, (int, float)) or total <= 0: + return + current = max(0, min(float(current), float(total))) + stack = _loading_progress_stack() + parent = stack[-1] if stack else None + if parent and parent.get("total"): + parent_total = float(parent["total"]) + parent_index = float(parent.get("index") or 1) + ratio = ((parent_index - 1) + current / float(total)) / parent_total + else: + ratio = current / float(total) + progress = min(99, max(0, int(round(ratio * 100)))) + step = max(0, min(int(current), int(total))) + item_label = _loading_item_label(item) + if starting and item_label and "component" in self._description.lower(): + component_scope = "pipeline" if "pipeline component" in self._description.lower() else "model" + message = f"Loading {component_scope} component {int(current) + 1}/{int(total)}: {item_label}" + step = min(int(total), int(current) + 1) + else: + label = self._description or "Loading" + message = f"{label} {step}/{int(total)}" + now = time.monotonic() + if ( + not starting + and step not in (0, int(total)) + and progress == self._last_report_progress + and now - self._last_report_at < 0.25 + ): + return + description = self._description.lower() + component = item_label if starting and item_label and "component" in description else None + shard_current = step if "shard" in description else None + shard_total = int(total) if "shard" in description else None + try: + try: + self._report( + progress, + message, + step, + int(total), + component=component, + shard_current=shard_current, + shard_total=shard_total, + ) + except TypeError as error: + # Preserve the established four-argument extension callback + # while MoDiff's reporter consumes the richer metadata. + if "unexpected keyword argument" not in str(error): + raise + self._report(progress, message, step, int(total)) + self._last_report_at = now + self._last_report_progress = progress + except Exception as error: + logger.debug("Could not publish structured loader progress: %s", error) + + def __iter__(self): + index = 0 + for item in self._bar: + index += 1 + context = {"index": index, "total": self._total, "description": self._description} + self._emit(index - 1, item=item, starting=True) + stack = _loading_progress_stack() + stack.append(context) + completed = False + try: + yield item + completed = True + finally: + if stack and stack[-1] is context: + stack.pop() + elif context in stack: + stack.remove(context) + if completed: + self._emit(index, item=item) + + def __enter__(self): + self._bar.__enter__() + return self + + def __exit__(self, exc_type, exc_value, traceback): + result = self._bar.__exit__(exc_type, exc_value, traceback) + if exc_type is None: + bar_current = int(getattr(self._bar, "n", 0) or 0) + self._manual_current = max(self._manual_current, bar_current) + self._emit(self._manual_current) + return result + + def close(self): + result = self._bar.close() + # Some Hugging Face loaders advance the underlying tqdm counter + # directly and only finalize it while closing the bar. Publish that + # terminal value instead of leaving the queue/node snapshot at N-1/N + # throughout the following silent model-placement work. + bar_current = int(getattr(self._bar, "n", 0) or 0) + self._manual_current = max(self._manual_current, bar_current) + self._emit(self._manual_current) + return result + + def update(self, amount=1): + previous = self._manual_current + result = self._bar.update(amount) + bar_current = int(getattr(self._bar, "n", 0) or 0) + # Disabled tqdm bars intentionally leave ``n`` unchanged. Structured + # progress must still advance when terminal rendering is disabled. + self._manual_current = max(previous + int(amount or 0), bar_current) + self._emit(self._manual_current) + return result + def _server(): from modiff.server import server @@ -128,6 +304,10 @@ def recursive_type_cast(value, ttype, key): class NodeBase: CALLBACK = 'execute' + # Subclasses may list validated inputs that affect how a resident object is + # used but not how it is constructed. Changes to these values should update + # the node's current parameters without discarding expensive cached output. + cache_ignored_params = frozenset() def __init__(self, node_id=None): self.node_id = node_id @@ -141,6 +321,7 @@ def __init__(self, node_id=None): self._sid = None self._has_changed = False + self._cache_invalidated = False self._execution_time = { 'last': None, 'min': None, 'max': None } self._memory_usage = { 'last': None, 'min': None, 'max': None } self._mm_models = [] @@ -149,6 +330,17 @@ def __init__(self, node_id=None): self._progress_last_at = None self._skip_params_check = _module_map()[self.module_name][self.class_name].get('skipParamsCheck', False) + def invalidate_cache(self): + """Force the next graph invocation to execute this node. + + Connected nodes can receive the same mutable pipeline object across + graph runs even when an upstream adapter node changed that object in + place. Object equality cannot represent that provenance, so the graph + executor uses this one-shot invalidation signal when a source node + actually re-executed. + """ + self._cache_invalidated = True + def __call__(self, **kwargs): self._interrupt = False self._progress_started_at = None @@ -180,12 +372,23 @@ def __call__(self, **kwargs): if 'options' in self.default_params[key] and not self.default_params[key].get('fieldOptions', {}).get('noValidation', False): options = self.default_params[key]['options'] value_list = [value] if not isinstance(value, list) else value + option_type = self.default_params[key].get('type') + if isinstance(option_type, list): + option_type = option_type[0] if option_type else None + + def matches_option(candidate, option): + if deep_equal(candidate, option): + return True + if not isinstance(option_type, str): + return False + return deep_equal(candidate, recursive_type_cast(option, option_type, key)) + if isinstance(options, list): - if any(v not in options for v in value_list): + if any(not any(matches_option(v, option) for option in options) for v in value_list): params[key] = [] #raise ValueError(f"Module {self.module_name}.{self.class_name}: Invalid value for {key}: {value} (options: {options})") elif isinstance(options, dict): - if any(v not in options.keys() for v in value_list): + if any(not any(matches_option(v, option) for option in options) for v in value_list): params[key] = {} #raise ValueError(f"Module {self.module_name}.{self.class_name}: Invalid value for {key}: {value} (options: {options})") else: @@ -217,8 +420,23 @@ def __call__(self, **kwargs): self._has_changed = False # flag to know if the node has changed since the last execution - # if any of the values has changed or self.output is empty, we need to execute the node - if (not deep_equal(self.params, params)) or any(v is None for v in self.output.values()): + ignored_cache_params = set(getattr(self, 'cache_ignored_params', ()) or ()) + previous_cache_params = { + key: value for key, value in self.params.items() if key not in ignored_cache_params + } + current_cache_params = { + key: value for key, value in params.items() if key not in ignored_cache_params + } + + # If any load-relevant value changed, or output is empty, execute the + # node. Validated passthrough inputs are still recorded below so + # diagnostics reflect the current graph invocation. + if ( + self._cache_invalidated + or (not deep_equal(previous_cache_params, current_cache_params)) + or any(v is None for v in self.output.values()) + ): + self._cache_invalidated = False self._has_changed = True self.params = params self.output = {k: None for k in self.output} @@ -260,6 +478,8 @@ def __call__(self, **kwargs): "type": "local_cache_update", "node": self.node_id, }, self._sid) + else: + self.params = params return self.output @@ -274,46 +494,33 @@ def __del__(self): # Python is shutting down or import system is unavailable pass - del self.params, self.output - - def graceful_model_loader(self, callback, model_id, config, local_files_only=True): - output = None - online_status = CONFIG.hf['online_status'] - if online_status == 'Online': - local_files_only = False - - if hasattr(callback, 'from_pretrained'): - callback = callback.from_pretrained - - try: - if model_id is None: - output = callback(**config, local_files_only=local_files_only) - else: - output = callback(model_id, **config, local_files_only=local_files_only) - - except (LocalEntryNotFoundError, OSError) as e: - if not local_files_only: - raise e - - if online_status == 'Offline': - logger.error(f"Model {model_id} is not available in offline mode. Consider changing online_status to 'Auto' or 'Online' in the config.ini file.") - raise - - logger.info(f"Model {model_id} not found locally, attempting to download...") - output = self.graceful_model_loader(callback, model_id, config, local_files_only=False) - modelstore.update_hf() - except Exception as e: - logger.error(f"Error loading {model_id}: {e}") - raise - - return output + # Partially constructed nodes can reach ``__del__`` when their + # constructor raises (for example, a guarded optional model loader). + # Cleanup must never emit a secondary exception that hides the useful + # construction error. + self.__dict__.pop("params", None) + self.__dict__.pop("output", None) def pipe_callback(self, pipe, step_index, timestep, callback_kwargs): if not self.node_id: - return + return callback_kwargs if self._interrupt: pipe._interrupt = True + # Some Diffusers loops inspect `_interrupt` before invoking the + # callback, which can start another multi-minute step. Raising at + # this completed-step boundary gives the worker an immediate, + # cleanly classified interruption and preserves normal cleanup. + raise InterruptedError("Execution interrupted by the user after the current model step.") + + current_task = _server().current_task or {} + runtime_limit = (current_task.get('runtimeHints') or {}).get('maxRuntimeSeconds') + started_at = current_task.get('started_at') + if runtime_limit and started_at and time.time() - float(started_at) >= float(runtime_limit): + pipe._interrupt = True + raise TimeoutError( + f"Execution reached the configured {int(runtime_limit)} second runtime limit after the current model step." + ) if hasattr(pipe, '_cfg_cutoff_step') and pipe._cfg_cutoff_step is not None: cutoff_step = int(pipe._num_timesteps * pipe._cfg_cutoff_step) @@ -325,12 +532,18 @@ def pipe_callback(self, pipe, step_index, timestep, callback_kwargs): callback_kwargs['pooled_prompt_embeds'] = callback_kwargs['pooled_prompt_embeds'][-1:] now = time.time() - if self._progress_started_at is None or step_index == 0: + if self._progress_started_at is None: self._progress_started_at = now elapsed = max(0.0, now - self._progress_started_at) completed_steps = step_index + 1 total_steps = int(pipe._num_timesteps) - average_step_seconds = elapsed / completed_steps if completed_steps > 0 else None + # The timer starts at the first completed-step callback, so at step 0 + # there is not yet a measured interval. From step 1 onward, divide by + # the number of intervals since that boundary (step_index), not by the + # total completed-step count. Dividing by completed_steps made the + # first useful long-video ETA exactly half of the observed runtime. + measured_intervals = step_index + average_step_seconds = elapsed / measured_intervals if measured_intervals > 0 else None eta_seconds = average_step_seconds * max(0, total_steps - completed_steps) if average_step_seconds is not None else None self._progress_last_at = now progress = int(completed_steps / total_steps * 100) @@ -369,6 +582,108 @@ def ws_message(self, message): _server().queue_message(message, self._sid) + @contextmanager + def diffusers_loading_progress(self): + """Publish Diffusers/Transformers loading as normal node progress. + + Pipeline loading exposes these stages only through the libraries' tqdm + facades. Patch both facades for one loader call, preserving the terminal + bars while forwarding nested component, checkpoint-shard, and weight + counts to the queue, websocket, graph node, and activity notification. + """ + + if not self.node_id: + yield + return + try: + from diffusers.utils import logging as diffusers_logging + except Exception: + yield + return + progress_facades = [diffusers_logging] + try: + from transformers.utils import logging as transformers_logging + + if transformers_logging is not diffusers_logging: + progress_facades.append(transformers_logging) + # Transformers 5 copies the tqdm function into this module at + # import time, so patching only the logging facade does not reach + # its per-weight loader bar. + from transformers import core_model_loading + + progress_facades.append(core_model_loading) + except Exception: + pass + progress_facades = [ + facade + for index, facade in enumerate(progress_facades) + if callable(getattr(facade, "tqdm", None)) and facade not in progress_facades[:index] + ] + + with _DIFFUSERS_PROGRESS_PATCH_LOCK: + owner_thread_id = threading.get_ident() + originals = [(facade, facade.tqdm) for facade in progress_facades] + last_reported_progress = -1 + + def report( + progress, + message, + current, + total, + *, + component=None, + shard_current=None, + shard_total=None, + ): + nonlocal last_reported_progress + # A component can expose more than one sequential nested bar + # (for example checkpoint shards followed by Transformers + # weights). Never make the node/notification bar move backward. + progress = max(last_reported_progress, progress) + last_reported_progress = progress + normalized_message = str(message or "").lower() + if shard_total is not None or "shard" in normalized_message or "weight" in normalized_message: + phase = "shard_loading" + elif component is not None or "component" in normalized_message: + phase = "component_loading" + else: + phase = "loading" + self.progress( + progress, + phase=phase, + message=message, + current_step=current, + total_steps=total, + component=component, + shard_current=shard_current, + shard_total=shard_total, + ) + + def structured_tqdm_factory(original_tqdm): + def structured_tqdm(*args, **kwargs): + bar = original_tqdm(*args, **kwargs) + # These facades are module-global. A concurrent model + # download on another thread must retain its own progress + # channel rather than being attributed to this graph node. + if threading.get_ident() != owner_thread_id: + return bar + return _StructuredLoadingProgress( + bar, + report, + description=kwargs.get("desc"), + total=kwargs.get("total"), + ) + + return structured_tqdm + + for facade, original_tqdm in originals: + facade.tqdm = structured_tqdm_factory(original_tqdm) + try: + yield + finally: + for facade, original_tqdm in reversed(originals): + facade.tqdm = original_tqdm + def progress( self, progress: int, @@ -380,6 +695,9 @@ def progress( elapsed_seconds: float | None = None, average_step_seconds: float | None = None, eta_seconds: float | None = None, + component: str | None = None, + shard_current: int | None = None, + shard_total: int | None = None, ): if not self._sid or not self.node_id: return @@ -414,50 +732,73 @@ def progress( payload["average_step_seconds"] = average_step_seconds if eta_seconds is not None: payload["eta_seconds"] = eta_seconds + if component: + payload["component"] = component + if shard_current is not None: + payload["shard_current"] = shard_current + if shard_total is not None: + payload["shard_total"] = shard_total + payload["last_heartbeat_at"] = time.time() payload = _server().record_node_progress(payload) - _server().queue_message(payload, self._sid) + _server().queue_message(payload) + + def _queue_dynamic_node_message(self, message): + if not self._sid or not self.node_id: + return + + identity = _node_message_identity() + target_sid = identity.get("sid") or self._sid + payload = { + **message, + **identity, + "sid": target_sid, + } + _server().queue_message(payload, target_sid) def send_node_definition(self, params): if not self._sid or not self.node_id: return - _server().queue_message({ + current_server = _server() + describe = getattr(current_server, "describe_node_params", None) + public_params = describe(params) if callable(describe) else params + self._queue_dynamic_node_message({ "type": "node_definition", "node": self.node_id, - "params": params, - }, self._sid) + "params": public_params, + }) def set_field_visibility(self, fields: dict): if not self._sid or not self.node_id: return - _server().queue_message({ + self._queue_dynamic_node_message({ "type": "set_field_visibility", "node": self.node_id, "fields": fields, - }, self._sid) + }) def set_field_value(self, field: dict): if not self._sid or not self.node_id: return - _server().queue_message({ + self._queue_dynamic_node_message({ "type": "set_field_value", "node": self.node_id, "fields": field, - }, self._sid) + }) def set_field_params(self, field: str, params: dict): if not self._sid or not self.node_id: return - _server().queue_message({ + self._queue_dynamic_node_message({ "type": "set_field_params", "node": self.node_id, "field": field, "params": params, - }, self._sid) + }) def get_signal_value(self, field: str, timeout: int = 5): if not self._sid or not self.node_id: @@ -484,7 +825,7 @@ def notify(self, message: str, variant: str = 'default', persist: bool = False, "variant": variant, "persist": persist, "autoHideDuration": autoHideDuration, - }, self._sid) + }) """ @@ -532,9 +873,9 @@ def mm_load(self, model, device=None): return memory_manager.load_model(model, device) - def mm_exec(self, func, device, models=[], exclude=[], args=None, kwargs=None): + def mm_exec(self, func, device, models=None, exclude=None, args=None, kwargs=None): if self.node_id is None: - return func(*args, **kwargs) + return func(*(args or ()), **(kwargs or {})) return memory_manager.exec(func, device, models, exclude, args, kwargs) diff --git a/modiff/auto_resource.py b/modiff/auto_resource.py index 5788a52..5894b86 100644 --- a/modiff/auto_resource.py +++ b/modiff/auto_resource.py @@ -6,7 +6,9 @@ from pathlib import Path from typing import Any -from modiff.diffusers_offload import ( +from modiff.optimization_packages import qualified_auto_overrides, workload_key_for_form + +from modiff.diffusers_offload_modes import ( OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK, OFFLOAD_MODE_MODEL_CPU, @@ -23,10 +25,18 @@ FLUX_KREA_REPO, FLUX_REDUX_REPO, FLUX_SCHNELL_REPO, + FLUX2_KLEIN_REPO, + LTX_VIDEO_REPO, QWEN_IMAGE_2512_PREQUANTIZED_REPO, QWEN_IMAGE_2512_REPO, ) from modiff.hardware import disk_snapshot, get_hardware_snapshot, system_memory_snapshot +from modiff.model_artifact_catalog import ( + AUTO_TRUST_LEVELS, + catalog_artifact, + catalog_model, + community_artifact_is_discoverable, +) GIB = 1024**3 @@ -38,6 +48,8 @@ QWEN_NATIVE_HEIGHT = 1328 QWEN_NATIVE_STEPS = 50 QWEN_NATIVE_TRUE_CFG = 4.0 +QWEN_AUTO_MAX_DIMENSION = 1344 +QWEN_PRACTICAL_PIXEL_BUDGET = QWEN_PRACTICAL_WIDTH * QWEN_PRACTICAL_HEIGHT QWEN_TRANSFORMER_ONLY_COMPONENTS = ["transformer"] Z_IMAGE_REPO = "Tongyi-MAI/Z-Image-Turbo" @@ -52,11 +64,33 @@ READY_PROOF_STATUSES = {"passed", "declared_safe", "live_proven"} PROVEN_PROOF_STATUSES = READY_PROOF_STATUSES FAILED_HERE_PROOF_STATUS = "failed_here_before" -AUTO_HISTORY_VERSION = 1 +AUTO_HISTORY_VERSION = 2 +AUTO_RESOURCE_SCHEMA_VERSION = 2 AUTO_HISTORY_RELATIVE_PATH = Path("auto_resource") / "history.json" +AUDIO_MODES = {"text_to_audio", "audio_variation", "audio_continuation", "audio_repaint"} +VIDEO_MODES = { + "text_to_video", + "image_to_video", + "video_to_video", + "reference_to_video", + "control_to_video", + "video_color_edit", +} +CPU_OR_DISK_OFFLOAD_MODES = { + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, +} + MODELISH_EXTENSIONS = {".safetensors", ".bin", ".pt", ".pth", ".ckpt", ".gguf", ".onnx"} CONFIG_JSON_NAMES = {"model_index.json", "config.json", "model_config.json", "scheduler_config.json"} +HIGH_MEMORY_FULL_RESIDENCY = { + "accelerator": "cuda", + "vramBytes": 80 * GIB, + "systemRamBytes": 64 * GIB, +} AUTO_MODEL_REQUIREMENTS: dict[str, dict[str, Any]] = { "ZImageModularPipeline": { @@ -64,15 +98,16 @@ "defaultRepo": Z_IMAGE_REPO, "executionPath": "modular-diffusers", "qualityDefaults": {"width": 1024, "height": 1024, "steps": 8, "guidanceScale": 1}, - "minimum": {"accelerator": "cuda_or_mps_or_cpu", "vramBytes": 0, "systemRamBytes": 8 * GIB}, - "recommended": {"accelerator": "cuda_or_mps", "vramBytes": 8 * GIB, "systemRamBytes": 16 * GIB}, + "minimum": {"accelerator": "gpu_or_cpu", "vramBytes": 0, "systemRamBytes": 8 * GIB}, + "recommended": {"accelerator": "gpu", "vramBytes": 8 * GIB, "systemRamBytes": 16 * GIB}, + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, "supportedOffloadModes": [OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_DISK], }, "QwenImageModularPipeline:text_to_image": { "supportedTasks": ["text_to_image"], "defaultRepo": QWEN_IMAGE_2512_REPO, "preferredLowerMemoryRepo": QWEN_IMAGE_2512_PREQUANTIZED_REPO, - "executionPath": "direct-qwen-image", + "executionPath": "direct-diffusers-image", "qualityDefaults": { "width": QWEN_PRACTICAL_WIDTH, "height": QWEN_PRACTICAL_HEIGHT, @@ -88,7 +123,12 @@ "minimum": {"accelerator": "cuda", "vramBytes": 10 * GIB, "systemRamBytes": 24 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 16 * GIB, "systemRamBytes": 32 * GIB}, "officialBf16": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 30 * GIB}, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + ], "knownBad": [ "Do not Auto-quantize the Qwen text encoder with BitsAndBytes on unknown kernels; prior runs hit CUBLAS_STATUS_NOT_SUPPORTED.", "Do not start Auto from official BF16 native settings on constrained CUDA memory.", @@ -102,6 +142,13 @@ "qualityDefaults": {"width": 768, "height": 768, "steps": 28, "guidanceScale": 4, "maxSequenceLength": 512}, "minimum": {"accelerator": "cuda", "vramBytes": 16 * GIB, "systemRamBytes": 32 * GIB, "diskFreeBytes": 20 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 35 * GIB}, + # Qwen Image BF16 occupies about 54 GiB on disk and the current Union + # ControlNet adds about 3.4 GiB. The earlier 109 GiB pressure result was + # produced by a graph-bridge defect that loaded a second full Qwen + # pipeline in the ControlNet slot; it is not a valid residency + # measurement. Leave conservative activation headroom while allowing + # the qualified 97.7 GiB high-memory tier to avoid CPU offload. + "fullResidency": {"accelerator": "cuda", "vramBytes": 80 * GIB, "systemRamBytes": 64 * GIB}, "onLoadQuantization": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -110,7 +157,7 @@ "quantizationMode": "bnb_4bit", "quantizedComponents": ["transformer", "text_encoder"], }, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "bitsandbytes"], "guardedReason": "Qwen Control image is broad Auto coverage: MoDiff will try the generic modular Diffusers graph with quantization/offload, then remember any failure on this machine.", }, @@ -118,10 +165,11 @@ "supportedTasks": ["edit_image", "inpaint", "outpaint"], "defaultRepo": QWEN_IMAGE_EDIT_REPO, "preferredLowerMemoryRepo": QWEN_IMAGE_EDIT_PREQUANTIZED_REPO, - "executionPath": "direct-qwen-image-edit", + "executionPath": "direct-diffusers-image", "qualityDefaults": {"width": 1024, "height": 1024, "steps": 40, "guidanceScale": 4}, "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 40 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 48 * GIB}, + "fullResidency": {"accelerator": "cuda", "vramBytes": 64 * GIB, "systemRamBytes": 64 * GIB}, "lowerMemory": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -138,7 +186,12 @@ "quantizationMode": "bnb_4bit", "quantizedComponents": ["transformer", "text_encoder"], }, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "bitsandbytes"], "guardedReason": "Prefer the Apache-2.0 Diffusers-compatible prequantized Qwen Image Edit artifact on nominal 16 GiB CUDA systems before attempting official BF16 disk offload.", }, @@ -150,6 +203,7 @@ "qualityDefaults": {"width": 768, "height": 768, "steps": 24, "guidanceScale": 4, "maxSequenceLength": 512}, "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 35 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 64 * GIB, "diskFreeBytes": 45 * GIB}, + "fullResidency": {"accelerator": "cuda", "vramBytes": 64 * GIB, "systemRamBytes": 64 * GIB}, "onLoadQuantization": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -158,7 +212,7 @@ "quantizationMode": "bnb_4bit", "quantizedComponents": ["transformer", "text_encoder"], }, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "bitsandbytes"], "guardedReason": "Qwen Edit Plus has guarded Auto coverage through the modular Diffusers graph. Failures are recorded per machine and candidate.", }, @@ -170,6 +224,7 @@ "qualityDefaults": {"width": 768, "height": 768, "steps": 30, "guidanceScale": 4, "maxSequenceLength": 512}, "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 35 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 64 * GIB, "diskFreeBytes": 45 * GIB}, + "fullResidency": {"accelerator": "cuda", "vramBytes": 64 * GIB, "systemRamBytes": 64 * GIB}, "onLoadQuantization": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -178,20 +233,21 @@ "quantizationMode": "bnb_4bit", "quantizedComponents": ["transformer", "text_encoder"], }, - "supportedOffloadModes": [OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "bitsandbytes"], "guardedReason": "Qwen Layered is guarded Auto coverage. MoDiff can try a low-memory modular recipe and remember failures.", }, "WanVACEPipeline": { "supportedTasks": [ "text_to_video", - "image_to_video", - "video_to_video", "video_inpaint", "video_outpaint", - "reference_to_video", "control_to_video", - "video_color_edit", ], "defaultRepo": WAN_VACE_REPO, "executionPath": "direct-wan-vace", @@ -199,7 +255,62 @@ "minimum": {"accelerator": "cuda", "vramBytes": 10 * GIB, "systemRamBytes": 24 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 12 * GIB, "systemRamBytes": 32 * GIB}, "highQuality": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB}, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], + }, + "WanVideoPipeline": { + "supportedTasks": ["text_to_video", "video_to_video", "video_color_edit"], + "defaultRepo": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + "executionPath": "direct-diffusers-video", + "pipelineClass": "WanVideoToVideoPipeline", + "qualityDefaults": {"width": 832, "height": 480, "steps": 30, "guidanceScale": 5, "numFrames": 49}, + "minimum": {"accelerator": "cuda", "vramBytes": 10 * GIB, "systemRamBytes": 24 * GIB}, + "recommended": {"accelerator": "cuda", "vramBytes": 12 * GIB, "systemRamBytes": 32 * GIB}, + "highQuality": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB}, + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], + }, + "WanVideoPipeline:text_to_video": { + "supportedTasks": ["text_to_video"], + "defaultRepo": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + "executionPath": "direct-diffusers-video", + "pipelineClass": "WanPipeline", + "qualityDefaults": {"width": 832, "height": 480, "steps": 30, "guidanceScale": 5, "numFrames": 81}, + "minimum": {"accelerator": "cuda", "vramBytes": 10 * GIB, "systemRamBytes": 24 * GIB}, + "recommended": {"accelerator": "cuda", "vramBytes": 12 * GIB, "systemRamBytes": 32 * GIB}, + "highQuality": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB}, + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], + }, + "LTXVideoPipeline": { + "supportedTasks": ["text_to_video", "image_to_video", "video_to_video", "reference_to_video"], + "defaultRepo": LTX_VIDEO_REPO, + "executionPath": "direct-diffusers-video", + "pipelineClass": "LTXConditionPipeline", + "qualityDefaults": {"width": 704, "height": 480, "steps": 8, "guidanceScale": 1, "numFrames": 81}, + "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 50 * GIB}, + "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 64 * GIB, "diskFreeBytes": 70 * GIB}, + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], + "requiredPackages": ["diffusers", "transformers", "accelerate", "torch"], }, "AceStepAudioPipeline": { "supportedTasks": ["text_to_audio", "audio_variation", "audio_continuation", "audio_repaint"], @@ -209,7 +320,30 @@ "qualityDefaults": {"audioDuration": 30, "steps": 8, "guidanceScale": 1, "shift": 3}, "minimum": {"accelerator": "cuda", "vramBytes": 10 * GIB, "systemRamBytes": 24 * GIB, "diskFreeBytes": 20 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 16 * GIB, "systemRamBytes": 32 * GIB, "diskFreeBytes": 30 * GIB}, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + # The qualified ACE-Step run peaks below 14 GiB including allocator + # reserve. A 16 GiB CUDA card can therefore load the complete pipeline + # directly; forcing CPU offload here made cold loading dramatically + # slower and did not reduce generation time. + "fullResidency": { + "accelerator": "cuda", + "vramBytes": 16 * GIB, + "systemRamBytes": 32 * GIB, + }, + "coldLoadTarget": { + "deviceName": "NVIDIA GeForce RTX 4080", + "maxSeconds": 120, + "recipe": { + "dtype": "bfloat16", + "offloadMode": OFFLOAD_MODE_NONE, + "deviceMap": "cuda", + }, + }, + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "scipy"], }, "FluxSchnellPipeline": { @@ -220,7 +354,13 @@ "qualityDefaults": {"width": 1024, "height": 1024, "steps": 4, "guidanceScale": 0, "maxSequenceLength": 256}, "minimum": {"accelerator": "cuda", "vramBytes": 12 * GIB, "systemRamBytes": 24 * GIB, "diskFreeBytes": 25 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 16 * GIB, "systemRamBytes": 32 * GIB, "diskFreeBytes": 35 * GIB}, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch"], }, "FluxDevPipeline": { @@ -232,6 +372,7 @@ "qualityDefaults": {"width": 768, "height": 768, "steps": 20, "guidanceScale": 3.5, "maxSequenceLength": 256}, "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 45 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 64 * GIB, "diskFreeBytes": 60 * GIB}, + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, "lowerMemory": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -240,9 +381,26 @@ "quantizationMode": "quanto_float8", "quantizedComponents": ["transformer", "text_encoder_2"], }, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "optimum-quanto"], }, + "Flux2KleinPipeline": { + "supportedTasks": ["text_to_image", "edit_image", "multi_image_reference_edit"], + "defaultRepo": FLUX2_KLEIN_REPO, + "executionPath": "direct-diffusers-image", + "pipelineClass": "Flux2KleinPipeline", + "qualityDefaults": {"width": 1024, "height": 1024, "steps": 4, "guidanceScale": 1, "maxSequenceLength": 512}, + "minimum": {"accelerator": "cuda", "vramBytes": 13 * GIB, "systemRamBytes": 24 * GIB, "diskFreeBytes": 25 * GIB}, + "recommended": {"accelerator": "cuda", "vramBytes": 20 * GIB, "systemRamBytes": 32 * GIB, "diskFreeBytes": 35 * GIB}, + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, + "supportedOffloadModes": [OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "requiredPackages": ["diffusers", "transformers", "accelerate", "torch"], + }, "FluxKreaPipeline": { "supportedTasks": ["text_to_image"], "defaultRepo": FLUX_KREA_REPO, @@ -251,6 +409,7 @@ "qualityDefaults": {"width": 768, "height": 768, "steps": 24, "guidanceScale": 3.5, "maxSequenceLength": 256}, "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 45 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 64 * GIB, "diskFreeBytes": 60 * GIB}, + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, "onLoadQuantization": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -259,7 +418,12 @@ "quantizationMode": "quanto_float8", "quantizedComponents": ["transformer", "text_encoder_2"], }, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "optimum-quanto"], "guardedReason": "FLUX Krea has broad guarded Auto coverage through on-load float8 quantization and Diffusers offload.", }, @@ -272,6 +436,7 @@ "qualityDefaults": {"width": 768, "height": 768, "steps": 24, "guidanceScale": 3.5, "maxSequenceLength": 256}, "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 45 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 64 * GIB, "diskFreeBytes": 60 * GIB}, + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, "lowerMemory": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -280,7 +445,12 @@ "quantizationMode": "torchao_float8", "quantizedComponents": ["transformer", "text_encoder_2"], }, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "torchao"], "guardedReason": "FLUX Kontext uses the NVFP4 lower-memory artifact when available; failures are remembered for this machine.", }, @@ -292,6 +462,7 @@ "qualityDefaults": {"width": 768, "height": 768, "steps": 24, "guidanceScale": 30, "maxSequenceLength": 256}, "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 45 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 64 * GIB, "diskFreeBytes": 60 * GIB}, + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, "onLoadQuantization": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -300,7 +471,12 @@ "quantizationMode": "quanto_float8", "quantizedComponents": ["transformer", "text_encoder_2"], }, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "optimum-quanto"], "guardedReason": "FLUX Fill has guarded Auto coverage through generic Diffusers inpaint/outpaint nodes and on-load quantization.", }, @@ -312,6 +488,7 @@ "qualityDefaults": {"width": 768, "height": 768, "steps": 24, "guidanceScale": 10, "maxSequenceLength": 256}, "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 45 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 64 * GIB, "diskFreeBytes": 60 * GIB}, + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, "onLoadQuantization": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -320,7 +497,12 @@ "quantizationMode": "quanto_float8", "quantizedComponents": ["transformer", "text_encoder_2"], }, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "optimum-quanto"], "guardedReason": "FLUX Depth has guarded Auto coverage through generic control-image Diffusers nodes.", }, @@ -332,6 +514,7 @@ "qualityDefaults": {"width": 768, "height": 768, "steps": 24, "guidanceScale": 10, "maxSequenceLength": 256}, "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 45 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 64 * GIB, "diskFreeBytes": 60 * GIB}, + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, "onLoadQuantization": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -340,18 +523,24 @@ "quantizationMode": "quanto_float8", "quantizedComponents": ["transformer", "text_encoder_2"], }, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "optimum-quanto"], "guardedReason": "FLUX Canny has guarded Auto coverage through generic control-image Diffusers nodes.", }, "FluxReduxPipeline": { - "supportedTasks": ["edit_image"], + "supportedTasks": ["edit_image", "multi_image_reference_edit"], "defaultRepo": FLUX_REDUX_REPO, "executionPath": "direct-diffusers-image", - "pipelineClass": "FluxPipeline", + "pipelineClass": "FluxReduxPipeline", "qualityDefaults": {"width": 768, "height": 768, "steps": 24, "guidanceScale": 3.5, "maxSequenceLength": 256}, "minimum": {"accelerator": "cuda", "vramBytes": 24 * GIB, "systemRamBytes": 48 * GIB, "diskFreeBytes": 45 * GIB}, "recommended": {"accelerator": "cuda", "vramBytes": 32 * GIB, "systemRamBytes": 64 * GIB, "diskFreeBytes": 60 * GIB}, + "fullResidency": HIGH_MEMORY_FULL_RESIDENCY, "onLoadQuantization": { "accelerator": "cuda", "vramBytes": 16 * GIB, @@ -360,7 +549,12 @@ "quantizationMode": "quanto_float8", "quantizedComponents": ["transformer", "text_encoder_2"], }, - "supportedOffloadModes": [OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK], + "supportedOffloadModes": [ + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_DISK, + OFFLOAD_MODE_NONE, + ], "requiredPackages": ["diffusers", "transformers", "accelerate", "torch", "optimum-quanto"], "guardedReason": "FLUX Redux has guarded Auto coverage through generic Diffusers image/reference nodes.", }, @@ -631,7 +825,14 @@ def _validate_snapshot_shards(snapshot_dir: Path, expected_files: list[dict[str, "checkedShardCount": checked_shards, } - has_weight_file = any(snapshot_dir.rglob("*.safetensors")) or any(snapshot_dir.rglob("*.bin")) + # Auxiliary app-managed artifacts (for example Spandrel upscalers, LoRAs, + # GGUF weights, and ONNX models) are valid runnable snapshots even though + # they do not use Diffusers' usual .safetensors/.bin naming. Keep this in + # sync with the model-ish extensions accepted by the download pipeline. + has_weight_file = any( + candidate.is_file() and candidate.suffix.lower() in MODELISH_EXTENSIONS + for candidate in snapshot_dir.rglob("*") + ) return { "complete": bool(has_weight_file), "reason": "No shard index files found; direct weight files are present." if has_weight_file else "No local weight files were found in the snapshot.", @@ -732,10 +933,25 @@ def _artifact_cache_status(repo_id: str, local_models: list[dict[str, Any]] | No } +def artifact_cache_status(repo_id: str, local_models: list[dict[str, Any]] | None) -> dict[str, Any]: + """Return the runnable installation state for one cached Hub repository. + + Hugging Face exposes a revision as soon as its snapshot directory exists, + which can be well before large blobs finish downloading. Public inventory + endpoints must use the same shard/plan validation as Auto readiness instead + of treating every scanned revision as installed. + """ + return _artifact_cache_status(repo_id, local_models) + + def _runtime_key(runtime_fingerprint: dict[str, Any] | None) -> str: if not isinstance(runtime_fingerprint, dict): return "unknown-runtime" - return str(runtime_fingerprint.get("fingerprint") or "unknown-runtime") + return str( + runtime_fingerprint.get("resourceFingerprint") + or runtime_fingerprint.get("fingerprint") + or "unknown-runtime" + ) def _history_path(data_dir: str | os.PathLike[str]) -> Path: @@ -783,20 +999,47 @@ def _candidate_history_signature( runtime_fingerprint: dict[str, Any] | None = None, hardware: dict[str, Any] | None = None, ) -> dict[str, Any]: + resolution = candidate.get("artifactResolution") if isinstance(candidate.get("artifactResolution"), dict) else {} + resolved = resolution.get("resolved") if isinstance(resolution.get("resolved"), dict) else {} + workload = _candidate_workload_signature(candidate) return { "hardwareFingerprint": _hardware_history_key(runtime_fingerprint, hardware), "modelType": str(candidate.get("modelType") or ""), "mode": str(candidate.get("mode") or ""), "artifact": str(candidate.get("resolvedArtifact") or candidate.get("artifact") or candidate.get("modelRepo") or ""), + "artifactRevision": str(resolved.get("revision") or candidate.get("artifactRevision") or "unversioned"), "dtype": str(candidate.get("dtype") or ""), "quantizationMode": str(candidate.get("quantizationMode") or "none"), "quantizedComponents": [str(item) for item in candidate.get("quantizedComponents") or []], "offloadMode": str(candidate.get("offloadMode") or ""), + "device": str(candidate.get("device") or ""), + "deviceMap": str(candidate.get("deviceMap") or ""), + "attentionBackend": str(candidate.get("attentionBackend") or "auto"), + "regionalCompile": bool(candidate.get("regionalCompile")), + "denoiserCache": str(candidate.get("denoiserCache") or "none"), + "channelsLast": bool(candidate.get("channelsLast")), + "layerwiseCasting": bool(candidate.get("layerwiseCasting")), "pipelineClass": str(candidate.get("pipelineClass") or ""), "executionPath": str(candidate.get("executionPath") or ""), + "workload": workload, } +def _candidate_workload_signature(candidate: dict[str, Any]) -> dict[str, Any]: + """Return only workload fields that affect this media kind's resource proof.""" + + generation = candidate.get("generation") if isinstance(candidate.get("generation"), dict) else {} + mode = str(candidate.get("mode") or "") + model_type = str(candidate.get("modelType") or "") + if mode in AUDIO_MODES or "audio" in model_type.lower(): + keys = ("audioDuration", "extensionDuration", "batchSize", "steps") + elif mode in VIDEO_MODES or "video" in model_type.lower() or model_type.startswith("Wan"): + keys = ("width", "height", "numFrames", "batchSize", "steps") + else: + keys = ("width", "height", "batchSize", "steps") + return {key: generation.get(key) for key in keys if generation.get(key) is not None} + + def auto_resource_history_key( candidate: dict[str, Any], *, @@ -810,7 +1053,9 @@ def auto_resource_history_key( ) import hashlib - return hashlib.sha1(payload.encode("utf-8")).hexdigest() + # SHA-1 is retained only as the stable legacy cache-key shape. It is not a + # signature or integrity boundary. + return hashlib.sha1(payload.encode("utf-8"), usedforsecurity=False).hexdigest() def _runtime_candidate_from_hints(runtime_hints: dict[str, Any] | None) -> dict[str, Any] | None: @@ -829,31 +1074,79 @@ def _runtime_candidate_from_hints(runtime_hints: dict[str, Any] | None) -> dict[ "quantizationMode": runtime_hints.get("quantizationMode"), "quantizedComponents": runtime_hints.get("quantizedComponents") if isinstance(runtime_hints.get("quantizedComponents"), list) else [], "offloadMode": runtime_hints.get("offloadMode"), + "device": runtime_hints.get("device"), + "deviceMap": runtime_hints.get("deviceMap"), + "attentionBackend": runtime_hints.get("attentionBackend"), + "regionalCompile": runtime_hints.get("regionalCompile"), + "denoiserCache": runtime_hints.get("denoiserCache"), + "channelsLast": runtime_hints.get("channelsLast"), + "layerwiseCasting": runtime_hints.get("layerwiseCasting"), "pipelineClass": runtime_hints.get("pipelineClass"), "executionPath": runtime_hints.get("executionPath"), + "generation": runtime_hints.get("generation") if isinstance(runtime_hints.get("generation"), dict) else {}, + "artifactResolution": runtime_hints.get("artifactResolution") if isinstance(runtime_hints.get("artifactResolution"), dict) else {}, } def _history_candidate_summary(candidate: dict[str, Any]) -> dict[str, Any]: + resolution = candidate.get("artifactResolution") if isinstance(candidate.get("artifactResolution"), dict) else {} + resolved = resolution.get("resolved") if isinstance(resolution.get("resolved"), dict) else {} return { "id": candidate.get("id"), "modelType": candidate.get("modelType"), "mode": candidate.get("mode"), "artifact": candidate.get("resolvedArtifact") or candidate.get("artifact") or candidate.get("modelRepo"), + "artifactRevision": resolved.get("revision") or candidate.get("artifactRevision"), "dtype": candidate.get("dtype"), "quantizationMode": candidate.get("quantizationMode"), "quantizedComponents": candidate.get("quantizedComponents") if isinstance(candidate.get("quantizedComponents"), list) else [], "offloadMode": candidate.get("offloadMode"), + "device": candidate.get("device"), + "deviceMap": candidate.get("deviceMap"), + "attentionBackend": candidate.get("attentionBackend") or "auto", + "regionalCompile": bool(candidate.get("regionalCompile")), + "denoiserCache": candidate.get("denoiserCache") or "none", + "channelsLast": bool(candidate.get("channelsLast")), + "layerwiseCasting": bool(candidate.get("layerwiseCasting")), "pipelineClass": candidate.get("pipelineClass"), "executionPath": candidate.get("executionPath"), + "generation": candidate.get("generation") if isinstance(candidate.get("generation"), dict) else {}, } +def _normalized_measurement(measurement: dict[str, Any] | None) -> dict[str, Any] | None: + if not isinstance(measurement, dict): + return None + normalized = {} + for key in ( + "elapsedSeconds", + "peakAllocatedBytes", + "peakReservedBytes", + "allocatedBytes", + "reservedBytes", + "driverAllocatedBytes", + "processRssBytes", + ): + value = measurement.get(key) + if value is None: + continue + try: + normalized[key] = float(value) if key == "elapsedSeconds" else int(value) + except (TypeError, ValueError): + continue + for key in ("backend", "device"): + value = measurement.get(key) + if value not in (None, ""): + normalized[key] = str(value) + return normalized or None + + def record_auto_resource_success( data_dir: str | os.PathLike[str], *, runtime_fingerprint: dict[str, Any] | None, runtime_hints: dict[str, Any] | None, + measurement: dict[str, Any] | None = None, ) -> dict[str, Any] | None: candidate = _runtime_candidate_from_hints(runtime_hints) if not candidate: @@ -863,6 +1156,7 @@ def record_auto_resource_success( entries = history.setdefault("entries", {}) entry = entries.get(key) if isinstance(entries.get(key), dict) else {} now = _now_ms() + normalized_measurement = _normalized_measurement(measurement) entry.update({ "key": key, "signature": _candidate_history_signature(candidate, runtime_fingerprint=runtime_fingerprint), @@ -871,6 +1165,16 @@ def record_auto_resource_success( "lastSuccessAt": now, "lastStatus": "live_proven", }) + if normalized_measurement: + entry["lastMeasurement"] = normalized_measurement + elapsed = normalized_measurement.get("elapsedSeconds") + previous_best = entry.get("bestElapsedSeconds") + if elapsed is not None and (previous_best is None or elapsed < float(previous_best)): + entry["bestElapsedSeconds"] = elapsed + peak = normalized_measurement.get("peakAllocatedBytes") + previous_peak = entry.get("maxObservedPeakAllocatedBytes") + if peak is not None and (previous_peak is None or peak > int(previous_peak)): + entry["maxObservedPeakAllocatedBytes"] = peak entries[key] = entry _write_auto_resource_history(data_dir, history) return entry @@ -989,12 +1293,22 @@ def _disk_snapshot( return snapshot -def _mps_accelerator(name: Any = None) -> dict[str, Any]: +def _mps_accelerator(name: Any = None, device: dict[str, Any] | None = None) -> dict[str, Any]: + device = device if isinstance(device, dict) else {} + total = _safe_int(device.get("planning_memory_total") or device.get("vram_total")) + free = _safe_int(device.get("planning_memory_free") or device.get("vram_free")) return { "kind": "mps", + "backend": "mps", + "vendor": "apple", "name": str(name or "Apple Metal Performance Shaders"), - "totalBytes": None, - "freeBytes": None, + "architecture": device.get("architecture"), + "memoryKind": device.get("memory_kind") or "unified", + "totalBytes": total, + "freeBytes": free, + "accessibleTotalBytes": _safe_int(device.get("torch_vram_total")) or total, + "dedicatedTotalBytes": _safe_int(device.get("dedicated_memory_total")), + "sharedTotalBytes": _safe_int(device.get("shared_memory_total")), "capability": None, "band": "shared_memory", } @@ -1019,23 +1333,30 @@ def _normalized_accelerator_snapshot(normalized_hardware: dict[str, Any] | None) (item for item in devices if isinstance(item, dict) and str(item.get("type") or "").lower() == kind), None, ) - for kind in ("cuda", "mps", "cpu") + for kind in ("cuda", "xpu", "mps", "cpu") } device = device_by_kind["cuda"] if device is not None: - total = _safe_int(device.get("vram_total")) + total = _safe_int(device.get("planning_memory_total") or device.get("vram_total")) if total is None: total = _safe_int(device.get("torch_vram_total")) - free = _safe_int(device.get("vram_free")) + free = _safe_int(device.get("planning_memory_free") or device.get("vram_free")) if free is None: free = _safe_int(device.get("torch_vram_free")) return { "kind": "cuda", + "backend": device.get("backend") or "cuda", + "vendor": device.get("vendor") or ("amd" if device.get("backend") == "rocm" else "nvidia"), "name": device.get("name"), + "architecture": device.get("architecture"), + "memoryKind": device.get("memory_kind") or "dedicated", "totalBytes": total, "freeBytes": free, - "capability": device.get("capability"), - "band": _vram_band(total), + "accessibleTotalBytes": _safe_int(device.get("torch_vram_total")) or total, + "dedicatedTotalBytes": _safe_int(device.get("dedicated_memory_total")) or total, + "sharedTotalBytes": _safe_int(device.get("shared_memory_total")), + "capability": device.get("compute_capability") or device.get("capability"), + "band": "shared_memory" if device.get("memory_kind") in {"shared", "unified"} else _vram_band(total), } torch_state = normalized_hardware.get("torch") if isinstance(normalized_hardware, dict) else None @@ -1050,9 +1371,38 @@ def _normalized_accelerator_snapshot(normalized_hardware: dict[str, Any] | None) "band": "unknown", } + device = device_by_kind["xpu"] + if device is not None: + total = _safe_int(device.get("planning_memory_total") or device.get("vram_total") or device.get("torch_vram_total")) + free = _safe_int(device.get("planning_memory_free") or device.get("vram_free") or device.get("torch_vram_free")) + return { + "kind": "xpu", + "backend": "xpu", + "vendor": "intel", + "name": device.get("name") or "Intel XPU", + "architecture": device.get("architecture"), + "memoryKind": device.get("memory_kind") or "dedicated", + "totalBytes": total, + "freeBytes": free, + "accessibleTotalBytes": _safe_int(device.get("torch_vram_total")) or total, + "dedicatedTotalBytes": _safe_int(device.get("dedicated_memory_total")), + "sharedTotalBytes": _safe_int(device.get("shared_memory_total")), + "capability": device.get("capability"), + "band": "shared_memory" if device.get("memory_kind") in {"shared", "unified"} else _vram_band(total), + } + if bool(torch_state.get("xpu_available")): + return { + "kind": "xpu", + "name": "Intel XPU", + "totalBytes": None, + "freeBytes": None, + "capability": None, + "band": "unknown", + } + device = device_by_kind["mps"] if device is not None: - return _mps_accelerator(device.get("name")) + return _mps_accelerator(device.get("name"), device) if bool(torch_state.get("mps_available")): return _mps_accelerator() @@ -1081,7 +1431,18 @@ def _legacy_accelerator_snapshot(runtime_fingerprint: dict[str, Any] | None) -> mps_devices = torch_state.get("mps_devices") first_mps = mps_devices[0] if isinstance(mps_devices, list) and mps_devices and isinstance(mps_devices[0], dict) else {} return _mps_accelerator(first_mps.get("name")) - if "cuda_available" in torch_state or "mps_available" in torch_state: + if bool(torch_state.get("xpu_available")): + xpu_devices = torch_state.get("xpu_devices") + first_xpu = xpu_devices[0] if isinstance(xpu_devices, list) and xpu_devices and isinstance(xpu_devices[0], dict) else {} + return { + "kind": "xpu", + "name": str(first_xpu.get("name") or "Intel XPU"), + "totalBytes": _safe_int(first_xpu.get("total_memory")), + "freeBytes": _safe_int(first_xpu.get("memory_free_bytes")), + "capability": None, + "band": _vram_band(_safe_int(first_xpu.get("total_memory"))), + } + if "cuda_available" in torch_state or "xpu_available" in torch_state or "mps_available" in torch_state: return _cpu_accelerator() return None @@ -1111,15 +1472,143 @@ def _hardware_snapshot(runtime_fingerprint: dict[str, Any] | None, data_dir: str normalized_hardware = get_hardware_snapshot(data_dir) except Exception: normalized_hardware = {} + system = normalized_hardware.get("system") if isinstance(normalized_hardware, dict) else None + system = system if isinstance(system, dict) else {} return { "runtimeFingerprint": _runtime_key(runtime_fingerprint), "runtime": runtime_fingerprint, + "platform": _normalized_platform_name(system.get("platform") or system.get("os") or system.get("os_name")), + "architecture": str(system.get("architecture") or "unknown").lower(), "accelerator": _accelerator_snapshot(runtime_fingerprint, normalized_hardware), "systemMemory": _system_memory_snapshot(normalized_hardware), "offloadDisk": _disk_snapshot(data_dir, normalized_hardware), } +def _normalized_platform_name(value: Any) -> str: + normalized = str(value or "").strip().lower() + if normalized.startswith("win") or normalized == "nt": + return "windows" + if normalized.startswith("darwin") or normalized.startswith("mac"): + return "macos" + if normalized.startswith("linux") or normalized == "posix": + return "linux" + return normalized or "unknown" + + +def _hardware_identity(hardware: dict[str, Any]) -> tuple[str, str, str, float | None]: + runtime = hardware.get("runtime") if isinstance(hardware.get("runtime"), dict) else {} + runtime_hardware = runtime.get("hardware") if isinstance(runtime.get("hardware"), dict) else {} + system = runtime_hardware.get("system") if isinstance(runtime_hardware.get("system"), dict) else {} + platform_name = _normalized_platform_name( + hardware.get("platform") or hardware.get("os") or system.get("platform") or system.get("os") or system.get("os_name") + ) + architecture = str(hardware.get("architecture") or system.get("architecture") or "unknown").strip().lower() + accelerator = hardware.get("accelerator") if isinstance(hardware.get("accelerator"), dict) else {} + backend = str(hardware.get("backend") or accelerator.get("backend") or accelerator.get("kind") or "cpu").lower() + if backend == "cuda" and str(hardware.get("runtimeBackend") or "").lower() == "rocm": + backend = "rocm" + capability_value = accelerator.get("capability") + try: + if isinstance(capability_value, (list, tuple)) and len(capability_value) >= 2: + capability = float(f"{int(capability_value[0])}.{int(capability_value[1])}") + else: + capability = float(str(capability_value)) if capability_value is not None else None + except (TypeError, ValueError): + capability = None + return platform_name, architecture, backend, capability + + +def _apply_catalog_hardware_support( + candidates: list[dict[str, Any]], hardware: dict[str, Any] +) -> list[dict[str, Any]]: + platform_name, architecture, backend, capability = _hardware_identity(hardware) + output: list[dict[str, Any]] = [] + for candidate in candidates: + item = _clone_candidate(candidate) + artifact = catalog_artifact(str(item.get("modelType") or ""), str(item.get("artifact") or "")) + missing: list[str] = [] + if artifact: + supported_platforms = {str(value).lower() for value in artifact.get("supportedPlatforms") or []} + supported_architectures = {str(value).lower() for value in artifact.get("supportedArchitectures") or []} + supported_backends = {str(value).lower() for value in artifact.get("supportedBackends") or []} + if str(artifact.get("trust") or "community") in AUTO_TRUST_LEVELS and not artifact.get("revision"): + missing.append("Qualified Auto artifact is missing an immutable catalog revision") + if platform_name != "unknown" and supported_platforms and platform_name not in supported_platforms: + missing.append(f"Artifact does not support {platform_name}") + if architecture != "unknown" and supported_architectures and architecture not in supported_architectures: + missing.append(f"Artifact does not support {architecture}") + if backend and supported_backends and backend not in supported_backends: + missing.append(f"Artifact does not support the {backend} backend") + if str(artifact.get("format") or "").lower() in {"nvfp4", "mxfp8"} and ( + backend != "cuda" or capability is None or capability < 10.0 + ): + missing.append("This artifact requires an NVIDIA Blackwell GPU (compute capability 10.0 or newer)") + if missing: + all_missing = list(dict.fromkeys([*(item.get("requirementsMissing") or []), *missing])) + message = "; ".join(missing) + item["requirementsMissing"] = all_missing + item["requirementsMatched"] = [] + item["canAutoRun"] = False + item["readiness"] = "known_bad" + item["skipReason"] = message + item["healthBadge"] = "Not suitable locally" + proof = item.get("proof") if isinstance(item.get("proof"), dict) else {} + item["proof"] = {**proof, "status": "known_bad", "source": "model_artifact_catalog", "message": message} + evidence = item.get("compatibilityEvidence") if isinstance(item.get("compatibilityEvidence"), dict) else {} + item["compatibilityEvidence"] = { + **evidence, + "level": "blocked", + "label": "Not suitable locally", + "source": "model_artifact_catalog", + "message": message, + } + output.append(item) + return output + + +def _apply_community_confirmation( + candidates: list[dict[str, Any]], form: dict[str, Any] +) -> list[dict[str, Any]]: + confirmed = str(form.get("confirmedCommunityArtifact") or "").strip().lower() + if not confirmed: + return candidates + output: list[dict[str, Any]] = [] + for candidate in candidates: + item = _clone_candidate(candidate) + repo = str(item.get("resolvedArtifact") or item.get("artifact") or "").strip().lower() + if ( + repo == confirmed + and item.get("requiresConfirmation") + and item.get("installed") + and not item.get("requirementsMissing") + ): + message = "Community artifact explicitly confirmed for this workflow and machine." + proof = item.get("proof") if isinstance(item.get("proof"), dict) else {} + item["proof"] = { + **proof, + "status": "declared_safe", + "source": "user_community_confirmation", + "message": message, + "checkedAt": _now_ms(), + } + item["canAutoRun"] = True + item["readiness"] = "ready" + item["skipReason"] = None + item["healthBadge"] = "Community option" + evidence = item.get("compatibilityEvidence") if isinstance(item.get("compatibilityEvidence"), dict) else {} + item["compatibilityEvidence"] = { + **evidence, + "level": "community", + "label": "Community option", + "source": "user_community_confirmation", + "message": message, + "checkedAt": _now_ms(), + } + output.append(item) + return output + + def _safe_int(value: Any) -> int | None: try: if value is None: @@ -1178,6 +1667,7 @@ def _requirements_missing( min_vram_bytes: int | None = None, min_system_ram_bytes: int | None = None, min_disk_free_bytes: int | None = None, + offload_mode: str = OFFLOAD_MODE_NONE, ) -> list[str]: missing = [] accelerator_state = hardware.get("accelerator") if isinstance(hardware.get("accelerator"), dict) else {} @@ -1186,8 +1676,29 @@ def _requirements_missing( missing.append("CUDA accelerator required") elif accelerator == "cuda_or_mps" and actual_accelerator not in {"cuda", "mps"}: missing.append("CUDA or MPS accelerator required") + elif accelerator in {"cuda_or_mps_or_xpu", "gpu"} and actual_accelerator not in {"cuda", "mps", "xpu"}: + missing.append("CUDA, Apple MPS, or Intel XPU accelerator required") + elif accelerator in {"cuda_or_mps_or_xpu_or_cpu", "gpu_or_cpu"} and actual_accelerator not in { + "cuda", "mps", "xpu", "cpu" + }: + missing.append("Supported accelerator or CPU required") total_vram = _safe_int(accelerator_state.get("totalBytes")) + memory_kind = str(accelerator_state.get("memoryKind") or "dedicated").lower() + if memory_kind in {"shared", "unified"} and ( + offload_mode in CPU_OR_DISK_OFFLOAD_MODES or actual_accelerator in {"mps", "xpu"} + ): + # Integrated and unified-memory accelerators do not have a discrete + # VRAM pool. For offloaded recipes, admission is based on the memory + # the runtime can actually address, while full-residency ranking keeps + # using local/dedicated capacity so an APU is never mistaken for a + # workstation GPU merely because it can borrow system RAM. + shared_capacity = max( + _safe_int(accelerator_state.get("accessibleTotalBytes")) or 0, + _safe_int(accelerator_state.get("sharedTotalBytes")) or 0, + ) + if shared_capacity: + total_vram = max(total_vram or 0, shared_capacity) if not _meets_total_capacity(total_vram, min_vram_bytes): missing.append(f"GPU memory requires at least {min_vram_bytes // GIB} GiB total") @@ -1201,10 +1712,43 @@ def _requirements_missing( return missing +def _qwen_dimension(value: Any, fallback: int) -> int: + try: + finite = float(value) + except (TypeError, ValueError): + finite = float(fallback) + if not finite > 0: + finite = float(fallback) + return max(256, int((finite + 1e-6) // 16) * 16) + + +def _qwen_generation_dimensions(form: dict[str, Any], *, native: bool) -> tuple[int, int]: + fallback_width = QWEN_NATIVE_WIDTH if native else QWEN_PRACTICAL_WIDTH + fallback_height = QWEN_NATIVE_HEIGHT if native else QWEN_PRACTICAL_HEIGHT + requested_width = _qwen_dimension(form.get("width"), fallback_width) + requested_height = _qwen_dimension(form.get("height"), fallback_height) + if native: + # A high-memory candidate must honor the user's/template's basic + # generation controls. The native quality preset supplies defaults + # only when dimensions are absent; it must never force every portrait + # and landscape workflow into a square 1328px render. + return requested_width, requested_height + + requested_pixels = requested_width * requested_height + scale = min( + 1.0, + (QWEN_PRACTICAL_PIXEL_BUDGET / requested_pixels) ** 0.5, + QWEN_AUTO_MAX_DIMENSION / max(requested_width, requested_height), + ) + return ( + _qwen_dimension(requested_width * scale, QWEN_PRACTICAL_WIDTH), + _qwen_dimension(requested_height * scale, QWEN_PRACTICAL_HEIGHT), + ) + + def _qwen_generation_defaults(form: dict[str, Any], hardware: dict[str, Any], *, native: bool = False) -> dict[str, Any]: negative = str(form.get("negativePrompt") or "").strip() or " " - width = QWEN_NATIVE_WIDTH if native else QWEN_PRACTICAL_WIDTH - height = QWEN_NATIVE_HEIGHT if native else QWEN_PRACTICAL_HEIGHT + width, height = _qwen_generation_dimensions(form, native=native) return { "width": width, "height": height, @@ -1230,23 +1774,21 @@ def _candidate_health_badge( missing: list[str], ) -> str: if proof_status == "live_proven": - return "Works here" + return "Ran here" if proof_status in READY_PROOF_STATUSES: - if "quant" in quality_tier.lower() or "lower-memory" in quality_tier.lower(): - return "Works with quantized artifact" - return "Works here" + return "This should work" if _artifact_needs_repair(artifact_status): return "Repair required" if proof_status == FAILED_HERE_PROOF_STATUS: return "Failed here before" if proof_status == "known_bad": - return "Will not work on this machine" + return "Not suitable locally" if proof_status == "manual_only" or manual_only_reason: return "Expert only" if missing: blocked = [item for item in missing if "requires at least" in item.lower() or "requires cuda" in item.lower() or "offload disk" in item.lower()] if blocked and not (artifact_status and artifact_status.get("installed") is False): - return "Will not work on this machine" + return "Not suitable locally" return "Needs setup" @@ -1289,6 +1831,7 @@ def _candidate( requirements: dict[str, Any] | None = None, required_packages: list[str] | None = None, install_action_label: str | None = None, + device_map: str | None = None, ) -> dict[str, Any]: missing = list(requirements_missing or []) known_bad = list(known_bad_reasons or []) @@ -1320,6 +1863,30 @@ def _candidate( artifact_status=artifact_status, quality_tier=quality_tier, ) + model_catalog = catalog_model(model_type) or {} + artifact_catalog = catalog_artifact(model_type, artifact) or {} + base_artifact = str(model_catalog.get("baseRepo") or artifact) + trust = str(artifact_catalog.get("trust") or ("official" if artifact == base_artifact else "community")) + artifact_format = str(artifact_catalog.get("format") or ("native" if artifact == base_artifact else "prequantized")) + is_prequantized_artifact = artifact.lower() != base_artifact.lower() and artifact_format != "native" + loaded_quantization = artifact_format if is_prequantized_artifact else quantization_mode + resolved_revision = artifact_catalog.get("revision") or ( + model_catalog.get("baseRevision") if artifact.lower() == base_artifact.lower() else None + ) + if trust not in AUTO_TRUST_LEVELS: + health_badge = "Community option" + if status in READY_PROOF_STATUSES: + status = "manual_only" + message = "Community artifact requires explicit confirmation for this workflow and machine." + evidence_level = ( + "ran_here" + if status == "live_proven" + else "documented" + if status in READY_PROOF_STATUSES + else "community" + if trust not in AUTO_TRUST_LEVELS + else "blocked" + ) return { "id": candidate_id, @@ -1329,15 +1896,46 @@ def _candidate( "executionPath": execution_path, "pipelineClass": pipeline_class, "artifact": artifact, + "baseArtifact": base_artifact, "artifactSource": "huggingface-cache", "modelRepo": artifact, "resolvedArtifact": artifact, + "artifactResolution": { + "base": {"repo": base_artifact, "revision": model_catalog.get("baseRevision")}, + "resolved": { + "repo": artifact, + "revision": resolved_revision, + "format": artifact_format, + "bits": artifact_catalog.get("bits"), + "quantization": loaded_quantization, + "components": list(artifact_catalog.get("components") or quantized_components), + }, + "substituted": artifact.lower() != base_artifact.lower(), + }, + "compatibilityEvidence": { + "level": evidence_level, + "label": health_badge, + "source": "model_artifact_catalog" if artifact_catalog else "static_auto_requirements", + "message": message, + "checkedAt": _now_ms(), + "popularity": { + "downloads": artifact_catalog.get("downloads"), + "likes": artifact_catalog.get("likes"), + } if artifact_catalog else None, + }, + "artifactTrust": trust, + "artifactFormat": artifact_format, + "requiresConfirmation": trust not in AUTO_TRUST_LEVELS, "dtype": dtype, - "quantizationMode": quantization_mode, + # This field controls runtime conversion in legacy/custom graphs. A + # prequantized artifact must never turn that conversion back on. + "quantizationMode": "none" if is_prequantized_artifact else quantization_mode, + "loadedQuantization": loaded_quantization, "quantizedComponents": quantized_components, "bnb4ComputeDtype": "bfloat16", "offloadMode": offload_mode, "autoOffload": offload_mode != OFFLOAD_MODE_NONE, + "deviceMap": device_map, "qualityTier": quality_tier, "qualityScore": max(0, 1000 - rank), "reason": reason, @@ -1373,6 +1971,62 @@ def _candidate( } +def _catalog_community_candidates( + *, + model_type: str, + mode: str, + execution_path: str, + pipeline_class: str, + generation: dict[str, Any], + local_models: list[dict[str, Any]] | None, + hardware: dict[str, Any], + requirements: dict[str, Any], + existing_artifacts: set[str], +) -> list[dict[str, Any]]: + model = catalog_model(model_type) or {} + installed = _repo_id_set(local_models) + minimum = requirements.get("minimum") if isinstance(requirements.get("minimum"), dict) else {} + missing = _requirements_missing_for_dict(hardware, minimum, offload_mode=OFFLOAD_MODE_MODEL_CPU) + output = [] + for index, artifact in enumerate(model.get("artifacts") or []): + if not isinstance(artifact, dict): + continue + repo = str(artifact.get("repo") or "") + trust = str(artifact.get("trust") or "community") + if not repo or repo.lower() in existing_artifacts or trust in AUTO_TRUST_LEVELS: + continue + if not community_artifact_is_discoverable(artifact): + continue + cache_status = _artifact_cache_status(repo, local_models) + candidate = _candidate( + candidate_id=f"{model_type or 'studio'}-{mode or 'mode'}-community-{index}", + rank=70 + index, + model_type=model_type, + mode=mode, + execution_path=execution_path, + artifact=repo, + dtype="bfloat16", + quantization_mode="none", + quantized_components=list(artifact.get("components") or []), + offload_mode=OFFLOAD_MODE_MODEL_CPU, + quality_tier="community-quantized-option", + reason=f"Popular Hugging Face community option; review its evidence before installing {repo}.", + generation=generation, + installed=bool(cache_status.get("installed")) or _has_installed(repo, installed), + requirements_missing=missing + _cache_missing_for_status(cache_status, "community"), + manual_only_reason="Community artifact requires explicit confirmation until MoDiff qualifies it on this runtime.", + artifact_status=cache_status, + pipeline_class=pipeline_class or None, + requirements={"minimum": minimum}, + install_action_label="Review community option", + ) + candidate["healthBadge"] = "Community option" + candidate["compatibilityEvidence"]["label"] = "Community option" + candidate["requiresConfirmation"] = True + output.append(candidate) + return output + + def _qwen_auto_offload_for(hardware: dict[str, Any]) -> str: free = _resource_value(hardware, "accelerator", "freeBytes") if free is not None and free < 6 * GIB: @@ -1380,6 +2034,16 @@ def _qwen_auto_offload_for(hardware: dict[str, Any]) -> str: return OFFLOAD_MODE_MODEL_CPU +def _qwen_native_offload_for(hardware: dict[str, Any]) -> str: + free = _resource_value(hardware, "accelerator", "freeBytes") + # The official BF16 artifact plus native-resolution activations fit with + # useful headroom above this boundary. Keep lower-memory systems on the + # established model-offload path. + if free is not None and free >= 64 * GIB: + return OFFLOAD_MODE_NONE + return _qwen_auto_offload_for(hardware) + + def _qwen_text_to_image_candidates( form: dict[str, Any], local_models: list[dict[str, Any]] | None, @@ -1393,20 +2057,22 @@ def _qwen_text_to_image_candidates( model_type = str(form.get("modelType") or "QwenImageModularPipeline") mode = str(form.get("mode") or "text_to_image") + offload_mode = _qwen_auto_offload_for(hardware) + native_offload_mode = _qwen_native_offload_for(hardware) prequantized_missing = _requirements_missing( hardware, accelerator="cuda", min_vram_bytes=10 * GIB, min_system_ram_bytes=24 * GIB, + offload_mode=offload_mode, ) official_missing = _requirements_missing( hardware, accelerator="cuda", min_vram_bytes=32 * GIB, min_system_ram_bytes=30 * GIB, + offload_mode=native_offload_mode, ) - - offload_mode = _qwen_auto_offload_for(hardware) prequantized_generation = _qwen_generation_defaults(form, hardware, native=False) official_generation = _qwen_generation_defaults(form, hardware, native=True) prequantized_cache_missing = [] @@ -1422,7 +2088,7 @@ def _qwen_text_to_image_candidates( rank=1, model_type=model_type, mode=mode, - execution_path="direct-qwen-image", + execution_path="direct-diffusers-image", artifact=QWEN_IMAGE_2512_PREQUANTIZED_REPO, dtype="bfloat16", quantization_mode="none", @@ -1440,7 +2106,7 @@ def _qwen_text_to_image_candidates( rank=2, model_type=model_type, mode=mode, - execution_path="direct-qwen-image", + execution_path="direct-diffusers-image", artifact=QWEN_IMAGE_2512_PREQUANTIZED_REPO, dtype="bfloat16", quantization_mode="none", @@ -1458,7 +2124,7 @@ def _qwen_text_to_image_candidates( rank=3, model_type=model_type, mode=mode, - execution_path="direct-qwen-image", + execution_path="direct-diffusers-image", artifact=QWEN_IMAGE_2512_PREQUANTIZED_REPO, dtype="bfloat16", quantization_mode="none", @@ -1474,6 +2140,7 @@ def _qwen_text_to_image_candidates( min_vram_bytes=10 * GIB, min_system_ram_bytes=20 * GIB, min_disk_free_bytes=20 * GIB, + offload_mode=OFFLOAD_MODE_GROUP_DISK, ) + prequantized_cache_missing, artifact_status=prequantized_cache_status, ), @@ -1482,12 +2149,13 @@ def _qwen_text_to_image_candidates( rank=4, model_type=model_type, mode=mode, - execution_path="direct-qwen-image", + execution_path="direct-diffusers-image", artifact=QWEN_IMAGE_2512_REPO, dtype="bfloat16", quantization_mode="none", quantized_components=[], - offload_mode=OFFLOAD_MODE_MODEL_CPU, + offload_mode=native_offload_mode, + device_map="cuda" if native_offload_mode == OFFLOAD_MODE_NONE else None, quality_tier="official-bf16-native-quality", reason="Use the official BF16 Diffusers repo only when hardware has enough GPU and system memory headroom.", generation=official_generation, @@ -1500,7 +2168,7 @@ def _qwen_text_to_image_candidates( rank=5, model_type=model_type, mode=mode, - execution_path="direct-qwen-image", + execution_path="direct-diffusers-image", artifact=QWEN_IMAGE_2512_REPO, dtype="bfloat16", quantization_mode="bnb_4bit", @@ -1536,6 +2204,10 @@ def _generation_for_requirements(model_type: str, form: dict[str, Any], generati "maxSequenceLength": int(_number_from_form_or_defaults(form, generation_defaults, "maxSequenceLength", 512)), "numFrames": int(_number_from_form_or_defaults(form, generation_defaults, "numFrames", 0)) or None, "audioDuration": float(_number_from_form_or_defaults(form, generation_defaults, "audioDuration", 0)) or None, + "extensionDuration": float( + _number_from_form_or_defaults(form, generation_defaults, "extensionDuration", 0) + ) + or None, "shift": float(_number_from_form_or_defaults(form, generation_defaults, "shift", 0)) or None, "qualityPreset": f"{model_type or 'studio'}-auto", } @@ -1546,13 +2218,19 @@ def _requirements_for_candidate(requirements: dict[str, Any], key: str, fallback return value if isinstance(value, dict) else fallback -def _requirements_missing_for_dict(hardware: dict[str, Any], requirement: dict[str, Any]) -> list[str]: +def _requirements_missing_for_dict( + hardware: dict[str, Any], + requirement: dict[str, Any], + *, + offload_mode: str = OFFLOAD_MODE_NONE, +) -> list[str]: return _requirements_missing( hardware, accelerator=str(requirement.get("accelerator") or "cuda_or_mps_or_cpu"), min_vram_bytes=_safe_int(requirement.get("vramBytes")), min_system_ram_bytes=_safe_int(requirement.get("systemRamBytes")), min_disk_free_bytes=_safe_int(requirement.get("diskFreeBytes")), + offload_mode=offload_mode, ) @@ -1584,13 +2262,40 @@ def _declared_profile_candidates( generation = _generation_for_requirements(model_type, form, generation_defaults) required_packages = requirements.get("requiredPackages") if isinstance(requirements.get("requiredPackages"), list) else [] supported_offload = requirements.get("supportedOffloadModes") if isinstance(requirements.get("supportedOffloadModes"), list) else [] - preferred_offload = str(form.get("offloadMode") or (supported_offload[0] if supported_offload else OFFLOAD_MODE_MODEL_CPU)) + # Auto owns the resource choice. A saved/template form can contain a stale + # Expert offload value from another machine, so use the declared constrained + # default until this runtime proves that full residency is appropriate. + # The supported list is a capability set, not a constrained-hardware + # preference order. Several modular profiles list `none` first so the UI + # can offer it in Expert mode; treating that as Auto's fallback silently + # selected full residency even when the full-residency requirements failed. + constrained_offload = next( + (mode for mode in supported_offload if str(mode) != OFFLOAD_MODE_NONE), + OFFLOAD_MODE_NONE if OFFLOAD_MODE_NONE in supported_offload else OFFLOAD_MODE_MODEL_CPU, + ) + accelerator = hardware.get("accelerator") if isinstance(hardware.get("accelerator"), dict) else {} + accelerator_kind = str(accelerator.get("kind") or "cpu") + # Diffusers/Accelerate CPU-offload hooks are currently qualified only for + # CUDA-device APIs (NVIDIA CUDA and AMD ROCm). MPS and XPU remain useful + # direct-residency execution devices and must not inherit an invalid CUDA + # offload recipe merely because a model profile lists one. + preferred_offload = OFFLOAD_MODE_NONE if accelerator_kind in {"mps", "xpu", "cpu"} else str(constrained_offload) + full_residency = requirements.get("fullResidency") if isinstance(requirements.get("fullResidency"), dict) else None + high_quality = requirements.get("highQuality") if isinstance(requirements.get("highQuality"), dict) else None + on_device_requirements = full_residency or high_quality + full_residency_ready = bool( + on_device_requirements + and OFFLOAD_MODE_NONE in supported_offload + and not _requirements_missing_for_dict(hardware, on_device_requirements) + ) + if full_residency_ready: + preferred_offload = OFFLOAD_MODE_NONE candidates: list[dict[str, Any]] = [] lower_memory = requirements.get("lowerMemory") if isinstance(requirements.get("lowerMemory"), dict) else None if lower_memory_repo: lower_req = _requirements_for_candidate(requirements, "lowerMemory", minimum) - lower_missing = _requirements_missing_for_dict(hardware, lower_req) + lower_missing = _requirements_missing_for_dict(hardware, lower_req, offload_mode=preferred_offload) lower_cache_status = _artifact_cache_status(lower_memory_repo, local_models) candidates.append(_candidate( candidate_id=f"{model_type or 'studio'}-{mode or 'mode'}-lower-memory-artifact", @@ -1610,55 +2315,26 @@ def _declared_profile_candidates( requirements_missing=lower_missing + _cache_missing_for_status(lower_cache_status, "lower-memory"), artifact_status=lower_cache_status, pipeline_class=pipeline_class or None, + device_map="cuda" if preferred_offload == OFFLOAD_MODE_NONE and accelerator_kind == "cuda" else None, requirements={ "minimum": requirements.get("minimum"), "recommended": requirements.get("recommended"), + "fullResidency": requirements.get("fullResidency"), "lowerMemory": requirements.get("lowerMemory"), "supportedOffloadModes": requirements.get("supportedOffloadModes"), + "coldLoadTarget": requirements.get("coldLoadTarget"), }, required_packages=required_packages, install_action_label="Install quantized artifact", )) - on_load = requirements.get("onLoadQuantization") if isinstance(requirements.get("onLoadQuantization"), dict) else None - if on_load and default_repo: - on_load_missing = _requirements_missing_for_dict(hardware, on_load) - default_cache_status = _artifact_cache_status(default_repo, local_models) - candidates.append(_candidate( - candidate_id=f"{model_type or 'studio'}-{mode or 'mode'}-on-load-quantized", - rank=20, - model_type=model_type, - mode=mode, - execution_path=execution_path, - artifact=default_repo, - dtype=str(form.get("dtype") or "bfloat16"), - quantization_mode=str(on_load.get("quantizationMode") or "none"), - quantized_components=list(on_load.get("quantizedComponents") or []), - offload_mode=OFFLOAD_MODE_GROUP_DISK if OFFLOAD_MODE_GROUP_DISK in supported_offload else preferred_offload, - quality_tier="on-load-quantized-guarded", - reason=str(requirements.get("guardedReason") or "Use a guarded on-load quantized Diffusers recipe with CPU/SSD offload where needed."), - generation=generation, - installed=bool(default_cache_status.get("installed")) or _has_installed(default_repo, installed), - requirements_missing=on_load_missing + _cache_missing_for_status(default_cache_status, "default"), - artifact_status=default_cache_status, - pipeline_class=pipeline_class or None, - requirements={ - "minimum": requirements.get("minimum"), - "recommended": requirements.get("recommended"), - "onLoadQuantization": requirements.get("onLoadQuantization"), - "supportedOffloadModes": requirements.get("supportedOffloadModes"), - }, - required_packages=required_packages, - install_action_label="Install model for Auto quantization", - )) - if default_repo: native_req = requirements.get("minimum") if isinstance(requirements.get("minimum"), dict) else minimum - native_missing = _requirements_missing_for_dict(hardware, native_req) + native_missing = _requirements_missing_for_dict(hardware, native_req, offload_mode=preferred_offload) default_cache_status = _artifact_cache_status(default_repo, local_models) candidates.append(_candidate( candidate_id=f"{model_type or 'studio'}-{mode or 'mode'}-native-bf16", - rank=30, + rank=5 if full_residency_ready else 30, model_type=model_type, mode=mode, execution_path=execution_path, @@ -1675,14 +2351,33 @@ def _declared_profile_candidates( manual_only_reason=str(manual_only_reason) if manual_only_reason else None, artifact_status=default_cache_status, pipeline_class=pipeline_class or None, + device_map="cuda" if preferred_offload == OFFLOAD_MODE_NONE and accelerator_kind == "cuda" else None, requirements={ "minimum": requirements.get("minimum"), "recommended": requirements.get("recommended"), + "fullResidency": requirements.get("fullResidency"), "supportedOffloadModes": requirements.get("supportedOffloadModes"), + "coldLoadTarget": requirements.get("coldLoadTarget"), }, required_packages=required_packages, )) + existing_artifacts = { + str(candidate.get("resolvedArtifact") or candidate.get("artifact") or "").lower() + for candidate in candidates + } + candidates.extend(_catalog_community_candidates( + model_type=model_type, + mode=mode, + execution_path=execution_path, + pipeline_class=pipeline_class, + generation=generation, + local_models=local_models, + hardware=hardware, + requirements=requirements, + existing_artifacts=existing_artifacts, + )) + if not candidates: candidates.append(_candidate( candidate_id=f"{model_type or 'studio'}-{mode or 'mode'}-expert-only", @@ -1710,6 +2405,103 @@ def _clone_candidate(candidate: dict[str, Any]) -> dict[str, Any]: return json.loads(json.dumps(candidate)) +def _normalized_history_entry_signature(entry: dict[str, Any]) -> dict[str, Any] | None: + stored = entry.get("signature") if isinstance(entry.get("signature"), dict) else {} + candidate = entry.get("candidate") if isinstance(entry.get("candidate"), dict) else None + if not candidate: + return None + candidate = _clone_candidate(candidate) + for key in ( + "modelType", + "mode", + "artifact", + "artifactRevision", + "dtype", + "quantizationMode", + "quantizedComponents", + "offloadMode", + "device", + "deviceMap", + "attentionBackend", + "regionalCompile", + "denoiserCache", + "channelsLast", + "layerwiseCasting", + "pipelineClass", + "executionPath", + ): + if candidate.get(key) is None and stored.get(key) is not None: + candidate[key] = stored[key] + hardware_fingerprint = stored.get("hardwareFingerprint") + hardware = {"runtimeFingerprint": hardware_fingerprint} if hardware_fingerprint else None + return _candidate_history_signature(candidate, hardware=hardware) + + +def _history_signatures_are_compatible(current: dict[str, Any], stored: dict[str, Any]) -> bool: + current_base = {key: value for key, value in current.items() if key != "workload"} + stored_base = {key: value for key, value in stored.items() if key != "workload"} + if current_base != stored_base: + return False + current_workload = current.get("workload") if isinstance(current.get("workload"), dict) else {} + stored_workload = stored.get("workload") if isinstance(stored.get("workload"), dict) else {} + if not stored_workload: + return False + # Newer signatures may add a media-specific dimension that old receipts + # could not record. Every dimension the older receipt did record must still + # match; missing new dimensions are accepted only for this migration path. + return all(key in current_workload and current_workload[key] == value for key, value in stored_workload.items()) + + +def _compatible_success_history_entry( + candidate: dict[str, Any], + *, + entries: dict[str, Any], + hardware: dict[str, Any], +) -> tuple[str, dict[str, Any]] | None: + current = _candidate_history_signature(candidate, hardware=hardware) + matches = [] + for key, value in entries.items(): + if not isinstance(value, dict) or not value.get("successCount"): + continue + if int(value.get("lastFailureAt") or 0) > int(value.get("lastSuccessAt") or 0): + continue + stored = _normalized_history_entry_signature(value) + if stored and _history_signatures_are_compatible(current, stored): + matches.append((int(value.get("lastSuccessAt") or 0), str(key), value)) + if not matches: + return None + _, key, entry = max(matches, key=lambda item: item[0]) + return key, entry + + +def _requirements_after_success_evidence( + candidate: dict[str, Any], + entry: dict[str, Any], + hardware: dict[str, Any], +) -> list[str]: + missing = [str(item) for item in candidate.get("requirementsMissing") or []] + measurement = entry.get("lastMeasurement") if isinstance(entry.get("lastMeasurement"), dict) else {} + accelerator = hardware.get("accelerator") if isinstance(hardware.get("accelerator"), dict) else {} + local_total = _safe_int(accelerator.get("totalBytes")) + peak = max( + _safe_int(measurement.get("peakReservedBytes")) or 0, + _safe_int(measurement.get("peakAllocatedBytes")) or 0, + _safe_int(measurement.get("reservedBytes")) or 0, + _safe_int(measurement.get("allocatedBytes")) or 0, + ) + system_total = _resource_value(hardware, "systemMemory", "totalBytes") + process_rss = _safe_int(measurement.get("processRssBytes")) + remaining = [] + for requirement in missing: + lowered = requirement.lower() + if lowered.startswith("gpu memory requires") and local_total and peak and peak <= local_total: + continue + if lowered.startswith("system memory requires") and system_total and process_rss and process_rss <= system_total: + continue + remaining.append(requirement) + return remaining + + def _apply_history_to_candidates( candidates: list[dict[str, Any]], *, @@ -1722,26 +2514,53 @@ def _apply_history_to_candidates( item = _clone_candidate(candidate) key = auto_resource_history_key(item, hardware=hardware) entry = entries.get(key) if isinstance(entries.get(key), dict) else None + compatible_key = None + if entry is None: + compatible = _compatible_success_history_entry(item, entries=entries, hardware=hardware) + if compatible: + compatible_key, entry = compatible item["historyKey"] = key + if compatible_key: + item["compatibleHistoryKey"] = compatible_key item["failureHistory"] = entry if entry and entry.get("failureCount") else None item["successHistory"] = entry if entry and entry.get("successCount") else None + if entry and entry.get("successCount") and item.get("installed"): + remaining_requirements = _requirements_after_success_evidence(item, entry, hardware) + item["requirementsMissing"] = remaining_requirements + if not remaining_requirements: + item["requirementsMatched"] = ["artifact", "hardware", "quality-defaults", "local-success"] if entry and entry.get("successCount") and not item.get("requirementsMissing") and item.get("installed"): proof = item.get("proof") if isinstance(item.get("proof"), dict) else {} proof = dict(proof) proof["status"] = "live_proven" - proof["source"] = "auto_resource_history" - proof["message"] = "This Auto candidate completed successfully on this machine before." + proof["source"] = "auto_resource_history_compatible" if compatible_key else "auto_resource_history" + proof["message"] = ( + "A compatible earlier Auto receipt completed successfully on this machine." + if compatible_key + else "This Auto candidate completed successfully on this machine before." + ) proof["checkedAt"] = _now_ms() item["proof"] = proof item["readiness"] = "ready" item["canAutoRun"] = True - item["healthBadge"] = "Works here" + item["healthBadge"] = "Ran here" + evidence = item.get("compatibilityEvidence") if isinstance(item.get("compatibilityEvidence"), dict) else {} + item["compatibilityEvidence"] = { + **evidence, + "level": "ran_here", + "label": "Ran here", + "source": "auto_resource_history", + "message": proof["message"], + "checkedAt": _now_ms(), + } item["qualityScore"] = int(item.get("qualityScore") or 0) + 250 if entry and entry.get("failureCount"): last_failure = entry.get("lastFailure") if isinstance(entry.get("lastFailure"), dict) else {} last_failure_at = int(entry.get("lastFailureAt") or 0) last_success_at = int(entry.get("lastSuccessAt") or 0) - if last_failure_at >= last_success_at: + # A success recorded in the same millisecond is the newest event in + # the synchronous completion path and must clear the prior failure. + if last_failure_at > last_success_at: proof = item.get("proof") if isinstance(item.get("proof"), dict) else {} message = str(last_failure.get("message") or "This Auto candidate failed on this machine before.") proof = dict(proof) @@ -1759,11 +2578,56 @@ def _apply_history_to_candidates( known_bad = item.get("knownBadReasons") if isinstance(item.get("knownBadReasons"), list) else [] item["knownBadReasons"] = list(dict.fromkeys([*known_bad, message])) output.append(item) - output.sort(key=lambda item: ( - 0 if (isinstance(item.get("proof"), dict) and item["proof"].get("status") == "live_proven") else 1, - item.get("rank") if isinstance(item.get("rank"), int) else 999, - -int(item.get("qualityScore") or 0), - )) + return output + + +def _preference_sort_key(candidate: dict[str, Any], preference: str) -> tuple[Any, ...]: + proof = candidate.get("proof") if isinstance(candidate.get("proof"), dict) else {} + status = str(proof.get("status") or "") + quality_tier = str(candidate.get("qualityTier") or "").lower() + trust = str(candidate.get("artifactTrust") or "community") + ran_here = 0 if status == "live_proven" else 1 + blocked = 0 if status in READY_PROOF_STATUSES else 1 + trusted = 0 if trust in AUTO_TRUST_LEVELS else 1 + native = 0 if "native" in quality_tier or not (candidate.get("artifactResolution") or {}).get("substituted") else 1 + quantized = 0 if "quant" in quality_tier or "lower-memory" in quality_tier else 1 + rank = int(candidate.get("rank") or 999) + install_only_needles = ( + "not installed", + "snapshot is incomplete", + "cached artifact", + "download is still incomplete", + "local weight files", + "zero bytes", + ".safetensors", + ) + hardware_blocked = 0 + for requirement in candidate.get("requirementsMissing") or []: + text = str(requirement).lower() + if not any(needle in text for needle in install_only_needles): + hardware_blocked = 1 + break + if preference == "best_quality": + return blocked, hardware_blocked, native, ran_here, trusted, rank + if preference == "faster": + history = candidate.get("successHistory") if isinstance(candidate.get("successHistory"), dict) else {} + elapsed = float(history.get("bestElapsedSeconds") or 1e30) + return blocked, hardware_blocked, ran_here, elapsed, trusted, rank + if preference == "lowest_memory": + bits = ((candidate.get("artifactResolution") or {}).get("resolved") or {}).get("bits") + return blocked, hardware_blocked, 0 if bits else 1, int(bits or 128), quantized, ran_here, rank + # Recommended is quality-leaning: use a compatible native artifact first, + # but prefer an exact successful lower-memory recipe over an estimate. + return blocked, hardware_blocked, ran_here, trusted, native, rank + + +def _rank_candidates_for_preference(candidates: list[dict[str, Any]], preference: str) -> list[dict[str, Any]]: + normalized = preference if preference in {"recommended", "best_quality", "faster", "lowest_memory"} else "recommended" + output = [_clone_candidate(candidate) for candidate in candidates] + output.sort(key=lambda item: _preference_sort_key(item, normalized)) + for index, candidate in enumerate(output): + candidate["preference"] = normalized + candidate["preferenceRank"] = index + 1 return output @@ -1852,7 +2716,7 @@ def _plan_health_badge( readiness: str, ) -> str: if selected: - return str(selected.get("healthBadge") or "Works here") + return str(selected.get("healthBadge") or "This should work") if selected_install_target and selected_install_target.get("repair"): return "Repair required" if selected_install_target: @@ -1861,11 +2725,95 @@ def _plan_health_badge( return "Expert only" if any(candidate.get("healthBadge") == "Failed here before" for candidate in candidates): return "Failed here before" - if any(candidate.get("healthBadge") == "Will not work on this machine" for candidate in candidates): - return "Will not work on this machine" + if any(candidate.get("healthBadge") == "Not suitable locally" for candidate in candidates): + return "Not suitable locally" return "Needs setup" +def _compatibility_assessment( + *, + selected: dict[str, Any] | None, + candidates: list[dict[str, Any]], + selected_install_target: dict[str, Any] | None, + readiness: str, + status_label: str, + blocking_reason: str | None, + will_not_work_reason: str | None, +) -> dict[str, Any]: + """Return the UI-facing result of the authoritative Auto assessment. + + Clients should render this result instead of independently interpreting + GPU memory, operating-system, accelerator, or model-fit thresholds. + """ + if selected: + proof = selected.get("proof") if isinstance(selected.get("proof"), dict) else {} + detail = ( + proof.get("message") + or selected.get("reason") + or "Auto selected a qualified recipe for the current runtime and installed models." + ) + return { + "state": "ready", + "severity": "success", + "code": "auto_recipe_ready", + "summary": status_label, + "detail": str(detail), + "action": None, + "source": "backend_auto_planner", + } + + if selected_install_target: + repair = bool(selected_install_target.get("repair")) + return { + "state": "needs_model", + "severity": "warning", + "code": "model_repair_required" if repair else "model_install_required", + "summary": "Repair required" if repair else "Model setup required", + "detail": str( + selected_install_target.get("reason") + or blocking_reason + or "Install the selected Auto artifact before running this workflow." + ), + "action": { + "type": "repair_model" if repair else "install_model", + "label": selected_install_target.get("actionLabel") + or ("Repair Auto artifact" if repair else "Install Auto artifact"), + "repo": selected_install_target.get("repo"), + "candidateId": selected_install_target.get("candidateId"), + }, + "source": "backend_auto_planner", + } + + if readiness == "manual_only": + return { + "state": "expert_only", + "severity": "warning", + "code": "expert_configuration_required", + "summary": "Expert configuration required", + "detail": str(blocking_reason or "This workflow does not have a qualified Auto recipe yet."), + "action": {"type": "switch_to_expert", "label": "Review in Expert mode"}, + "source": "backend_auto_planner", + } + + unsuitable = bool(will_not_work_reason) or any( + candidate.get("healthBadge") in {"Failed here before", "Not suitable locally"} + for candidate in candidates + ) + return { + "state": "unsuitable" if unsuitable else "needs_setup", + "severity": "error" if unsuitable else "warning", + "code": "no_qualified_local_recipe" if unsuitable else "auto_setup_required", + "summary": "Not suitable on this runtime" if unsuitable else status_label, + "detail": str( + will_not_work_reason + or blocking_reason + or "No qualified local Auto recipe matched this runtime and model cache." + ), + "action": {"type": "open_setup", "label": "Open Setup"}, + "source": "backend_auto_planner", + } + + def build_auto_resource_plan( request_payload: dict[str, Any] | None, *, @@ -1878,6 +2826,7 @@ def build_auto_resource_plan( form = payload.get("form") if isinstance(payload.get("form"), dict) else payload model_type = str(form.get("modelType") or "") mode = str(form.get("mode") or "") + preference = str(form.get("resourcePreference") or payload.get("resourcePreference") or "recommended") hardware_override = payload.get("hardwareOverride") if isinstance(payload.get("hardwareOverride"), dict) else None hardware = hardware_override or _hardware_snapshot(runtime_fingerprint, data_dir) @@ -1886,7 +2835,37 @@ def build_auto_resource_plan( else: candidates = _declared_profile_candidates(form, local_models, hardware) + candidates = _apply_catalog_hardware_support(candidates, hardware) + candidates = _apply_community_confirmation(candidates, form) candidates = _apply_history_to_candidates(candidates, history=history or read_auto_resource_history(data_dir), hardware=hardware) + candidates = _rank_candidates_for_preference(candidates, preference) + runtime_identity = ( + runtime_fingerprint.get("resourceFingerprint") + if isinstance(runtime_fingerprint, dict) + else runtime_fingerprint + ) + workload_key = workload_key_for_form(form) + for candidate in candidates: + artifact = str( + candidate.get("resolvedArtifact") + or candidate.get("artifact") + or candidate.get("modelRepo") + or "" + ) + overrides = qualified_auto_overrides( + runtime_fingerprint=runtime_identity, + model_type=str(candidate.get("modelType") or model_type), + mode=str(candidate.get("mode") or mode), + artifact=artifact, + workload_key=workload_key, + ) + if overrides: + candidate.update(overrides) + candidate["optimizationQualification"] = { + "status": "qualified", + "workloadKey": workload_key, + "selection": overrides, + } ready = [ candidate for candidate in candidates @@ -1923,7 +2902,7 @@ def build_auto_resource_plan( failed_reasons = [ str((candidate.get("proof") or {}).get("message") or candidate.get("skipReason")) for candidate in candidates - if candidate.get("healthBadge") in {"Failed here before", "Will not work on this machine"} + if candidate.get("healthBadge") in {"Failed here before", "Not suitable locally"} ] if failed_reasons and not selected: will_not_work_reason = failed_reasons[0] @@ -1936,9 +2915,21 @@ def build_auto_resource_plan( requirements_missing.extend(candidate.get("requirementsMissing") or []) known_bad_reasons.extend(candidate.get("knownBadReasons") or []) + compatibility = _compatibility_assessment( + selected=selected, + candidates=candidates, + selected_install_target=selected_install_target, + readiness=readiness, + status_label=status_label, + blocking_reason=blocking_reason, + will_not_work_reason=will_not_work_reason, + ) + return { "error": False, + "schemaVersion": AUTO_RESOURCE_SCHEMA_VERSION, "resourceMode": "auto", + "resourcePreference": preference, "status": status, "readiness": readiness, "statusLabel": status_label, @@ -1950,12 +2941,14 @@ def build_auto_resource_plan( selected_install_target=selected_install_target, readiness=readiness, ), + "compatibility": compatibility, "canAutoRun": bool(selected), "repairRequired": bool(selected_install_target and selected_install_target.get("repair")), "selectedInstallTarget": selected_install_target, "failureHistory": [candidate.get("failureHistory") for candidate in candidates if candidate.get("failureHistory")], "selectedCandidate": selected, "candidates": candidates, + "nextCandidate": next((candidate for candidate in candidates if candidate is not selected and candidate.get("proof", {}).get("status") in READY_PROOF_STATUSES), None), "hardware": hardware, "hardwareSnapshot": hardware, "modelRequirements": AUTO_MODEL_REQUIREMENTS, @@ -1995,6 +2988,7 @@ def build_auto_resource_plans( plans.append(plan) return { "error": False, + "schemaVersion": AUTO_RESOURCE_SCHEMA_VERSION, "resourceMode": "auto", "count": len(plans), "plans": plans, diff --git a/modiff/client.py b/modiff/client.py index 09fdc00..dc6b304 100644 --- a/modiff/client.py +++ b/modiff/client.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. from modiff.config import CONFIG import aiohttp import asyncio diff --git a/modiff/compatibility/accelerators.v1.json b/modiff/compatibility/accelerators.v1.json index 0ad89ca..4ea47d5 100644 --- a/modiff/compatibility/accelerators.v1.json +++ b/modiff/compatibility/accelerators.v1.json @@ -1,7 +1,7 @@ { "schema_version": 1, - "revision": "2026.07.12-rocm72", - "validated_at": "2026-07-12", + "revision": "2026.08.04-topology-xpu-contract", + "validated_at": "2026-08-04", "python": "3.12.*", "profiles": { "nvidia-cuda": { @@ -29,12 +29,25 @@ "proof": "amd-qualified-stack-awaiting-modiff-model-proof", "sources": ["https://rocm.docs.amd.com/projects/radeon-ryzen/en/docs-7.2/docs/compatibility/compatibilityryz/native_linux/native_linux_compatibility.html", "https://rocm.docs.amd.com/projects/radeon-ryzen/en/docs-7.2/docs/install/installryz/native_linux/install-pytorch.html"] }, "amd-pytorch-windows": { - "os": ["windows"], "architectures": ["x86_64"], "tier": "preview", - "torch": "2.8.0", "torchvision": "0.23.0", "torchaudio": "2.8.0", "cuda": null, "rocm": "windows-distribution", + "os": ["windows"], "architectures": ["x86_64"], "tier": "conditional", + "torch": "2.9.1+rocm7.2.1", "torchvision": "0.24.1+rocm7.2.1", "torchaudio": "2.9.1+rocm7.2.1", "cuda": null, "rocm": "7.2.1", "index": null, "requirements": "requirements/profiles/amd-pytorch-windows.txt", "required": [], "prohibited": ["bitsandbytes", "xformers", "nunchaku"], "default_dtype": "float16", "capabilities": ["torch-gpu", "amd-rocm", "fp16"], - "device_families": [], "proof": "manifest-fixtures-only", "sources": ["https://rocm.docs.amd.com/projects/radeon-ryzen/en/latest/docs/compatibility/compatibilityryz/windows/windows_compatibility.html"] + "device_families": ["gfx1100", "gfx1101", "gfx1150", "gfx1151", "gfx1200", "gfx1201"], + "qualified_os_versions": ["11"], + "proof": "official-pytorch-distribution-awaiting-modiff-model-proof", + "sources": ["https://rocm.docs.amd.com/projects/radeon-ryzen/en/latest/docs/compatibility/compatibilityryz/windows/windows_compatibility.html", "https://rocm.docs.amd.com/projects/radeon-ryzen/en/latest/docs/install/installryz/windows/install-pytorch.html"] + }, + "intel-xpu": { + "os": ["linux", "windows"], "architectures": ["x86_64"], "tier": "preview", + "torch": "2.12.1", "torchvision": "0.27.1", "torchaudio": null, "cuda": null, "rocm": null, + "index": "https://download.pytorch.org/whl/xpu", "requirements": "requirements/profiles/intel-xpu.txt", + "required": [], "prohibited": ["bitsandbytes", "xformers", "nunchaku"], + "default_dtype": "float16", "capabilities": ["torch-gpu", "intel-xpu", "fp16", "bf16", "integrated-memory"], + "device_families": ["alchemist", "battlemage", "meteor-lake", "arrow-lake", "lunar-lake", "panther-lake", "ponte-vecchio"], + "proof": "official-pytorch-xpu-prototype-awaiting-modiff-model-proof", + "sources": ["https://docs.pytorch.org/docs/stable/notes/get_start_xpu.html"] }, "apple-mps": { "os": ["macos"], "architectures": ["arm64"], "tier": "supported", diff --git a/modiff/config.py b/modiff/config.py index 94ae116..484886b 100644 --- a/modiff/config.py +++ b/modiff/config.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import configparser import logging import os @@ -46,7 +47,10 @@ def __init__(self): 'secure': cfg.getboolean('server', 'secure', fallback=False), 'certfile': cfg.get('server', 'ssl_cert', fallback=None), 'keyfile': cfg.get('server', 'ssl_key', fallback=None), - 'client_max_size': cfg.getint('server', 'client_max_size', fallback=1024**4), + # Keep the default aligned with config.example.ini. The previous + # 1 TiB fallback effectively disabled aiohttp's request-body guard + # and allowed a single client to exhaust process memory or disk. + 'client_max_size': cfg.getint('server', 'client_max_size', fallback=1024**3), } if self.server['certfile'] and self.server['keyfile']: if not os.path.exists(self.server['certfile']): diff --git a/modiff/diffusers_offload.py b/modiff/diffusers_offload.py index 661d857..805d992 100644 --- a/modiff/diffusers_offload.py +++ b/modiff/diffusers_offload.py @@ -5,23 +5,17 @@ import torch -logger = logging.getLogger("modiff") - -OFFLOAD_MODE_NONE = "none" -OFFLOAD_MODE_MODEL_CPU = "model_cpu" -OFFLOAD_MODE_SEQUENTIAL_CPU = "sequential_cpu" -OFFLOAD_MODE_GROUP_CPU = "group_cpu" -OFFLOAD_MODE_GROUP_DISK = "group_disk" -OFFLOAD_MODE_AUTO_CPU = "auto_cpu" - -OFFLOAD_MODE_OPTIONS = [ - OFFLOAD_MODE_NONE, - OFFLOAD_MODE_MODEL_CPU, - OFFLOAD_MODE_SEQUENTIAL_CPU, +from modiff.diffusers_offload_modes import ( + OFFLOAD_MODE_AUTO_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK, -] -OFFLOAD_MODE_LEGACY_OPTIONS = [OFFLOAD_MODE_AUTO_CPU, *OFFLOAD_MODE_OPTIONS] + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_OPTIONS, + OFFLOAD_MODE_SEQUENTIAL_CPU, +) + +logger = logging.getLogger("modiff") DEFAULT_GROUP_COMPONENTS = ( "transformer", @@ -38,6 +32,8 @@ # remain on CPU while their inputs are prepared on CUDA. Leaf-level hooks are # attached to the actual convolution/linear calls and cover both entry points. LEAF_LEVEL_GROUP_COMPONENTS = frozenset({"vae"}) +MIN_STREAMING_RAM_BYTES = 8 * 1024**3 +CPU_OFFLOAD_ACCELERATOR_TYPES = frozenset({"cuda"}) @dataclass @@ -50,15 +46,43 @@ class OffloadResult: detail: str | None = None -def normalize_offload_mode(mode, auto_offload=True): +def normalize_execution_device(device) -> torch.device: + """Return one canonical torch device for residency and offload decisions. + + PyTorch exposes AMD ROCm accelerators through the ``cuda`` device type, so + the CUDA branch intentionally covers both NVIDIA CUDA and AMD ROCm. Invalid + device strings are rejected here before an upstream offload helper can + install a partially configured hook. + """ + try: + normalized = torch.device(device) + except (RuntimeError, TypeError, ValueError) as exc: + raise ValueError(f"Unsupported execution device {device!r}.") from exc + if normalized.type == "cuda" and normalized.index is None: + return torch.device("cuda:0") + return normalized + + +def supports_accelerator_cpu_offload(device) -> bool: + """Whether Diffusers/Accelerate CPU-offload hooks may target ``device``.""" + return normalize_execution_device(device).type in CPU_OFFLOAD_ACCELERATOR_TYPES + + +def normalize_offload_mode(mode, auto_offload=True, device=None): if not auto_offload: return OFFLOAD_MODE_NONE if mode in (None, "", OFFLOAD_MODE_AUTO_CPU): - return OFFLOAD_MODE_MODEL_CPU - mode = str(mode) - if mode in OFFLOAD_MODE_OPTIONS: - return mode - return OFFLOAD_MODE_MODEL_CPU + normalized_mode = OFFLOAD_MODE_MODEL_CPU + else: + mode = str(mode) + normalized_mode = mode if mode in OFFLOAD_MODE_OPTIONS else OFFLOAD_MODE_MODEL_CPU + if ( + device is not None + and normalized_mode != OFFLOAD_MODE_NONE + and not supports_accelerator_cpu_offload(device) + ): + return OFFLOAD_MODE_NONE + return normalized_mode def offload_mode_param(default=OFFLOAD_MODE_MODEL_CPU, modes=None): @@ -90,6 +114,21 @@ def _is_group_offloaded(module): return False +def _streaming_offload_allowed(device, mode): + """Use pinned asynchronous transfers only on qualified accelerator paths.""" + if mode != OFFLOAD_MODE_GROUP_CPU or not supports_accelerator_cpu_offload(device): + return False + try: + if torch.are_deterministic_algorithms_enabled(): + return False + from modiff.hardware import system_memory_snapshot + + available = system_memory_snapshot().get("available_bytes") + return isinstance(available, int) and available >= MIN_STREAMING_RAM_BYTES + except Exception: + return False + + def _apply_group_to_module(module, *, component_name, device, node_id, scope, mode, offload_type="block_level", num_blocks_per_group=2): if module is None or not isinstance(module, torch.nn.Module): return None @@ -104,6 +143,7 @@ def _apply_group_to_module(module, *, component_name, device, node_id, scope, mo offload_to_disk_path = None if mode == OFFLOAD_MODE_GROUP_DISK: offload_to_disk_path = str(_disk_path(node_id, scope, component_name)) + use_stream = _streaming_offload_allowed(device, mode) try: apply_group_offloading( @@ -114,6 +154,9 @@ def _apply_group_to_module(module, *, component_name, device, node_id, scope, mo num_blocks_per_group=num_blocks_per_group if offload_type == "block_level" else None, low_cpu_mem_usage=True, offload_to_disk_path=offload_to_disk_path, + non_blocking=use_stream, + use_stream=use_stream, + record_stream=False, ) except Exception as exc: raise RuntimeError(f"Could not apply {mode} group offload to {component_name}: {exc}") from exc @@ -129,13 +172,34 @@ def apply_component_group_offload( node_id, scope="pipeline", ): + requested_mode = normalize_offload_mode(mode, auto_offload=mode != OFFLOAD_MODE_NONE) + normalized_device = normalize_execution_device(device) + mode = normalize_offload_mode(requested_mode, auto_offload=True, device=normalized_device) + if mode == OFFLOAD_MODE_NONE: + moved = [] + for component_name in component_names: + module = getattr(pipeline, component_name, None) + if isinstance(module, torch.nn.Module): + module.to(normalized_device) + moved.append(component_name) + return OffloadResult( + mode=mode, + applied=bool(moved), + method="to_device", + components=moved, + detail=( + f"{requested_mode} hooks require a CUDA/ROCm execution device; " + f"moved components to {normalized_device} without offload hooks." + ), + ) + applied = [] for component_name in component_names: offload_type = "leaf_level" if component_name in LEAF_LEVEL_GROUP_COMPONENTS else "block_level" applied_name = _apply_group_to_module( getattr(pipeline, component_name, None), component_name=component_name, - device=device, + device=normalized_device, node_id=node_id, scope=scope, mode=mode, @@ -154,8 +218,9 @@ def apply_model_offload(model, *, component_name, mode, device, node_id, scope=" inherit pipeline CPU/group hooks. Leaving them on CPU while a modular pipeline prepares CUDA inputs produces a late tensor-device mismatch. """ - mode = normalize_offload_mode(mode, auto_offload=mode != OFFLOAD_MODE_NONE) - device_text = str(device) + requested_mode = normalize_offload_mode(mode, auto_offload=mode != OFFLOAD_MODE_NONE) + normalized_device = normalize_execution_device(device) + mode = normalize_offload_mode(requested_mode, auto_offload=True, device=normalized_device) if not isinstance(model, torch.nn.Module): return OffloadResult( mode=mode, @@ -164,9 +229,20 @@ def apply_model_offload(model, *, component_name, mode, device, node_id, scope=" components=[], detail=f"{component_name} is not a torch.nn.Module.", ) - if mode == OFFLOAD_MODE_NONE or not device_text.startswith("cuda"): - model.to(torch.device(device)) - return OffloadResult(mode=mode, applied=True, method="to_device", components=[component_name]) + if mode == OFFLOAD_MODE_NONE: + model.to(normalized_device) + return OffloadResult( + mode=mode, + applied=True, + method="to_device", + components=[component_name], + detail=( + f"{requested_mode} hooks require a CUDA/ROCm execution device; " + f"moved {component_name} to {normalized_device} without offload hooks." + if requested_mode != OFFLOAD_MODE_NONE + else None + ), + ) effective_group_mode = mode if mode in (OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU): @@ -174,7 +250,7 @@ def apply_model_offload(model, *, component_name, mode, device, node_id, scope=" applied_name = _apply_group_to_module( model, component_name=component_name, - device=device, + device=normalized_device, node_id=node_id, scope=scope, mode=effective_group_mode, @@ -194,6 +270,114 @@ def apply_model_offload(model, *, component_name, mode, device, node_id, scope=" ) +def configure_components_manager_offload(manager, *, mode, device): + """Safely configure Modular Diffusers' shared ComponentsManager. + + ``ComponentsManager.enable_auto_cpu_offload`` queries accelerator free + memory through ``mem_get_info``. Calling it with CPU or MPS therefore + raises before a graph can load. Keep that upstream API behind the same + execution-device invariant as pipeline/model offload. + """ + requested_mode = normalize_offload_mode(mode, auto_offload=mode != OFFLOAD_MODE_NONE) + normalized_device = normalize_execution_device(device) + effective_mode = normalize_offload_mode( + requested_mode, + auto_offload=True, + device=normalized_device, + ) + enabled = bool(getattr(manager, "_auto_offload_enabled", False)) + + if effective_mode == OFFLOAD_MODE_MODEL_CPU: + configured_device = getattr(manager, "_auto_offload_device", None) + configured_device = ( + normalize_execution_device(configured_device) + if configured_device is not None + else None + ) + if not enabled or configured_device != normalized_device: + manager.enable_auto_cpu_offload(device=normalized_device) + return OffloadResult( + mode=effective_mode, + applied=True, + method="components_manager_auto_cpu_offload", + components=[], + ) + + if enabled: + manager.disable_auto_cpu_offload() + return OffloadResult( + mode=effective_mode, + applied=False, + method="components_manager_no_offload", + components=[], + detail=( + f"{requested_mode} hooks require a CUDA/ROCm execution device; " + f"kept components on {normalized_device} without offload hooks." + if requested_mode != OFFLOAD_MODE_NONE and effective_mode == OFFLOAD_MODE_NONE + else None + ), + ) + + +def reset_pipeline_device_map_for_runtime(pipeline): + """Detach Accelerate placement before applying runtime movement/offload. + + Diffusers pipelines loaded with ``device_map`` cannot safely be passed to + ``.to()``, model/sequential CPU offload, or group offload until their + placement hooks are reset. Quantized loaders commonly use a device map, so + centralize the transition here instead of relying on every loader to + remember the ordering contract. + """ + device_map = getattr(pipeline, "hf_device_map", None) + if not device_map: + return False + reset = getattr(pipeline, "reset_device_map", None) + if not callable(reset): + raise RuntimeError( + "This pipeline was loaded with a device map, but it cannot reset that placement before runtime offload. " + "Load it without device_map or use a Diffusers pipeline that exposes reset_device_map()." + ) + reset() + return True + + +def _device_map_is_fully_on_target(pipeline, device): + device_map = getattr(pipeline, "hf_device_map", None) + target = torch.device(device) + if target.type != "cuda": + return False + target_index = 0 if target.index is None else target.index + + # Diffusers intentionally preserves an explicit device-type strategy such + # as ``device_map="cuda"`` as a string on the pipeline. It already means + # every component was materialized on that accelerator, so resetting it + # before a no-offload run would create a CPU copy and then migrate the + # entire pipeline a second time. + if isinstance(device_map, str): + try: + placement_device = torch.device(device_map) + except (RuntimeError, TypeError): + return False + placement_index = 0 if placement_device.index is None else placement_device.index + return placement_device.type == "cuda" and placement_index == target_index + + if not isinstance(device_map, dict) or not device_map: + return False + + for placement in device_map.values(): + if isinstance(placement, int): + placement_device = torch.device("cuda", placement) + else: + try: + placement_device = torch.device(placement) + except (RuntimeError, TypeError): + return False + placement_index = 0 if placement_device.index is None else placement_device.index + if placement_device.type != "cuda" or placement_index != target_index: + return False + return True + + def apply_pipeline_offload( pipeline, *, @@ -204,55 +388,85 @@ def apply_pipeline_offload( component_names: Iterable[str] = DEFAULT_GROUP_COMPONENTS, prefer_pipeline_group=True, ): - mode = normalize_offload_mode(mode, auto_offload=mode != OFFLOAD_MODE_NONE) - device_text = str(device) + requested_mode = normalize_offload_mode(mode, auto_offload=mode != OFFLOAD_MODE_NONE) + normalized_device = normalize_execution_device(device) + mode = normalize_offload_mode(requested_mode, auto_offload=True, device=normalized_device) + device_text = str(normalized_device) + + # A pipeline streamed directly to one accelerator already satisfies the + # no-offload contract. Resetting that map first can materialize a second + # CPU copy before ``pipeline.to()``, which is fatal on unified-memory hosts + # even when the resident model itself fits. + if mode == OFFLOAD_MODE_NONE and _device_map_is_fully_on_target(pipeline, normalized_device): + return OffloadResult( + mode=mode, + applied=True, + method="preserve_device_map", + components=[], + detail=f"Pipeline is already fully resident on {device_text}.", + ) - if mode == OFFLOAD_MODE_NONE: - pipeline.to(torch.device(device)) - return OffloadResult(mode=mode, applied=True, method="to_device", components=[]) + reset_pipeline_device_map_for_runtime(pipeline) - if not device_text.startswith("cuda"): - pipeline.to(torch.device(device)) + if mode == OFFLOAD_MODE_NONE: + pipeline.to(normalized_device) return OffloadResult( mode=mode, - applied=False, + applied=True, method="to_device", components=[], - detail=f"{mode} offload is CUDA-oriented; moved pipeline to {device_text}.", + detail=( + f"{requested_mode} hooks require a CUDA/ROCm execution device; " + f"moved pipeline to {device_text} without offload hooks." + if requested_mode != OFFLOAD_MODE_NONE + else None + ), ) if mode == OFFLOAD_MODE_MODEL_CPU: if not hasattr(pipeline, "enable_model_cpu_offload"): raise RuntimeError("This Diffusers pipeline does not expose enable_model_cpu_offload().") - pipeline.enable_model_cpu_offload(device=device) + pipeline.enable_model_cpu_offload(device=normalized_device) return OffloadResult(mode=mode, applied=True, method="enable_model_cpu_offload", components=[]) if mode == OFFLOAD_MODE_SEQUENTIAL_CPU: if not hasattr(pipeline, "enable_sequential_cpu_offload"): raise RuntimeError("This Diffusers pipeline does not expose enable_sequential_cpu_offload().") - pipeline.enable_sequential_cpu_offload(device=device) + pipeline.enable_sequential_cpu_offload(device=normalized_device) return OffloadResult(mode=mode, applied=True, method="enable_sequential_cpu_offload", components=[]) if mode in (OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK): disk_path = str(_disk_path(node_id, scope)) if mode == OFFLOAD_MODE_GROUP_DISK else None + use_stream = _streaming_offload_allowed(normalized_device, mode) if prefer_pipeline_group and hasattr(pipeline, "enable_group_offload"): try: pipeline.enable_group_offload( - onload_device=torch.device(device), + onload_device=normalized_device, offload_device=torch.device("cpu"), offload_type="block_level", num_blocks_per_group=2, low_cpu_mem_usage=True, offload_to_disk_path=disk_path, + non_blocking=use_stream, + use_stream=use_stream, + record_stream=False, + ) + detail = "Pinned asynchronous prefetch enabled." if use_stream else "Synchronous group transfers." + return OffloadResult( + mode=mode, + applied=True, + method="enable_group_offload", + components=[], + disk_path=disk_path, + detail=detail, ) - return OffloadResult(mode=mode, applied=True, method="enable_group_offload", components=[], disk_path=disk_path) except Exception as exc: logger.warning("Pipeline group offload failed; trying component-level group offload: %s", exc) result = apply_component_group_offload( pipeline, component_names=component_names, - device=device, + device=normalized_device, mode=mode, node_id=node_id, scope=scope, diff --git a/modiff/diffusers_offload_modes.py b/modiff/diffusers_offload_modes.py new file mode 100644 index 0000000..7968c6b --- /dev/null +++ b/modiff/diffusers_offload_modes.py @@ -0,0 +1,17 @@ +"""Torch-free public constants for Diffusers residency planning.""" + +OFFLOAD_MODE_NONE = "none" +OFFLOAD_MODE_MODEL_CPU = "model_cpu" +OFFLOAD_MODE_SEQUENTIAL_CPU = "sequential_cpu" +OFFLOAD_MODE_GROUP_CPU = "group_cpu" +OFFLOAD_MODE_GROUP_DISK = "group_disk" +OFFLOAD_MODE_AUTO_CPU = "auto_cpu" + +OFFLOAD_MODE_OPTIONS = [ + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, +] +OFFLOAD_MODE_LEGACY_OPTIONS = [OFFLOAD_MODE_AUTO_CPU, *OFFLOAD_MODE_OPTIONS] diff --git a/modiff/diffusers_profiles.py b/modiff/diffusers_profiles.py index 00ead6b..bebb556 100644 --- a/modiff/diffusers_profiles.py +++ b/modiff/diffusers_profiles.py @@ -2,7 +2,7 @@ from dataclasses import asdict, dataclass -from modiff.diffusers_offload import ( +from modiff.diffusers_offload_modes import ( OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK, OFFLOAD_MODE_MODEL_CPU, @@ -21,7 +21,17 @@ FLUX_FILL_REPO = "black-forest-labs/FLUX.1-Fill-dev" FLUX_DEPTH_REPO = "black-forest-labs/FLUX.1-Depth-dev" FLUX_CANNY_REPO = "black-forest-labs/FLUX.1-Canny-dev" +FLUX_CANNY_VERIFIED_REPAIR_REPO = "fuliucansheng/FLUX.1-Canny-dev-diffusers" FLUX_REDUX_REPO = "black-forest-labs/FLUX.1-Redux-dev" +FLUX2_KLEIN_REPO = "black-forest-labs/FLUX.2-klein-4B" +LTX_VIDEO_REPO = "Lightricks/LTX-Video-0.9.8-13B-distilled" +LTX_VIDEO_FALLBACK_REPO = "Lightricks/LTX-Video" +WAN_T2V_1_3B_REPO = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" +WAN_22_TI2V_5B_REPO = "Wan-AI/Wan2.2-TI2V-5B-Diffusers" + +VERIFIED_REPAIR_SOURCES = { + FLUX_CANNY_REPO: FLUX_CANNY_VERIFIED_REPAIR_REPO, +} @dataclass(frozen=True) @@ -67,7 +77,7 @@ def to_public_dict(self) -> dict: id="qwen-image:t2i-direct", model_type="QwenImageModularPipeline", modes=("text_to_image",), - backend_path="modules.QwenImage.LoadPipeline", + backend_path="modules.DiffusersImage.LoadPipeline", pipeline_class="QwenImagePipeline", default_repo=QWEN_IMAGE_2512_REPO, fallback_repo=QWEN_IMAGE_2512_PREQUANTIZED_REPO, @@ -105,7 +115,7 @@ def to_public_dict(self) -> dict: id="qwen-edit:direct-inpaint", model_type="QwenImageEditModularPipeline", modes=("inpaint", "outpaint"), - backend_path="modules.QwenImage.LoadInpaintPipeline", + backend_path="modules.DiffusersImage.LoadPipeline", pipeline_class="QwenImageEditInpaintPipeline", default_repo="Qwen/Qwen-Image-Edit", fallback_repo=None, @@ -123,6 +133,22 @@ def to_public_dict(self) -> dict: max_low_memory_steps=24, live_proof=False, ), + "qwen-edit:modular": DiffusersExecutionProfile( + id="qwen-edit:modular", + model_type="QwenImageEditModularPipeline", + modes=("edit_image",), + backend_path="modules.ModularDiffusers.ModelsLoader", + pipeline_class="QwenImageEditModularPipeline", + default_repo="Qwen/Qwen-Image-Edit", + fallback_repo=None, + quantizable_components=("transformer", "text_encoder"), + default_quantized_components=("transformer", "text_encoder"), + supported_offload_modes=(OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK), + retry_offload_modes=(OFFLOAD_MODE_GROUP_DISK,), + max_low_memory_side=768, + max_low_memory_steps=24, + live_proof=False, + ), "qwen-edit-plus:modular": DiffusersExecutionProfile( id="qwen-edit-plus:modular", model_type="QwenImageEditPlusModularPipeline", @@ -160,15 +186,11 @@ def to_public_dict(self) -> dict: model_type="WanVACEPipeline", modes=( "text_to_video", - "image_to_video", - "video_to_video", "video_inpaint", "video_outpaint", - "reference_to_video", "control_to_video", - "video_color_edit", ), - backend_path="modules.WanVACE.LoadPipeline", + backend_path="modules.DiffusersVideo.LoadPipeline", pipeline_class="WanVACEPipeline", default_repo="Wan-AI/Wan2.1-VACE-1.3B-diffusers", fallback_repo=None, @@ -186,6 +208,115 @@ def to_public_dict(self) -> dict: max_low_memory_steps=24, live_proof=False, ), + "wan-video-to-video:direct": DiffusersExecutionProfile( + id="wan-video-to-video:direct", + model_type="WanVideoPipeline", + modes=("video_to_video", "video_color_edit"), + backend_path="modules.DiffusersVideo.LoadPipeline", + pipeline_class="WanVideoToVideoPipeline", + default_repo=WAN_T2V_1_3B_REPO, + fallback_repo=None, + quantizable_components=(), + default_quantized_components=(), + supported_offload_modes=( + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ), + retry_offload_modes=(OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_DISK), + max_low_memory_side=832, + max_low_memory_steps=30, + live_proof=False, + ), + "wan-text-to-video:direct": DiffusersExecutionProfile( + id="wan-text-to-video:direct", + model_type="WanVideoPipeline", + modes=("text_to_video",), + backend_path="modules.DiffusersVideo.LoadPipeline", + pipeline_class="WanPipeline", + default_repo=WAN_T2V_1_3B_REPO, + fallback_repo=None, + quantizable_components=(), + default_quantized_components=(), + supported_offload_modes=( + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ), + retry_offload_modes=(OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_DISK), + max_low_memory_side=832, + max_low_memory_steps=30, + live_proof=True, + ), + "wan-22-image-to-video:direct": DiffusersExecutionProfile( + id="wan-22-image-to-video:direct", + model_type="WanImageToVideoPipeline", + modes=("image_to_video",), + backend_path="modules.DiffusersVideo.LoadPipeline", + pipeline_class="WanImageToVideoPipeline", + default_repo="Wan-AI/Wan2.2-I2V-A14B-Diffusers", + fallback_repo=None, + quantizable_components=("transformer", "transformer_2", "text_encoder"), + default_quantized_components=("transformer", "transformer_2"), + supported_offload_modes=( + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ), + retry_offload_modes=(OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_DISK), + max_low_memory_side=832, + max_low_memory_steps=40, + live_proof=False, + ), + "wan-22-ti2v-5b:direct": DiffusersExecutionProfile( + id="wan-22-ti2v-5b:direct", + model_type="WanTI2VPipeline", + modes=("text_to_video",), + backend_path="modules.DiffusersVideo.LoadPipeline", + pipeline_class="WanTI2VPipeline", + default_repo=WAN_22_TI2V_5B_REPO, + fallback_repo=None, + quantizable_components=("transformer", "text_encoder"), + default_quantized_components=(), + supported_offload_modes=( + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ), + retry_offload_modes=(OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK), + max_low_memory_side=1280, + max_low_memory_steps=50, + live_proof=False, + ), + "ltx-video:direct": DiffusersExecutionProfile( + id="ltx-video:direct", + model_type="LTXVideoPipeline", + modes=("text_to_video", "image_to_video", "video_to_video", "reference_to_video"), + backend_path="modules.DiffusersVideo.LoadPipeline", + pipeline_class="LTXConditionPipeline", + default_repo=LTX_VIDEO_REPO, + fallback_repo=LTX_VIDEO_FALLBACK_REPO, + quantizable_components=("transformer", "text_encoder"), + default_quantized_components=(), + supported_offload_modes=( + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ), + retry_offload_modes=(OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_DISK), + max_low_memory_side=704, + max_low_memory_steps=8, + live_proof=False, + ), "ace-step-audio:direct": DiffusersExecutionProfile( id="ace-step-audio:direct", model_type="AceStepAudioPipeline", @@ -249,5 +380,149 @@ def to_public_dict(self) -> dict: } +def _flux_execution_profile( + profile_id: str, + model_type: str, + modes: tuple[str, ...], + pipeline_class: str, + repo: str, + *, + live_proof: bool = False, +) -> DiffusersExecutionProfile: + """Build the shared generic-image execution contract for FLUX variants.""" + + return DiffusersExecutionProfile( + id=profile_id, + model_type=model_type, + modes=modes, + backend_path="modules.DiffusersImage.LoadPipeline", + pipeline_class=pipeline_class, + default_repo=repo, + fallback_repo=None, + quantizable_components=("transformer", "text_encoder_2"), + default_quantized_components=("transformer",), + supported_offload_modes=( + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ), + retry_offload_modes=(OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_DISK), + max_low_memory_side=768, + max_low_memory_steps=24, + live_proof=live_proof, + ) + + +DIFFUSERS_EXECUTION_PROFILES.update( + { + "flux2-klein:direct": _flux_execution_profile( + "flux2-klein:direct", + "Flux2KleinPipeline", + ("text_to_image", "edit_image", "multi_image_reference_edit"), + "Flux2KleinPipeline", + FLUX2_KLEIN_REPO, + live_proof=True, + ), + "flux-krea:direct": _flux_execution_profile( + "flux-krea:direct", "FluxKreaPipeline", ("text_to_image",), "FluxPipeline", FLUX_KREA_REPO + ), + "flux-kontext:direct": _flux_execution_profile( + "flux-kontext:direct", + "FluxKontextPipeline", + ("edit_image", "multi_image_reference_edit"), + "FluxKontextPipeline", + FLUX_KONTEXT_REPO, + ), + "flux-fill:direct": _flux_execution_profile( + "flux-fill:direct", "FluxFillPipeline", ("inpaint", "outpaint"), "FluxFillPipeline", FLUX_FILL_REPO + ), + "flux-depth:direct": _flux_execution_profile( + "flux-depth:direct", "FluxDepthPipeline", ("control_image",), "FluxControlPipeline", FLUX_DEPTH_REPO + ), + "flux-canny:direct": _flux_execution_profile( + "flux-canny:direct", "FluxCannyPipeline", ("control_image",), "FluxControlPipeline", FLUX_CANNY_REPO + ), + "flux-redux:direct": _flux_execution_profile( + "flux-redux:direct", + "FluxReduxPipeline", + ("edit_image",), + "FluxReduxPipeline", + FLUX_REDUX_REPO, + ), + } +) + +EXPERIMENTAL_DIFFUSERS_PIPELINES = [ + { + "modelType": "StableDiffusionXLModularPipeline", + "label": "Stable Diffusion XL (Modular)", + "mediaKind": "image", + "pipelineClasses": ["StableDiffusionXLModularPipeline"], + "runnableModes": ["text_to_image", "image_to_image", "inpaint", "control_image"], + }, + { + "modelType": "FluxModularPipeline", + "label": "FLUX (Modular)", + "mediaKind": "image", + "pipelineClasses": ["FluxModularPipeline"], + "runnableModes": ["text_to_image", "image_to_image", "control_image"], + }, + { + "modelType": "Flux2KleinModularPipeline", + "label": "FLUX.2 Klein (Modular)", + "mediaKind": "image", + "defaultRepo": "black-forest-labs/FLUX.2-klein-4B", + "pipelineClasses": ["Flux2KleinPipeline", "Flux2KleinModularPipeline"], + "backendPath": "modules.DiffusersImage.LoadPipeline", + "runnableModes": ["text_to_image", "edit_image", "multi_image_reference_edit"], + }, + { + "modelType": "WanModularPipeline", + "label": "Wan Text to Video (Modular)", + "mediaKind": "video", + "pipelineClasses": ["WanModularPipeline"], + "runnableModes": ["text_to_video"], + }, + { + "modelType": "WanImage2VideoModularPipeline", + "label": "Wan Image to Video (Modular)", + "mediaKind": "video", + "pipelineClasses": ["WanImage2VideoModularPipeline"], + "runnableModes": ["image_to_video"], + }, +] + + +def public_experimental_pipelines() -> list[dict]: + parameter_aliases = { + "modelRepository": ["model_id", "model", "repo"], + "guidanceScale": ["guidance_scale", "true_cfg_scale", "guidance"], + "steps": ["num_inference_steps", "steps"], + "sourceImage": ["image", "reference_images"], + "maskImage": ["mask_image", "mask"], + "controlImage": ["control_image", "conditioning_image"], + } + return [ + { + **pipeline, + "schemaVersion": 2, + "supportTier": "experimental", + "executionProfiles": [], + "inputContracts": pipeline.get("inputContracts", {}), + "parameterAliases": parameter_aliases, + "defaults": pipeline.get("defaults", {}), + "artifactCandidates": [pipeline["defaultRepo"]] if pipeline.get("defaultRepo") else [], + "revisionCandidates": pipeline.get("revisionCandidates", []), + "quantizationSupport": pipeline.get( + "quantizationSupport", + {"defaultMode": "none", "components": [], "offloadModes": []}, + ), + "qualificationStatus": pipeline.get("qualificationStatus", "unqualified"), + } + for pipeline in EXPERIMENTAL_DIFFUSERS_PIPELINES + ] + + def public_execution_profiles() -> list[dict]: return [profile.to_public_dict() for profile in DIFFUSERS_EXECUTION_PROFILES.values()] diff --git a/modiff/disk_activity.py b/modiff/disk_activity.py new file mode 100644 index 0000000..767c210 --- /dev/null +++ b/modiff/disk_activity.py @@ -0,0 +1,201 @@ +"""Best-effort active-time sampling for the disk backing a runtime path.""" + +from __future__ import annotations + +import ctypes +import os +import re +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Callable + + +@dataclass(frozen=True) +class DiskActivityCounters: + key: str + active_ticks: float + total_ticks: float + source: str + + +def _existing_path(path: str | os.PathLike[str]) -> Path: + candidate = Path(path).expanduser() + while not candidate.exists() and candidate != candidate.parent: + candidate = candidate.parent + return candidate.resolve(strict=True) + + +def _windows_disk_activity_counters(path: str | os.PathLike[str]) -> DiskActivityCounters | None: + """Read the physical disk idle/query counters used for Windows active time.""" + + if os.name != "nt": + return None + + from ctypes import wintypes + + resolved = _existing_path(path) + drive = resolved.drive.rstrip("\\/") + if not re.fullmatch(r"[A-Za-z]:", drive): + return None + + class StorageDeviceNumber(ctypes.Structure): + _fields_ = [ + ("device_type", wintypes.DWORD), + ("device_number", wintypes.DWORD), + ("partition_number", wintypes.DWORD), + ] + + class DiskPerformance(ctypes.Structure): + _fields_ = [ + ("bytes_read", ctypes.c_longlong), + ("bytes_written", ctypes.c_longlong), + ("read_time", ctypes.c_longlong), + ("write_time", ctypes.c_longlong), + ("idle_time", ctypes.c_longlong), + ("read_count", wintypes.DWORD), + ("write_count", wintypes.DWORD), + ("queue_depth", wintypes.DWORD), + ("split_count", wintypes.DWORD), + ("query_time", ctypes.c_longlong), + ("storage_device_number", wintypes.DWORD), + ("storage_manager_name", wintypes.WCHAR * 8), + ] + + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + kernel32.CreateFileW.argtypes = [ + wintypes.LPCWSTR, + wintypes.DWORD, + wintypes.DWORD, + wintypes.LPVOID, + wintypes.DWORD, + wintypes.DWORD, + wintypes.HANDLE, + ] + kernel32.CreateFileW.restype = wintypes.HANDLE + kernel32.DeviceIoControl.argtypes = [ + wintypes.HANDLE, + wintypes.DWORD, + wintypes.LPVOID, + wintypes.DWORD, + wintypes.LPVOID, + wintypes.DWORD, + ctypes.POINTER(wintypes.DWORD), + wintypes.LPVOID, + ] + kernel32.DeviceIoControl.restype = wintypes.BOOL + kernel32.CloseHandle.argtypes = [wintypes.HANDLE] + kernel32.CloseHandle.restype = wintypes.BOOL + + invalid_handle = wintypes.HANDLE(-1).value + share_read_write = 0x00000001 | 0x00000002 + open_existing = 3 + ioctl_storage_get_device_number = 0x002D1080 + ioctl_disk_performance = 0x00070020 + + def open_device(name: str): + handle = kernel32.CreateFileW(name, 0, share_read_write, None, open_existing, 0, None) + if handle == invalid_handle: + raise ctypes.WinError(ctypes.get_last_error()) + return handle + + def device_io_control(handle, code: int, output: ctypes.Structure) -> None: + returned = wintypes.DWORD() + if not kernel32.DeviceIoControl( + handle, + code, + None, + 0, + ctypes.byref(output), + ctypes.sizeof(output), + ctypes.byref(returned), + None, + ): + raise ctypes.WinError(ctypes.get_last_error()) + + volume_handle = open_device(rf"\\.\{drive}") + try: + device = StorageDeviceNumber() + device_io_control(volume_handle, ioctl_storage_get_device_number, device) + finally: + kernel32.CloseHandle(volume_handle) + + disk_handle = open_device(rf"\\.\PhysicalDrive{device.device_number}") + try: + performance = DiskPerformance() + device_io_control(disk_handle, ioctl_disk_performance, performance) + finally: + kernel32.CloseHandle(disk_handle) + + return DiskActivityCounters( + key=f"windows-physical-disk:{device.device_number}", + active_ticks=float(performance.query_time - performance.idle_time), + total_ticks=float(performance.query_time), + source="windows-physical-disk", + ) + + +def _linux_disk_activity_counters(path: str | os.PathLike[str]) -> DiskActivityCounters | None: + """Read Linux's cumulative milliseconds spent doing I/O for the path device.""" + + if not sys_platform_linux(): + return None + resolved = _existing_path(path) + device_number = resolved.stat().st_dev + device_link = Path("/sys/dev/block") / f"{os.major(device_number)}:{os.minor(device_number)}" + device_path = device_link.resolve(strict=True) + fields = (device_link / "stat").read_text(encoding="utf-8").split() + if len(fields) < 10: + return None + return DiskActivityCounters( + key=f"linux-sysfs:{device_path}", + active_ticks=float(fields[9]), + total_ticks=time.monotonic() * 1000.0, + source="linux-sysfs", + ) + + +def sys_platform_linux() -> bool: + # Kept as a tiny seam so platform selection is deterministic in tests. + import sys + + return sys.platform.startswith("linux") + + +def read_disk_activity_counters(path: str | os.PathLike[str]) -> DiskActivityCounters | None: + if os.name == "nt": + return _windows_disk_activity_counters(path) + if sys_platform_linux(): + return _linux_disk_activity_counters(path) + return None + + +class DiskActivitySampler: + """Convert cumulative OS counters into interval disk active-time percent.""" + + def __init__( + self, + counter_reader: Callable[[str | os.PathLike[str]], DiskActivityCounters | None] = read_disk_activity_counters, + ) -> None: + self._counter_reader = counter_reader + self._previous: dict[str, DiskActivityCounters] = {} + + def sample(self, path: str | os.PathLike[str]) -> tuple[float | None, str | None]: + try: + current = self._counter_reader(path) + except Exception: + return None, None + if current is None: + return None, None + + previous = self._previous.get(current.key) + self._previous[current.key] = current + if previous is None: + return None, current.source + + active_delta = current.active_ticks - previous.active_ticks + total_delta = current.total_ticks - previous.total_ticks + if active_delta < 0 or total_delta <= 0: + return None, current.source + percent = max(0.0, min(100.0, active_delta / total_delta * 100.0)) + return percent, current.source diff --git a/modiff/hardware.py b/modiff/hardware.py index e64b76d..3c609b7 100644 --- a/modiff/hardware.py +++ b/modiff/hardware.py @@ -21,11 +21,14 @@ from typing import Any, TypedDict -SCHEMA_VERSION = 1 +SCHEMA_VERSION = 2 SNAPSHOT_CACHE_TTL_SECONDS = 1.0 RELEVANT_ENV_VARS = ( "PYTORCH_CUDA_ALLOC_CONF", "CUDA_VISIBLE_DEVICES", + "ZE_AFFINITY_MASK", + "ONEAPI_DEVICE_SELECTOR", + "SYCL_DEVICE_FILTER", "PYTORCH_ENABLE_MPS_FALLBACK", "PYTORCH_MPS_HIGH_WATERMARK_RATIO", "HF_HOME", @@ -37,6 +40,8 @@ class SystemStats(TypedDict): os: str os_name: str + platform: str + architecture: str python_version: str python_executable: str pytorch_version: str | None @@ -53,6 +58,17 @@ class DeviceStats(TypedDict, total=False): index: int device: str name: str + vendor: str + backend: str + architecture: str | None + compute_capability: str | None + memory_kind: str + dedicated_memory_total: int | None + dedicated_memory_free: int | None + shared_memory_total: int | None + shared_memory_free: int | None + planning_memory_total: int | None + planning_memory_free: int | None vram_total: int | None vram_free: int | None torch_vram_total: int | None @@ -65,8 +81,12 @@ class DeviceStats(TypedDict, total=False): class TorchStats(TypedDict, total=False): available: bool version: str | None + cuda_version: str | None + hip_version: str | None cuda_available: bool cuda_device_count: int + xpu_available: bool + xpu_device_count: int mps_built: bool mps_available: bool cudnn_version: int | None @@ -251,11 +271,64 @@ def _cuda_memory_info(cuda: Any, index: int) -> tuple[int | None, int | None]: raise +def _read_int_file(path: Path) -> int | None: + try: + return _safe_int(path.read_text(encoding="utf-8").strip()) + except OSError: + return None + + +def _linux_amd_memory_regions() -> list[dict[str, int | None]]: + """Return AMD DRM local/shared memory regions in stable card order. + + ROCm exposes APUs through ``torch.cuda`` and may report the full GTT aperture + as device memory. The DRM driver separately exposes genuinely local VRAM + and borrowable system-memory GTT, which Auto needs in order to avoid treating + a high-capacity APU as an equivalently sized discrete GPU. + """ + + if not sys.platform.startswith("linux"): + return [] + output: list[dict[str, int | None]] = [] + for device_dir in sorted(Path("/sys/class/drm").glob("card*/device")): + try: + if (device_dir / "vendor").read_text(encoding="utf-8").strip().lower() != "0x1002": + continue + except OSError: + continue + output.append({ + "vram_total": _read_int_file(device_dir / "mem_info_vram_total"), + "vram_used": _read_int_file(device_dir / "mem_info_vram_used"), + "gtt_total": _read_int_file(device_dir / "mem_info_gtt_total"), + "gtt_used": _read_int_file(device_dir / "mem_info_gtt_used"), + }) + return output + + +def _device_property_text(properties: Any, *names: str) -> str | None: + for name in names: + value = getattr(properties, name, None) + if value not in (None, ""): + return str(value) + return None + + +def _shared_memory_device(name: str, total: int | None, ram_total: int | None) -> bool: + normalized = name.lower() + integrated_name = any( + marker in normalized + for marker in ("integrated", "uhd graphics", "iris", "core(tm) ultra", "radeon(tm) graphics") + ) + near_system_capacity = bool(total and ram_total and total >= int(ram_total * 0.7)) + return integrated_name or near_system_capacity + + def _probe_cuda( torch_module: Any, errors: dict[str, str], *, include_dynamic_memory: bool = True, + ram_total: int | None = None, ) -> tuple[bool, list[DeviceStats]]: cuda = getattr(torch_module, "cuda", None) if cuda is None: @@ -276,6 +349,10 @@ def _probe_cuda( return False, [] devices: list[DeviceStats] = [] + hip_version = getattr(getattr(torch_module, "version", None), "hip", None) + backend = "rocm" if hip_version else "cuda" + vendor = "amd" if hip_version else "nvidia" + amd_regions = _linux_amd_memory_regions() if hip_version else [] for index in range(count): device_errors: dict[str, str] = {} name = f"CUDA ({index})" @@ -289,6 +366,7 @@ def _probe_cuda( name = str(cuda.get_device_name(index)) except Exception as exc: device_errors["name"] = str(exc) + properties = None try: properties = cuda.get_device_properties(index) property_total = _safe_int(getattr(properties, "total_memory", None)) @@ -308,21 +386,168 @@ def _probe_cuda( except Exception as exc: device_errors["reserved"] = str(exc) - vram_total = property_total if property_total is not None else driver_total + runtime_total = property_total if property_total is not None else driver_total + region = amd_regions[index] if index < len(amd_regions) else {} + dedicated_total = _safe_int(region.get("vram_total")) if region else runtime_total + dedicated_used = _safe_int(region.get("vram_used")) if region else None + shared_total = _safe_int(region.get("gtt_total")) if region else None + shared_used = _safe_int(region.get("gtt_used")) if region else None + shared = bool( + hip_version + and ( + (dedicated_total and runtime_total and runtime_total >= dedicated_total * 2) + or _shared_memory_device(name, runtime_total, ram_total) + ) + ) + vram_total = dedicated_total if shared and dedicated_total else runtime_total + dedicated_free = ( + max(0, dedicated_total - dedicated_used) + if dedicated_total is not None and dedicated_used is not None + else (driver_free if not shared else None) + ) + shared_free = ( + max(0, shared_total - shared_used) + if shared_total is not None and shared_used is not None + else (driver_free if shared else None) + ) torch_free = None - if vram_total is not None and reserved is not None: - torch_free = max(0, vram_total - reserved) + if runtime_total is not None and reserved is not None: + torch_free = max(0, runtime_total - reserved) elif driver_free is not None: torch_free = driver_free + capability = None + capability_probe = getattr(cuda, "get_device_capability", None) + if callable(capability_probe): + try: + major, minor = capability_probe(index) + capability = f"{int(major)}.{int(minor)}" + except Exception as exc: + device_errors["capability"] = str(exc) + architecture = _device_property_text(properties, "gcnArchName", "architecture", "arch_name") + if architecture and ":" in architecture: + architecture = architecture.split(":", 1)[0] + device: DeviceStats = { "type": "cuda", "index": index, "device": f"cuda:{index}", "name": name, + "vendor": vendor, + "backend": backend, + "architecture": architecture, + "compute_capability": capability, + "memory_kind": "shared" if shared else "dedicated", + "dedicated_memory_total": dedicated_total, + "dedicated_memory_free": dedicated_free, + "shared_memory_total": shared_total, + "shared_memory_free": shared_free, + "planning_memory_total": vram_total, + "planning_memory_free": dedicated_free if shared else driver_free, "vram_total": vram_total, - "vram_free": driver_free, - "torch_vram_total": driver_total if driver_total is not None else vram_total, + "vram_free": dedicated_free if shared else driver_free, + "torch_vram_total": driver_total if driver_total is not None else runtime_total, + "torch_vram_free": torch_free, + "torch_allocated": allocated, + "torch_reserved": reserved, + } + if device_errors: + device["errors"] = device_errors + devices.append(device) + return bool(devices), devices + + +def _probe_xpu( + torch_module: Any, + errors: dict[str, str], + *, + include_dynamic_memory: bool = True, + ram_total: int | None = None, +) -> tuple[bool, list[DeviceStats]]: + xpu = getattr(torch_module, "xpu", None) + if xpu is None: + return False, [] + try: + available = bool(xpu.is_available()) + except Exception as exc: + errors["xpu_available"] = str(exc) + return False, [] + if not available: + return False, [] + try: + count = max(0, int(xpu.device_count())) + except Exception as exc: + errors["xpu_device_count"] = str(exc) + return False, [] + + devices: list[DeviceStats] = [] + for index in range(count): + device_errors: dict[str, str] = {} + name = f"Intel XPU ({index})" + total = free = allocated = reserved = None + try: + name = str(xpu.get_device_name(index)) + except Exception as exc: + device_errors["name"] = str(exc) + properties = None + try: + properties = xpu.get_device_properties(index) + total = _safe_int(getattr(properties, "total_memory", None)) + except Exception as exc: + device_errors["properties"] = str(exc) + if include_dynamic_memory: + memory_api = getattr(xpu, "memory", None) + mem_get_info = getattr(xpu, "mem_get_info", None) or getattr(memory_api, "mem_get_info", None) + if callable(mem_get_info): + try: + free, runtime_total = mem_get_info(index) + free = _safe_int(free) + total = total if total is not None else _safe_int(runtime_total) + except Exception as exc: + device_errors["memory"] = str(exc) + for label, method_name in (("allocated", "memory_allocated"), ("reserved", "memory_reserved")): + method = getattr(xpu, method_name, None) or getattr(memory_api, method_name, None) + if callable(method): + try: + value = _safe_int(method(index)) + if label == "allocated": + allocated = value + else: + reserved = value + except Exception as exc: + device_errors[label] = str(exc) + shared = _shared_memory_device(name, total, ram_total) + torch_free = max(0, total - reserved) if total is not None and reserved is not None else free + # Keep enough RAM for Python, model orchestration, and the OS. Intel's + # reported XPU aperture on an iGPU is accessible capacity, not a claim + # that the whole pool performs like dedicated VRAM. + shared_planning_limit = ram_total // 2 if ram_total is not None else None + planning_total = ( + min(total, shared_planning_limit) + if shared and total is not None and shared_planning_limit is not None + else total + ) + planning_free = min(free, planning_total) if free is not None and planning_total is not None else free + architecture = _device_property_text(properties, "architecture", "platform_name", "arch_name") + device: DeviceStats = { + "type": "xpu", + "index": index, + "device": f"xpu:{index}", + "name": name, + "vendor": "intel", + "backend": "xpu", + "architecture": architecture, + "compute_capability": None, + "memory_kind": "shared" if shared else "dedicated", + "dedicated_memory_total": None if shared else total, + "dedicated_memory_free": None if shared else free, + "shared_memory_total": planning_total if shared else None, + "shared_memory_free": free if shared else None, + "planning_memory_total": planning_total, + "planning_memory_free": planning_free, + "vram_total": planning_total, + "vram_free": planning_free, + "torch_vram_total": total, "torch_vram_free": torch_free, "torch_allocated": allocated, "torch_reserved": reserved, @@ -367,6 +592,7 @@ def _probe_torch( torch_module: Any = _AUTO_TORCH, *, include_dynamic_memory: bool = True, + ram_total: int | None = None, ) -> tuple[TorchStats, list[DeviceStats]]: if torch_module is _AUTO_TORCH: try: @@ -377,6 +603,8 @@ def _probe_torch( "version": None, "cuda_available": False, "cuda_device_count": 0, + "xpu_available": False, + "xpu_device_count": 0, "mps_built": False, "mps_available": False, "errors": {"import": str(exc)}, @@ -387,6 +615,8 @@ def _probe_torch( "version": None, "cuda_available": False, "cuda_device_count": 0, + "xpu_available": False, + "xpu_device_count": 0, "mps_built": False, "mps_available": False, "errors": {"import": "torch is unavailable"}, @@ -397,13 +627,24 @@ def _probe_torch( torch_module, errors, include_dynamic_memory=include_dynamic_memory, + ram_total=ram_total, + ) + xpu_available, xpu_devices = _probe_xpu( + torch_module, + errors, + include_dynamic_memory=include_dynamic_memory, + ram_total=ram_total, ) mps_built, mps_available = _probe_mps(torch_module, errors) torch_state: TorchStats = { "available": True, "version": str(getattr(torch_module, "__version__", "unknown")), + "cuda_version": getattr(getattr(torch_module, "version", None), "cuda", None), + "hip_version": getattr(getattr(torch_module, "version", None), "hip", None), "cuda_available": cuda_available, "cuda_device_count": len(cuda_devices), + "xpu_available": xpu_available, + "xpu_device_count": len(xpu_devices), "mps_built": mps_built, "mps_available": mps_available, } @@ -431,19 +672,49 @@ def _probe_torch( if errors: torch_state["errors"] = errors - devices = cuda_devices - if not cuda_devices and mps_available: + devices = cuda_devices or xpu_devices + if not devices and mps_available: + recommended_max = allocated = driver_allocated = None + runtime = getattr(torch_module, "mps", None) + if include_dynamic_memory and runtime is not None: + try: + recommended = getattr(runtime, "recommended_max_memory", None) + recommended_max = _safe_int(recommended()) if callable(recommended) else None + except Exception as exc: + errors["mps_recommended_memory"] = str(exc) + try: + allocated = _safe_int(runtime.current_allocated_memory()) + driver_allocated = _safe_int(runtime.driver_allocated_memory()) + except Exception as exc: + errors["mps_memory"] = str(exc) + planning_total = recommended_max or ram_total + planning_free = ( + max(0, planning_total - driver_allocated) + if planning_total is not None and driver_allocated is not None + else None + ) devices = [{ "type": "mps", "index": 0, "device": "mps:0", "name": "Apple Metal Performance Shaders", - "vram_total": None, - "vram_free": None, - "torch_vram_total": None, - "torch_vram_free": None, - "torch_allocated": None, - "torch_reserved": None, + "vendor": "apple", + "backend": "mps", + "architecture": platform.machine() or "arm64", + "compute_capability": None, + "memory_kind": "unified", + "dedicated_memory_total": None, + "dedicated_memory_free": None, + "shared_memory_total": ram_total, + "shared_memory_free": planning_free, + "planning_memory_total": planning_total, + "planning_memory_free": planning_free, + "vram_total": planning_total, + "vram_free": planning_free, + "torch_vram_total": recommended_max, + "torch_vram_free": planning_free, + "torch_allocated": allocated, + "torch_reserved": driver_allocated, }] return torch_state, devices @@ -458,6 +729,17 @@ def _cpu_device() -> DeviceStats: "index": 0, "device": "cpu:0", "name": processor, + "vendor": "cpu", + "backend": "cpu", + "architecture": platform.machine() or "unknown", + "compute_capability": None, + "memory_kind": "system", + "dedicated_memory_total": None, + "dedicated_memory_free": None, + "shared_memory_total": None, + "shared_memory_free": None, + "planning_memory_total": None, + "planning_memory_free": None, "vram_total": None, "vram_free": None, "torch_vram_total": None, @@ -474,13 +756,18 @@ def _build_hardware_snapshot( ) -> HardwareSnapshot: memory = system_memory_snapshot() try: - torch_state, accelerator_devices = _probe_torch(torch_module) + torch_state, accelerator_devices = _probe_torch( + torch_module, + ram_total=_safe_int(memory.get("total_bytes")), + ) except Exception as exc: torch_state = { "available": False, "version": None, "cuda_available": False, "cuda_device_count": 0, + "xpu_available": False, + "xpu_device_count": 0, "mps_built": False, "mps_available": False, "errors": {"probe": str(exc)}, @@ -496,6 +783,8 @@ def _build_hardware_snapshot( system: SystemStats = { "os": os_description, "os_name": os.name, + "platform": sys.platform, + "architecture": platform.machine() or "unknown", "python_version": sys.version, "python_executable": sys.executable, "pytorch_version": torch_state.get("version"), @@ -563,7 +852,7 @@ def legacy_device_list(snapshot: HardwareSnapshot) -> dict[str, dict[str, Any]]: """Map normalized devices to the long-standing ``utils.torch_utils`` shape.""" result: dict[str, dict[str, Any]] = {} - priority = {"cuda": 0, "mps": 1, "cpu": 2} + priority = {"cuda": 0, "xpu": 1, "mps": 2, "cpu": 3} devices = sorted( snapshot.get("devices", []), key=lambda item: (priority.get(str(item.get("type")), 99), _safe_int(item.get("index")) or 0), @@ -573,14 +862,19 @@ def legacy_device_list(snapshot: HardwareSnapshot) -> dict[str, dict[str, Any]]: index = _safe_int(device.get("index")) or 0 identifier = str(device.get("device") or f"{kind}:{index}") total = _safe_int(device.get("vram_total")) or 0 - if kind == "cuda": - name = f"{device.get('name') or 'CUDA'} {total / 1024**3:.2f}GB ({index})" + if kind in {"cuda", "xpu"}: + fallback_name = "CUDA" if kind == "cuda" else "Intel XPU" + name = f"{device.get('name') or fallback_name} {total / 1024**3:.2f}GB ({index})" elif kind == "mps": name = f"MPS ({index})" else: name = f"CPU ({index})" result[identifier] = { "arch": kind, + "backend": device.get("backend") or kind, + "vendor": device.get("vendor"), + "architecture": device.get("architecture"), + "memory_kind": device.get("memory_kind") or ("shared" if kind == "mps" else "dedicated"), "name": name, "label": [identifier], "total_memory": total, @@ -602,10 +896,13 @@ def legacy_torch_status(snapshot: HardwareSnapshot) -> dict[str, Any]: torch_state = snapshot.get("torch", {}) cuda_devices = [item for item in snapshot.get("devices", []) if item.get("type") == "cuda"] + xpu_devices = [item for item in snapshot.get("devices", []) if item.get("type") == "xpu"] mps_devices = [item for item in snapshot.get("devices", []) if item.get("type") == "mps"] status: dict[str, Any] = { "cuda_available": bool(torch_state.get("cuda_available")), "cuda_device_count": len(cuda_devices), + "xpu_available": bool(torch_state.get("xpu_available")), + "xpu_device_count": len(xpu_devices), "mps_built": bool(torch_state.get("mps_built")), "mps_available": bool(torch_state.get("mps_available")), "mps_device_count": len(mps_devices), @@ -616,9 +913,17 @@ def legacy_torch_status(snapshot: HardwareSnapshot) -> dict[str, Any]: legacy_device = { "index": device.get("index"), "name": device.get("name"), + "vendor": device.get("vendor"), + "backend": device.get("backend"), + "architecture": device.get("architecture"), + "compute_capability": device.get("compute_capability"), + "memory_kind": device.get("memory_kind"), + "dedicated_memory_total": device.get("dedicated_memory_total"), + "shared_memory_total": device.get("shared_memory_total"), + "accessible_memory_total": device.get("torch_vram_total"), "total_memory": device.get("vram_total"), "memory_free_bytes": device.get("vram_free"), - "memory_total_bytes": device.get("torch_vram_total"), + "memory_total_bytes": device.get("vram_total"), } if device.get("errors"): legacy_device["probe_errors"] = device["errors"] @@ -640,17 +945,37 @@ def legacy_torch_status(snapshot: HardwareSnapshot) -> dict[str, Any]: status["mps_devices"] = [{ "index": item.get("index"), "name": item.get("name"), + "vendor": item.get("vendor"), + "backend": item.get("backend"), + "architecture": item.get("architecture"), + "memory_kind": item.get("memory_kind"), "total_memory": item.get("vram_total") or 0, + "memory_free_bytes": item.get("vram_free"), } for item in mps_devices] + if xpu_devices: + status["xpu_devices"] = [{ + "index": item.get("index"), + "name": item.get("name"), + "vendor": item.get("vendor"), + "backend": item.get("backend"), + "architecture": item.get("architecture"), + "memory_kind": item.get("memory_kind"), + "total_memory": item.get("vram_total") or 0, + "memory_free_bytes": item.get("vram_free"), + "accessible_memory_total": item.get("torch_vram_total"), + } for item in xpu_devices] errors = torch_state.get("errors") if isinstance(errors, dict): cuda_errors = [f"{key}: {value}" for key, value in errors.items() if key.startswith("cuda")] mps_errors = [f"{key}: {value}" for key, value in errors.items() if key.startswith("mps")] + xpu_errors = [f"{key}: {value}" for key, value in errors.items() if key.startswith("xpu")] if cuda_errors: status["cuda_error"] = "; ".join(cuda_errors) if mps_errors: status["mps_error"] = "; ".join(mps_errors) + if xpu_errors: + status["xpu_error"] = "; ".join(xpu_errors) return status @@ -673,12 +998,22 @@ def format_hardware_summary(snapshot: HardwareSnapshot) -> str: ) else: cuda_summary = "unavailable" + xpu_devices = [item for item in snapshot.get("devices", []) if item.get("type") == "xpu"] + xpu_summary = ( + ", ".join( + f"{item.get('device')} {item.get('name')} " + f"({_format_bytes(item.get('vram_total'))} total, {_format_bytes(item.get('vram_free'))} free)" + for item in xpu_devices + ) + if xpu_devices + else "unavailable" + ) mps_summary = "available" if torch_state.get("mps_available") else "unavailable" if torch_state.get("mps_built") and not torch_state.get("mps_available"): mps_summary = "built, unavailable" allocator = system.get("pytorch_cuda_alloc_conf") or "unset" return ( f"Hardware: PyTorch {system.get('pytorch_version') or 'unavailable'}; allocator {allocator}; " - f"CUDA {cuda_summary}; MPS {mps_summary}; RAM {_format_bytes(system.get('ram_total'))} total, " + f"CUDA {cuda_summary}; XPU {xpu_summary}; MPS {mps_summary}; RAM {_format_bytes(system.get('ram_total'))} total, " f"{_format_bytes(system.get('ram_available'))} available; default {snapshot.get('default_device', 'cpu:0')}" ) diff --git a/modiff/install.py b/modiff/install.py index 9f6d8e7..e789408 100644 --- a/modiff/install.py +++ b/modiff/install.py @@ -12,6 +12,7 @@ import subprocess import sys import tarfile +import tempfile import time import urllib.request import zipfile @@ -23,7 +24,14 @@ except ImportError: # Windows does not provide the POSIX group database. grp = None -from modiff.runtime_profile import load_manifest, lock_digest, normalized_arch, normalized_os +from modiff.runtime_profile import ( + RUNTIME_CONTRACT_SCHEMA, + load_manifest, + lock_digest, + normalized_arch, + normalized_os, + runtime_contract_paths, +) from modiff.setup_catalog import CATALOG, PHASES, enrich_issue ROOT = Path(__file__).resolve().parents[1] @@ -34,6 +42,7 @@ MANAGED_ROOT = ROOT / ".modiff" JOURNAL_PATH = MANAGED_ROOT / "install-state.json" DIAGNOSTICS_DIR = MANAGED_ROOT / "diagnostics" +WEB_ROOT = ROOT / "web" TOOL_ARCHIVES = { ("linux", "x86_64", "uv"): ("https://github.com/astral-sh/uv/releases/download/0.11.26/uv-x86_64-unknown-linux-gnu.tar.gz", "6426a73c3837e6e2483ee344cbc00f36394d179afcba6183cb77437e67db4af0"), @@ -61,9 +70,13 @@ def _write_journal(**updates: Any) -> dict[str, Any]: MANAGED_ROOT.mkdir(exist_ok=True) journal = _read_journal() journal.update(updates) + if journal.get("status") in {"running", "complete"}: + journal.pop("failure", None) journal.setdefault("schema_version", 1) journal["updated_at"] = _now() - JOURNAL_PATH.write_text(json.dumps(journal, indent=2) + "\n", encoding="utf-8") + temporary = JOURNAL_PATH.with_suffix(JOURNAL_PATH.suffix + ".tmp") + temporary.write_text(json.dumps(journal, indent=2) + "\n", encoding="utf-8") + temporary.replace(JOURNAL_PATH) return journal @@ -131,6 +144,16 @@ def _rocm_environment() -> dict[str, str]: return environment +def _drm_vendor_ids() -> set[str]: + vendors = set() + for path in Path("/sys/class/drm").glob("card*/device/vendor"): + try: + vendors.add(path.read_text(encoding="utf-8").strip().lower()) + except OSError: + continue + return vendors + + def detect_host() -> dict[str, Any]: """Detect candidates without importing Torch and separate presence from usability.""" os_name = normalized_os() @@ -141,7 +164,31 @@ def detect_host() -> dict[str, Any]: nvidia_usable = bool(nvidia_result and nvidia_result["returncode"] == 0 and "GPU" in nvidia_result["stdout"]) lspci = _command(["lspci", "-nn"]) if shutil.which("lspci") else {"stdout": ""} - amd_candidate = "1002:" in lspci["stdout"].lower() or "advanced micro devices" in lspci["stdout"].lower() + display_text = lspci["stdout"].lower() + if os_name == "windows": + powershell = shutil.which("powershell") or shutil.which("pwsh") + if powershell: + display_probe = _command([ + powershell, + "-NoProfile", + "-Command", + "Get-CimInstance Win32_VideoController | Select-Object -ExpandProperty Name", + ]) + display_text = f"{display_text}\n{display_probe['stdout'].lower()}" + drm_vendors = _drm_vendor_ids() + amd_candidate = bool( + "0x1002" in drm_vendors + or "1002:" in display_text + or "advanced micro devices" in display_text + or "amd radeon" in display_text + ) + intel_candidate = bool( + "0x8086" in drm_vendors + or ( + ("8086:" in display_text or "intel" in display_text) + and any(marker in display_text for marker in ("vga", "display", "graphics", "arc", "video")) + ) + ) rocminfo = _command(["rocminfo"], timeout=15) if shutil.which("rocminfo") else None rocm_text = f"{rocminfo['stdout']}\n{rocminfo['stderr']}" if rocminfo else "" architectures = sorted(set(re.findall(r"\bgfx\d+[a-z0-9]*\b", rocm_text.lower()))) @@ -160,7 +207,12 @@ def detect_host() -> dict[str, Any]: and "render" in groups ) apple = os_name == "macos" and normalized_arch() == "arm64" - candidates = (["nvidia"] if nvidia_usable else []) + (["amd"] if amd_candidate else []) + (["mps"] if apple else []) + candidates = ( + (["nvidia"] if nvidia_usable else []) + + (["amd"] if amd_candidate else []) + + (["intel"] if intel_candidate else []) + + (["mps"] if apple else []) + ) return { "os": os_name, "os_id": os_release.get("ID"), @@ -174,6 +226,7 @@ def detect_host() -> dict[str, Any]: "amd_candidate": amd_candidate, "amd_usable": amd_usable, "amd_architectures": architectures, + "intel_xpu_candidate": intel_candidate, "kfd_present": kfd.exists(), "kfd_accessible": kfd.exists() and os.access(kfd, os.R_OK | os.W_OK), "render_nodes": render_nodes, @@ -185,7 +238,7 @@ def detect_host() -> dict[str, Any]: def resolve_profile(accelerator: str, host: dict[str, Any], *, allow_experimental: bool = False, non_interactive: bool = False) -> str: - aliases = {"nvidia": "nvidia-cuda", "mps": "apple-mps"} + aliases = {"nvidia": "nvidia-cuda", "intel": "intel-xpu", "mps": "apple-mps"} if accelerator not in {"auto", "amd", "cpu", *aliases}: raise ValueError(f"Unknown accelerator: {accelerator}") if accelerator != "auto": @@ -204,6 +257,8 @@ def resolve_profile(accelerator: str, host: dict[str, Any], *, allow_experimenta if experimental and non_interactive and not allow_experimental: return "cpu" return "amd-pytorch-windows" if host["os"] == "windows" else "amd-rocm-linux" + if host.get("intel_xpu_candidate"): + return "intel-xpu" if host.get("mps_candidate"): return "apple-mps" return "cpu" @@ -285,16 +340,33 @@ def build_plan(args: argparse.Namespace) -> dict[str, Any]: issues.extend(amd_issues) if tier == "experimental" and not args.allow_experimental: issues.append(_issue("experimental-opt-in-required", "Ubuntu 26.04 AMD setup requires --allow-experimental.")) + elif profile == "amd-pytorch-windows": + tier = "conditional" + issues.append(_issue( + "amd-windows-install-review-required", + "AMD now publishes PyTorch for selected Windows 11 Radeon and Ryzen devices, but MoDiff has not pinned the complete Windows SDK wheel set yet. Use the official AMD installation guide, then run repair/validation before model execution.", + blocking=True, + )) requirement = ROOT / spec["requirements"] if not requirement.is_file(): issues.append(_issue("profile-lock-missing", f"Profile requirements are missing: {requirement}")) + if host["os"] == "windows": + cpu_fallback_command = r".\install.ps1 -Accelerator cpu" + resume_command = r".\install.ps1 -Accelerator auto -Resume" + if tier == "experimental": + resume_command += " -AllowExperimental" + else: + cpu_fallback_command = "./install.sh --accelerator cpu" + resume_command = "./install.sh --accelerator auto --resume" + if tier == "experimental": + resume_command += " --allow-experimental" steps = [ {"id": "detect", "title": "Detect hardware and operating system", "phase": "detect", "status": "complete", "automatic": True}, {"id": "resolve-profile", "title": f"Select {profile}", "phase": "plan", "status": "complete", "automatic": True}, *[{**issue, "phase": "system-preparation"} for issue in issues], {"id": "toolchain", "title": "Prepare required app-local toolchains", "phase": "toolchain", "status": "pending", "automatic": True}, {"id": "backend", "title": "Install the staged backend environment", "phase": "backend", "status": "pending", "automatic": True}, - {"id": "client", "title": "Install and build the sibling client", "phase": "client", "status": "skipped" if getattr(args, "backend_only", False) else "pending", "automatic": True}, + {"id": "client", "title": "Install the client and verified local Gallery", "phase": "client", "status": "skipped" if getattr(args, "backend_only", False) else "pending", "automatic": True}, {"id": "validation", "title": "Verify packages and execute a device tensor", "phase": "validation", "status": "pending", "automatic": True}, {"id": "complete", "title": "Finish setup", "phase": "complete", "status": "pending", "automatic": True}, ] @@ -309,8 +381,8 @@ def build_plan(args: argparse.Namespace) -> dict[str, Any]: "steps": steps, "phases": PHASES, "downloads": {"accelerator_bytes_approx": 2_000_000_000 if profile == "amd-rocm-linux" else None}, - "cpu_fallback_command": "./install.sh --accelerator cpu", - "resume_command": "./install.sh --resume" + (" --allow-experimental" if tier == "experimental" else ""), + "cpu_fallback_command": cpu_fallback_command, + "resume_command": resume_command, "execution_ready": not any(issue["blocking"] for issue in issues), } @@ -364,7 +436,7 @@ def _render_plan(plan: dict[str, Any]) -> None: print("Download: approximately 2 GB for the accelerator runtime") print("\nSetup checklist:") for index, step in enumerate(plan["steps"], 1): - marker = {"complete": "✓", "blocked": "!", "warning": "!", "skipped": "-"}.get(step.get("status"), "·") + marker = {"complete": "x", "blocked": "!", "warning": "!", "skipped": "-"}.get(step.get("status"), " ") print(f" {index}. [{marker}] {step['title']}") if step.get("status") in {"blocked", "warning"}: print(f" {step.get('explanation') or step.get('message')}") @@ -392,11 +464,29 @@ def _ensure_venv(uv: str, target: Path) -> Path: return python +def _archive_member_destination(destination: Path, member_name: str) -> Path: + """Resolve an archive member while rejecting absolute and escaping paths.""" + + normalized = member_name.replace("\\", "/") + if normalized.startswith("/") or re.match(r"^[A-Za-z]:", normalized): + raise RuntimeError(f"Unsafe absolute path in tool archive: {member_name}") + member_path = Path(normalized) + if any(part == ".." for part in member_path.parts): + raise RuntimeError(f"Unsafe parent path in tool archive: {member_name}") + root = destination.resolve() + target = (root / member_path).resolve() + if target != root and root not in target.parents: + raise RuntimeError(f"Tool archive path escapes its destination: {member_name}") + return target + + def _download_tool(name: str) -> Path: key = (normalized_os(), normalized_arch(), name) if key not in TOOL_ARCHIVES: raise RuntimeError(f"No app-local {name} archive is pinned for {key[0]}/{key[1]}") url, expected_hash = TOOL_ARCHIVES[key] + if not url.startswith("https://"): + raise RuntimeError(f"Pinned {name} archive must use HTTPS") downloads = MANAGED_ROOT / "downloads" tools = MANAGED_ROOT / "tools" downloads.mkdir(parents=True, exist_ok=True) @@ -419,12 +509,13 @@ def _download_tool(name: str) -> Path: destination.mkdir(parents=True) if zipfile.is_zipfile(archive): with zipfile.ZipFile(archive) as bundle: - bundle.extractall(destination) + for member in bundle.infolist(): + _archive_member_destination(destination, member.filename) + bundle.extract(member, destination) else: with tarfile.open(archive) as bundle: for member in bundle.getmembers(): - if member.name.startswith("/") or ".." in Path(member.name).parts: - raise RuntimeError(f"Unsafe path in {name} archive") + _archive_member_destination(destination, member.name) bundle.extractall(destination, filter="data") return destination @@ -496,45 +587,251 @@ def _client_path() -> Path | None: return next((path for path in candidates if (path / "package-lock.json").is_file()), None) -def _install_client(*, backend_only: bool) -> dict[str, Any]: +def _npm_command(toolchains: dict[str, str], *arguments: str) -> list[str]: + """Run npm without asking ``subprocess`` to execute a Windows batch file.""" + + npm = Path(toolchains["npm"]) + if npm.suffix.lower() != ".cmd": + return [str(npm), *arguments] + npm_cli = npm.parent / "node_modules" / "npm" / "bin" / "npm-cli.js" + if not npm_cli.is_file(): + raise RuntimeError(f"The managed Node archive is missing npm-cli.js at {npm_cli}") + return [toolchains["node"], str(npm_cli), *arguments] + + +def _mirror_client_dist(client: Path, web_root: Path | None = None) -> Path: + """Atomically replace the bundled UI while retaining local user assets.""" + + source = (Path(client) / "dist").resolve() + destination = (web_root or WEB_ROOT).resolve() + if not (source / "index.html").is_file(): + raise RuntimeError(f"Client build did not produce {source / 'index.html'}") + + staging = destination.with_name(f".{destination.name}.next") + previous = destination.with_name(f".{destination.name}.previous") + for temporary in (staging, previous): + if temporary.exists(): + shutil.rmtree(temporary) + shutil.copytree(source, staging) + user_assets = destination / "user" + if user_assets.is_dir(): + shutil.copytree(user_assets, staging / "user", dirs_exist_ok=True) + + if destination.exists(): + destination.rename(previous) + try: + staging.rename(destination) + except Exception: + if previous.exists() and not destination.exists(): + previous.rename(destination) + raise + if previous.exists(): + shutil.rmtree(previous) + return destination + + +def _template_asset_source(client: Path) -> dict[str, Any]: + source_path = client / "src" / "studio" / "templateAssetSource.json" + try: + source = json.loads(source_path.read_text(encoding="utf-8")) + except (OSError, ValueError) as exc: + raise RuntimeError(f"Could not read the client Template Gallery source: {source_path}") from exc + if not isinstance(source, dict) or source.get("mode") not in {"local", "huggingface"}: + raise RuntimeError(f"Invalid client Template Gallery source: {source_path}") + return source + + +def _run_client_step( + command: list[str], *, client: Path, environment: dict[str, str], log_name: str +) -> None: + result = subprocess.run( + command, + cwd=client, + env=environment, + capture_output=True, + text=True, + check=False, + ) + (DIAGNOSTICS_DIR / log_name).write_text( + result.stdout + result.stderr, encoding="utf-8" + ) + if result.returncode != 0: + raise RuntimeError( + f"Client setup failed during {' '.join(command[1:])}; see {DIAGNOSTICS_DIR / log_name}" + ) + + +def _build_client_with_installed_gallery( + client: Path, + *, + python: Path, + toolchains: dict[str, str], + environment: dict[str, str], +) -> dict[str, Any]: + """Build a local Gallery bundle, downloading the immutable set if needed.""" + + source = _template_asset_source(client) + asset_script = client / "scripts" / "template-gallery-assets.py" + if not asset_script.is_file(): + raise RuntimeError(f"Client Template Gallery installer is missing: {asset_script}") + + public_root = client / "public" + gallery_root = public_root / "template-gallery" + build_environment = environment.copy() + build_environment["VITE_MODIFF_TEMPLATE_ASSET_MODE"] = "local" + + if source["mode"] == "local": + print("Verifying local Template Gallery assets...", file=sys.stderr) + _run_client_step( + [str(python), str(asset_script), "verify"], + client=client, + environment=environment, + log_name="template-gallery-verify.log", + ) + print("Building the bundled MoDiff client...", file=sys.stderr) + _run_client_step( + _npm_command(toolchains, "run", "build"), + client=client, + environment=build_environment, + log_name="npm-build.log", + ) + return {"source": "local", "asset_mode": "local"} + + MANAGED_ROOT.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory( + prefix="modiff-template-gallery-", dir=MANAGED_ROOT + ) as temporary: + download_root = Path(temporary) / "download" + print("Downloading and verifying the Template Gallery assets...", file=sys.stderr) + _run_client_step( + [ + str(python), + str(asset_script), + "download", + "--destination", + str(download_root), + ], + client=client, + environment=environment, + log_name="template-gallery-download.log", + ) + downloaded_gallery = download_root / "template-gallery" + if not downloaded_gallery.is_dir(): + raise RuntimeError("The verified Template Gallery download is missing its asset directory") + + # Keep the authoring checkout outside Vite's publicDir while building; + # anything beneath public/ is copied verbatim into dist, including dot + # directories and permission-dependent local preview files. + backup = client / ".template-gallery.install-backup" + if backup.exists(): + raise RuntimeError(f"A stale Template Gallery install backup exists: {backup}") + public_root.mkdir(parents=True, exist_ok=True) + had_original = gallery_root.exists() + if had_original: + gallery_root.rename(backup) + try: + downloaded_gallery.rename(gallery_root) + print("Building the bundled MoDiff client with local Gallery assets...", file=sys.stderr) + _run_client_step( + _npm_command(toolchains, "run", "build"), + client=client, + environment=build_environment, + log_name="npm-build.log", + ) + finally: + if gallery_root.exists(): + shutil.rmtree(gallery_root) + if had_original and backup.exists(): + backup.rename(gallery_root) + return { + "source": "huggingface", + "asset_mode": "local", + "repo_id": source.get("repoId"), + "revision": source.get("revision"), + "asset_set_id": source.get("assetSetId"), + } + + +def _install_client(*, backend_only: bool, python: Path | None = None) -> dict[str, Any]: if backend_only: return {"status": "skipped", "reason": "--backend-only"} client = _client_path() if not client: - return {"status": "skipped", "reason": "sibling client not found"} + raise RuntimeError( + f"Sibling MoDiff-client checkout not found. Clone it as {ROOT.parent / 'MoDiff-client'} " + "or rerun with --backend-only to install only the backend." + ) toolchains = _ensure_node() + managed_python = python or VENV / ("Scripts/python.exe" if os.name == "nt" else "bin/python") + if not managed_python.is_file(): + raise RuntimeError(f"The managed Python environment is missing: {managed_python}") DIAGNOSTICS_DIR.mkdir(parents=True, exist_ok=True) environment = os.environ.copy() environment["PATH"] = str(Path(toolchains["node"]).parent) + os.pathsep + environment.get("PATH", "") - for command, log_name in (([toolchains["npm"], "ci"], "npm-ci.log"), ([toolchains["npm"], "run", "build"], "npm-build.log")): - result = subprocess.run(command, cwd=client, env=environment, capture_output=True, text=True, check=False) - (DIAGNOSTICS_DIR / log_name).write_text(result.stdout + result.stderr, encoding="utf-8") - if result.returncode != 0: - raise RuntimeError(f"Client setup failed during {' '.join(command[1:])}; see {DIAGNOSTICS_DIR / log_name}") - return {"status": "complete", "path": str(client), "node": toolchains["node_version"]} + print("Installing locked client dependencies...", file=sys.stderr) + _run_client_step( + _npm_command(toolchains, "ci"), + client=client, + environment=environment, + log_name="npm-ci.log", + ) + gallery = _build_client_with_installed_gallery( + client, + python=managed_python, + toolchains=toolchains, + environment=environment, + ) + mirrored = _mirror_client_dist(client) + return { + "status": "complete", + "path": str(client), + "node": toolchains["node_version"], + "web": str(mirrored), + "template_gallery": gallery, + } def _smoke_script(profile: str) -> str: - expected = {"amd-rocm-linux": "rocm", "nvidia-cuda": "cuda", "apple-mps": "mps", "cpu": "cpu"}.get(profile, "cpu") + expected = {"amd-rocm-linux": "rocm", "amd-pytorch-windows": "rocm", "nvidia-cuda": "cuda", "intel-xpu": "xpu", "apple-mps": "mps", "cpu": "cpu"}.get(profile, "cpu") return f""" import json, torch -backend = 'rocm' if torch.version.hip else ('cuda' if torch.version.cuda else ('mps' if torch.backends.mps.is_built() else 'cpu')) +backend = 'rocm' if torch.version.hip else ('cuda' if torch.version.cuda else ('xpu' if hasattr(torch, 'xpu') and torch.xpu.is_available() else ('mps' if torch.backends.mps.is_built() else 'cpu'))) assert backend == {expected!r}, (backend, {expected!r}) -device = 'cuda:0' if backend in ('cuda', 'rocm') else ('mps:0' if backend == 'mps' else 'cpu:0') -assert device == 'cpu:0' or (torch.cuda.is_available() if device.startswith('cuda') else torch.backends.mps.is_available()) -dtype = torch.float16 if backend in ('cuda', 'rocm', 'mps') else torch.float32 +device = 'cuda:0' if backend in ('cuda', 'rocm') else ('xpu:0' if backend == 'xpu' else ('mps:0' if backend == 'mps' else 'cpu:0')) +assert device == 'cpu:0' or (torch.cuda.is_available() if device.startswith('cuda') else (torch.xpu.is_available() if device.startswith('xpu') else torch.backends.mps.is_available())) +dtype = torch.float16 if backend in ('cuda', 'rocm', 'xpu', 'mps') else torch.float32 x = torch.tensor([1.0, 2.0], device=device, dtype=dtype) y = x * 2 + 1 assert y.cpu().float().tolist() == [3.0, 5.0] if backend in ('cuda', 'rocm'): torch.cuda.synchronize() +elif backend == 'xpu': torch.xpu.synchronize() elif backend == 'mps': torch.mps.synchronize() del x, y if backend in ('cuda', 'rocm'): torch.cuda.empty_cache() +elif backend == 'xpu': torch.xpu.empty_cache() elif backend == 'mps': torch.mps.empty_cache() print(json.dumps({{'backend': backend, 'device': device, 'torch': torch.__version__, 'hip': torch.version.hip, 'cuda': torch.version.cuda}})) """ +def _profile_package_script(profile: str) -> str: + """Return an isolated validation script for profile package policy.""" + specification = load_manifest()["profiles"][profile] + required = specification.get("required", []) + prohibited = specification.get("prohibited", []) + return f""" +import importlib.metadata, json +normalize = lambda value: value.lower().replace('_', '-').replace('.', '-') +installed = {{normalize(item.metadata['Name']) for item in importlib.metadata.distributions() if item.metadata['Name']}} +required = {{normalize(item) for item in {required!r}}} +prohibited = {{normalize(item) for item in {prohibited!r}}} +missing = sorted(required - installed) +unexpected = sorted(prohibited & installed) +assert not missing and not unexpected, json.dumps({{'missing': missing, 'prohibited_installed': unexpected}}) +print(json.dumps({{'required_present': sorted(required), 'prohibited_absent': sorted(prohibited)}})) +""" + + def install(args: argparse.Namespace) -> dict[str, Any]: prior_journal = _read_journal() if prior_journal.get("consent", {}).get("experimental-platform") is True: @@ -613,8 +910,15 @@ def install(args: argparse.Namespace) -> dict[str, Any]: _write_journal(status="failed", current_phase="validation", failure=smoke["stderr"].strip() or smoke["stdout"].strip(), rollback={"performed": False, "reason": "staged environment was never promoted"}, next_action=plan["resume_command"]) raise RuntimeError(f"Device tensor smoke failed: {smoke['stderr'].strip() or smoke['stdout'].strip()}") + package_policy = _command([str(python), "-c", _profile_package_script(plan["profile"])], timeout=60) + if package_policy["returncode"] != 0: + detail = package_policy["stderr"].strip() or package_policy["stdout"].strip() + _write_journal(status="failed", current_phase="validation", failure=detail, + rollback={"performed": False, "reason": "staged environment was never promoted"}, next_action=plan["resume_command"]) + raise RuntimeError(f"Profile package policy failed: {detail}") _run([uv, "pip", "check", "--python", str(python)]) requirement = Path(plan["requirements"]) + runtime_contract = runtime_contract_paths(requirement) smoke_result = json.loads(smoke["stdout"].strip().splitlines()[-1]) state = { "schema_version": 1, @@ -622,7 +926,16 @@ def install(args: argparse.Namespace) -> dict[str, Any]: "support_tier": plan["support_tier"], "manifest_revision": plan["manifest_revision"], "requirements": requirement.name, - "lock_digest": lock_digest(requirement), + "runtime_contract_schema": RUNTIME_CONTRACT_SCHEMA, + "lock_digest": lock_digest( + requirement, + contract_paths=runtime_contract, + profile=plan["profile"], + ), + "runtime_contract_files": [ + str(path.resolve().relative_to(ROOT.resolve())) + for path in runtime_contract + ] + [f"modiff/compatibility/accelerators.v1.json#profiles/{plan['profile']}"], "host": {key: plan["host"].get(key) for key in ("os", "os_version", "architecture", "kernel", "amd_architectures")}, "smoke": smoke_result, } @@ -639,11 +952,12 @@ def install(args: argparse.Namespace) -> dict[str, Any]: _record_phase("backend", detail={"promoted": True, "previous_environment": PREVIOUS_VENV.exists()}) _record_phase("client", status="running") - client_result = _install_client(backend_only=args.backend_only) + client_result = _install_client(backend_only=args.backend_only, python=promoted_python) _record_phase("client", status=client_result["status"], detail=client_result) _record_phase("complete") + launch_command = ".\\run.ps1" if os.name == "nt" else "./run.sh" _write_journal(status="complete", current_phase="complete", completed_phases=PHASES, - rollback={"performed": rolled_back}, next_action="./run.sh", application_url="http://127.0.0.1:8088") + rollback={"performed": rolled_back}, next_action=launch_command, application_url="http://127.0.0.1:8088") plan["state"] = state plan["client"] = client_result plan["status"] = "complete" @@ -654,7 +968,7 @@ def install(args: argparse.Namespace) -> dict[str, Any]: def parser() -> argparse.ArgumentParser: result = argparse.ArgumentParser(description=__doc__) - result.add_argument("--accelerator", default="auto", choices=["auto", "nvidia", "amd", "mps", "cpu"]) + result.add_argument("--accelerator", default="auto", choices=["auto", "nvidia", "amd", "intel", "mps", "cpu"]) result.add_argument("--dry-run", action="store_true") result.add_argument("--non-interactive", action="store_true") result.add_argument("--repair", action="store_true") @@ -696,9 +1010,9 @@ def main(argv: list[str] | None = None) -> int: if args.json: print(json.dumps(result, indent=2)) elif result.get("status") == "complete": - print("\n✓ MoDiff installation complete") + print("\nMoDiff installation complete") print(f" Profile: {result['profile']} ({result['support_tier']})") - print(f" Start: ./run.sh") + print(f" Start: {'.\\run.ps1' if os.name == 'nt' else './run.sh'}") print(f" Open: {result['application_url']}") return 0 if result.get("status") in {"complete", "reboot-required"} else 2 except Exception as exc: diff --git a/modiff/media_assets.py b/modiff/media_assets.py new file mode 100644 index 0000000..6e826ec --- /dev/null +++ b/modiff/media_assets.py @@ -0,0 +1,269 @@ +"""Small file-backed media manifest for retained, user-cleanable intermediates.""" + +from __future__ import annotations + +import json +import os +import subprocess +import threading +import time +import uuid +from pathlib import Path +from typing import Any + +from modiff.config import CONFIG +from modiff.path_identifiers import resolve_runtime_input_path + + +_LOCK = threading.RLock() + + +def asset_root(root: str | os.PathLike[str] | None = None) -> Path: + return Path(root or (Path(CONFIG.paths["temp"]) / "media_assets")).resolve() + + +def _manifest_path(root: Path) -> Path: + return root / "manifest.json" + + +def _read(root: Path) -> dict[str, Any]: + path = _manifest_path(root) + if not path.is_file(): + return {"schema_version": 1, "assets": {}} + try: + value = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return {"schema_version": 1, "assets": {}} + return { + "schema_version": 1, + "assets": value.get("assets") if isinstance(value.get("assets"), dict) else {}, + } + + +def _write(root: Path, manifest: dict[str, Any]) -> None: + root.mkdir(parents=True, exist_ok=True) + path = _manifest_path(root) + temporary = path.with_suffix(".tmp") + temporary.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n", encoding="utf-8") + temporary.replace(path) + + +def allocate_video_path(*, task_id: str | None = None, suffix: str = ".mp4", root=None) -> tuple[str, Path]: + root_path = asset_root(root) + asset_id = uuid.uuid4().hex + task_part = str(task_id or "session").replace("/", "_").replace("\\", "_") + destination = root_path / task_part / f"{asset_id}{suffix}" + destination.parent.mkdir(parents=True, exist_ok=True) + return asset_id, destination + + +def register_video_asset( + path: str | os.PathLike[str], + *, + asset_id: str | None = None, + task_id: str | None = None, + width: int, + height: int, + fps: float, + frame_count: int, + temporary: bool = True, + pinned: bool = False, + source_asset_ids: list[str] | None = None, + operation: str | None = None, + root=None, +) -> dict[str, Any]: + root_path = asset_root(root) + resolved = Path(path).resolve() + if not resolved.is_file(): + raise FileNotFoundError(f"Media asset file does not exist: {resolved}") + now = time.time() + record = { + "schema_version": 1, + "asset_id": str(asset_id or uuid.uuid4().hex), + "storage": "file", + "path": str(resolved), + "media_type": "video", + "width": int(width), + "height": int(height), + "fps": float(fps), + "frame_count": int(frame_count), + "duration_seconds": float(frame_count / fps) if fps else 0.0, + "task_id": str(task_id) if task_id else None, + "temporary": bool(temporary), + "pinned": bool(pinned), + "created_at": now, + "updated_at": now, + } + if source_asset_ids: + record["source_asset_ids"] = [str(value) for value in source_asset_ids] + if operation: + record["operation"] = str(operation) + with _LOCK: + manifest = _read(root_path) + manifest["assets"][record["asset_id"]] = record + _write(root_path, manifest) + return record + + +def current_task_id() -> str | None: + """Return the active task identity without making media helpers server-dependent.""" + try: + from modiff.server import server + + value = (server.current_task or {}).get("task_id") + return str(value) if value else None + except Exception: + return None + + +def coerce_video_asset(value: Any) -> dict[str, Any]: + """Normalize a retained asset record or file path to file-backed video metadata.""" + if isinstance(value, dict): + path_value = value.get("path") or value.get("file") + if not path_value: + raise ValueError("Video asset records must contain a path or file value.") + path = resolve_runtime_input_path(str(path_value)).resolve() + if not path.is_file(): + raise FileNotFoundError(f"Video asset file does not exist: {path}") + record = dict(value) + record.update({"storage": "file", "path": str(path), "media_type": "video"}) + required = ("width", "height", "fps", "frame_count", "duration_seconds") + if any(record.get(key) is None for key in required): + record.update(probe_video_file(path)) + return record + if isinstance(value, (str, os.PathLike)) and str(value): + path = resolve_runtime_input_path(value).resolve() + if not path.is_file(): + raise FileNotFoundError(f"Video file does not exist: {path}") + return { + "schema_version": 1, + "asset_id": None, + "storage": "file", + "path": str(path), + "media_type": "video", + **probe_video_file(path), + } + raise TypeError("Expected a retained video asset record or an existing video file path.") + + +def probe_video_file(path: str | os.PathLike[str]) -> dict[str, Any]: + """Read video dimensions and timing without materializing the frame sequence.""" + import imageio.v2 as imageio + + resolved = Path(path).expanduser().resolve() + reader = imageio.get_reader(str(resolved), "ffmpeg") + try: + metadata = reader.get_meta_data() + width, height = metadata.get("size", (0, 0)) + fps = float(metadata.get("fps") or 0) + duration = float(metadata.get("duration") or 0) + try: + frame_count = int(reader.count_frames()) + except Exception: + frame_count = int(round(duration * fps)) if duration and fps else 0 + finally: + reader.close() + if not duration and fps and frame_count: + duration = frame_count / fps + return { + "width": int(width), + "height": int(height), + "fps": fps, + "frame_count": frame_count, + "duration_seconds": duration, + } + + +def run_ffmpeg(arguments: list[str], destination: str | os.PathLike[str]) -> Path: + """Run the bundled FFmpeg executable and never leave a partial destination.""" + from imageio_ffmpeg import get_ffmpeg_exe + + output = Path(destination).expanduser().resolve() + output.parent.mkdir(parents=True, exist_ok=True) + command = [get_ffmpeg_exe(), "-y", "-v", "error", *map(str, arguments), str(output)] + try: + result = subprocess.run(command, check=False, capture_output=True, text=True) + if result.returncode: + detail = (result.stderr or result.stdout or "unknown FFmpeg error").strip() + raise RuntimeError(f"FFmpeg failed while creating {output.name}: {detail}") + if not output.is_file(): + raise RuntimeError(f"FFmpeg reported success but did not create {output}.") + except Exception: + output.unlink(missing_ok=True) + raise + return output + + +def register_derived_video_asset( + destination: str | os.PathLike[str], + *, + asset_id: str | None = None, + task_id: str | None = None, + source_assets: list[dict[str, Any]] | None = None, + operation: str, + pinned: bool = False, + root=None, +) -> dict[str, Any]: + """Probe and register the output of a file-native media operation.""" + metadata = probe_video_file(destination) + source_ids = [str(item["asset_id"]) for item in (source_assets or []) if item.get("asset_id")] + return register_video_asset( + destination, + asset_id=asset_id, + task_id=task_id, + width=metadata["width"], + height=metadata["height"], + fps=metadata["fps"], + frame_count=metadata["frame_count"], + temporary=True, + pinned=pinned, + source_asset_ids=source_ids, + operation=operation, + root=root, + ) + + +def list_media_assets(*, root=None) -> list[dict[str, Any]]: + root_path = asset_root(root) + with _LOCK: + records = list(_read(root_path)["assets"].values()) + return sorted(records, key=lambda item: float(item.get("created_at") or 0), reverse=True) + + +def cleanup_media_assets( + *, + task_id: str | None = None, + older_than_seconds: float | None = None, + include_pinned: bool = False, + root=None, +) -> dict[str, Any]: + root_path = asset_root(root) + cutoff = time.time() - max(0.0, float(older_than_seconds)) if older_than_seconds is not None else None + removed = [] + errors = [] + with _LOCK: + manifest = _read(root_path) + retained = {} + for asset_id, record in manifest["assets"].items(): + selected = bool(record.get("temporary", False)) + if task_id is not None: + selected = selected and record.get("task_id") == str(task_id) + if cutoff is not None: + selected = selected and float(record.get("created_at") or 0) < cutoff + if record.get("pinned") and not include_pinned: + selected = False + if not selected: + retained[asset_id] = record + continue + path = Path(str(record.get("path") or "")) + try: + resolved = path.resolve() + resolved.relative_to(root_path) + resolved.unlink(missing_ok=True) + removed.append({"asset_id": asset_id, "path": str(resolved)}) + except Exception as exc: + errors.append({"asset_id": asset_id, "path": str(path), "error": str(exc)}) + retained[asset_id] = record + manifest["assets"] = retained + _write(root_path, manifest) + return {"removed": removed, "errors": errors, "remaining": len(retained)} diff --git a/modiff/media_import.py b/modiff/media_import.py new file mode 100644 index 0000000..cd18fb2 --- /dev/null +++ b/modiff/media_import.py @@ -0,0 +1,347 @@ +"""Safe, backend-owned imports for reusable graph media sources.""" + +from __future__ import annotations + +import hashlib +import http.client +import ipaddress +import mimetypes +import os +import shutil +import socket +import tempfile +import threading +from pathlib import Path +from urllib.error import HTTPError, URLError +from urllib.parse import urlparse +from urllib.request import HTTPHandler, HTTPRedirectHandler, HTTPSHandler, ProxyHandler, Request, build_opener + +from modiff.config import CONFIG + + +_MIME_EXTENSIONS = { + "image/jpeg": ".jpg", + "image/png": ".png", + "image/webp": ".webp", + "image/gif": ".gif", + "audio/mpeg": ".mp3", + "audio/wav": ".wav", + "audio/x-wav": ".wav", + "audio/flac": ".flac", + "audio/ogg": ".ogg", + "video/mp4": ".mp4", + "video/webm": ".webm", + "video/quicktime": ".mov", +} +_MEDIA_PREFIXES = ("image/", "audio/", "video/") +_YOUTUBE_HOSTS = {"youtube.com", "www.youtube.com", "m.youtube.com", "music.youtube.com", "youtu.be"} +_AUDIO_CONVERSION_LOCKS = tuple(threading.Lock() for _ in range(64)) + + +def _audio_conversion_lock(path: Path): + return _AUDIO_CONVERSION_LOCKS[hash(str(path)) % len(_AUDIO_CONVERSION_LOCKS)] + + +def _file_sha256(path: Path) -> str: + with path.open("rb") as handle: + return hashlib.file_digest(handle, "sha256").hexdigest() + + +def import_root(root: str | os.PathLike[str] | None = None) -> Path: + # Config exposes the canonical application data directory as ``data``. + # ``data_dir`` was never a supported key and broke non-WAV Audio.Load + # inputs exactly when they needed the managed ffmpeg conversion cache. + value = Path(root) if root is not None else Path(CONFIG.paths["data"]) / "imports" + return value.expanduser().resolve() + + +def _public_address_info(host: str, port: int) -> list[tuple[int, tuple]]: + try: + resolved = socket.getaddrinfo(host, port, type=socket.SOCK_STREAM) + except socket.gaierror as exc: + raise ValueError("The media host could not be resolved.") from exc + if not resolved: + raise ValueError("The media host could not be resolved.") + addresses = [] + seen = set() + for family, _socktype, _proto, _canonname, sockaddr in resolved: + address = sockaddr[0] + ip = ipaddress.ip_address(address) + if not ip.is_global: + raise ValueError("Local and private network addresses cannot be imported from a graph.") + key = (family, sockaddr) + if key not in seen: + seen.add(key) + addresses.append(key) + return addresses + + +def _public_http_url(value: str, *, youtube_only: bool = False) -> str: + parsed = urlparse(str(value or "").strip()) + if parsed.scheme not in {"http", "https"} or not parsed.hostname or parsed.username or parsed.password: + raise ValueError("Enter a public HTTP or HTTPS media URL.") + host = parsed.hostname.rstrip(".").lower() + if youtube_only and host not in _YOUTUBE_HOSTS: + raise ValueError("The YouTube source accepts youtube.com and youtu.be links only.") + _public_address_info(host, parsed.port or (443 if parsed.scheme == "https" else 80)) + return parsed.geturl() + + +def _open_public_socket(host: str, port: int, timeout: float | object, source_address=None): + """Resolve, validate, and connect to the exact validated address. + + Connecting to the validated numeric socket address prevents DNS rebinding + between the SSRF check and the actual network request. + """ + + last_error = None + for family, sockaddr in _public_address_info(host, port): + sock = socket.socket(family, socket.SOCK_STREAM) + try: + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sockaddr) + return sock + except OSError as exc: + last_error = exc + sock.close() + if last_error is not None: + raise last_error + raise OSError("The media host has no usable public address.") + + +class _PinnedHTTPConnection(http.client.HTTPConnection): + def connect(self): + self.sock = _open_public_socket(self.host, self.port, self.timeout, self.source_address) + if self._tunnel_host: + self._tunnel() + + +class _PinnedHTTPSConnection(http.client.HTTPSConnection): + def connect(self): + self.sock = _open_public_socket(self.host, self.port, self.timeout, self.source_address) + server_hostname = self.host + if self._tunnel_host: + self._tunnel() + server_hostname = self._tunnel_host + self.sock = self._context.wrap_socket(self.sock, server_hostname=server_hostname) + + +class _PinnedHTTPHandler(HTTPHandler): + def http_open(self, req): + return self.do_open(_PinnedHTTPConnection, req) + + +class _PinnedHTTPSHandler(HTTPSHandler): + def https_open(self, req): + return self.do_open( + _PinnedHTTPSConnection, + req, + context=self._context, + check_hostname=self._check_hostname, + ) + + +class _SafeRedirects(HTTPRedirectHandler): + def redirect_request(self, req, fp, code, msg, headers, newurl): + return super().redirect_request(req, fp, code, msg, headers, _public_http_url(newurl)) + + +def import_web_media( + url: str, + *, + max_bytes: int = 256 * 1024 * 1024, + timeout_seconds: float = 30, + root: str | os.PathLike[str] | None = None, +) -> Path: + """Download one public media response into the app cache with hard limits.""" + + safe_url = _public_http_url(url) + request = Request(safe_url, headers={"User-Agent": "MoDiff/1.0 media import"}) + # Do not delegate these graph-controlled URLs to an environment proxy: a + # proxy can resolve the hostname differently and bypass the local address + # validation. Each direct connection is pinned to its validated address. + opener = build_opener(ProxyHandler({}), _PinnedHTTPHandler(), _PinnedHTTPSHandler(), _SafeRedirects()) + destination_root = import_root(root) / "web" + destination_root.mkdir(parents=True, exist_ok=True) + digest = hashlib.sha256() + temporary = None + try: + with opener.open(request, timeout=float(timeout_seconds)) as response: + content_type = str(response.headers.get_content_type() or "").lower() + if not content_type.startswith(_MEDIA_PREFIXES): + raise ValueError("The URL did not return an image, audio file, or video file.") + content_length = response.headers.get("Content-Length") + if content_length and int(content_length) > max_bytes: + raise ValueError(f"The media file exceeds the {max_bytes // (1024 * 1024)} MB import limit.") + with tempfile.NamedTemporaryFile(dir=destination_root, prefix=".download-", delete=False) as handle: + temporary = Path(handle.name) + total = 0 + while True: + chunk = response.read(1024 * 1024) + if not chunk: + break + total += len(chunk) + if total > max_bytes: + raise ValueError(f"The media file exceeds the {max_bytes // (1024 * 1024)} MB import limit.") + digest.update(chunk) + handle.write(chunk) + suffix = _MIME_EXTENSIONS.get(content_type) + if not suffix: + suffix = Path(urlparse(response.geturl()).path).suffix.lower() or mimetypes.guess_extension( + content_type + ) + if not suffix or len(suffix) > 8: + suffix = ".bin" + destination = destination_root / f"{digest.hexdigest()}{suffix}" + if destination.exists(): + temporary.unlink(missing_ok=True) + else: + temporary.replace(destination) + return destination + except (HTTPError, URLError, TimeoutError) as exc: + raise ValueError(f"The media URL could not be downloaded: {exc}") from exc + finally: + if temporary is not None: + temporary.unlink(missing_ok=True) + + +def import_youtube_media( + url: str, + *, + media_kind: str = "video", + max_duration_seconds: int = 1800, + max_bytes: int = 512 * 1024 * 1024, + root: str | os.PathLike[str] | None = None, +) -> Path: + """Download one user-authorized YouTube item as video or WAV audio.""" + + safe_url = _public_http_url(url, youtube_only=True) + if media_kind not in {"video", "audio"}: + raise ValueError("YouTube media kind must be video or audio.") + try: + from yt_dlp import YoutubeDL + except ImportError as exc: + raise RuntimeError("YouTube import needs the yt-dlp dependency.") from exc + + destination_root = import_root(root) / "youtube" + destination_root.mkdir(parents=True, exist_ok=True) + work = Path(tempfile.mkdtemp(prefix=".youtube-", dir=destination_root)) + options = { + "noplaylist": True, + "playlist_items": "1", + "paths": {"home": str(work)}, + "outtmpl": {"default": "%(id)s.%(ext)s"}, + "max_filesize": int(max_bytes), + "match_filter": lambda info, *, incomplete=False: ( + f"Video exceeds the {max_duration_seconds}-second import limit" + if not incomplete and float(info.get("duration") or 0) > max_duration_seconds + else None + ), + "quiet": True, + "no_warnings": True, + "overwrites": False, + } + if media_kind == "audio": + options.update( + { + "format": "bestaudio/best", + "postprocessors": [{"key": "FFmpegExtractAudio", "preferredcodec": "wav"}], + } + ) + else: + options.update( + { + "format": "bv*[height<=1080]+ba/b[height<=1080]/b", + "merge_output_format": "mp4", + } + ) + try: + with YoutubeDL(options) as downloader: + info = downloader.extract_info(safe_url, download=True) + identifier = str((info or {}).get("id") or "youtube") + candidates = [path for path in work.iterdir() if path.is_file() and not path.name.endswith(".part")] + if not candidates: + raise RuntimeError("YouTube import completed without a media file.") + source = max(candidates, key=lambda path: path.stat().st_size) + if source.stat().st_size > max_bytes: + raise ValueError(f"The downloaded media exceeds the {max_bytes // (1024 * 1024)} MB import limit.") + digest = _file_sha256(source) + suffix = ".wav" if media_kind == "audio" else source.suffix.lower() + destination = destination_root / f"{identifier}-{digest[:12]}{suffix}" + if destination.exists(): + return destination + shutil.move(str(source), destination) + return destination + finally: + shutil.rmtree(work, ignore_errors=True) + + +def audio_as_wav( + path: str | os.PathLike[str], + *, + sample_rate: int | None = None, + channels: int | None = None, + root: str | os.PathLike[str] | None = None, +) -> Path: + """Return a model-ready WAV without altering the imported original. + + ``sample_rate`` and ``channels`` are intentionally caller-owned. A model + adapter should request the rate and channel layout its processor expects; + the generic loader preserves the source representation. + """ + + source = Path(path).expanduser().resolve() + if not source.is_file(): + raise FileNotFoundError(f"Audio file does not exist: {source}") + target_sample_rate = int(sample_rate) if sample_rate is not None else None + target_channels = int(channels) if channels is not None else None + if target_sample_rate is not None and not 8000 <= target_sample_rate <= 384000: + raise ValueError("Audio sample rate must be between 8 kHz and 384 kHz.") + if target_channels is not None and not 1 <= target_channels <= 8: + raise ValueError("Audio channel count must be between 1 and 8.") + if source.suffix.lower() == ".wav" and target_sample_rate is None and target_channels is None: + return source + import subprocess + + from imageio_ffmpeg import get_ffmpeg_exe + + digest = _file_sha256(source) + destination_root = import_root(root) / "audio" + destination_root.mkdir(parents=True, exist_ok=True) + conversion = f"{target_sample_rate or 'source'}-{target_channels or 'source'}" + destination = destination_root / f"{digest}-{conversion}.wav" + if destination.exists(): + return destination + with _audio_conversion_lock(destination): + if destination.exists(): + return destination + with tempfile.NamedTemporaryFile( + dir=destination_root, + prefix=f".{destination.stem}-", + suffix=".wav", + delete=False, + ) as handle: + temporary = Path(handle.name) + try: + command = [get_ffmpeg_exe(), "-y", "-v", "error", "-i", str(source), "-vn"] + if target_sample_rate is not None: + command.extend(["-ar", str(target_sample_rate)]) + if target_channels is not None: + command.extend(["-ac", str(target_channels)]) + command.extend(["-c:a", "pcm_s16le", str(temporary)]) + result = subprocess.run( + command, + check=False, + capture_output=True, + text=True, + ) + if result.returncode or not temporary.is_file(): + detail = (result.stderr or result.stdout or "unsupported audio format").strip() + raise ValueError(f"The selected audio could not be converted to WAV: {detail}") + temporary.replace(destination) + finally: + temporary.unlink(missing_ok=True) + return destination diff --git a/modiff/media_io.py b/modiff/media_io.py new file mode 100644 index 0000000..658f322 --- /dev/null +++ b/modiff/media_io.py @@ -0,0 +1,571 @@ +"""Capability-driven media probing, normalization, and delivery exports. + +Original user files remain untouched. Model loaders and download actions may +derive cached representations from those originals, keyed by the source digest +and the complete conversion request. +""" + +from __future__ import annotations + +import hashlib +import json +import mimetypes +import re +import subprocess +import tempfile +import threading +from functools import lru_cache +from pathlib import Path +from typing import Any + +from imageio_ffmpeg import get_ffmpeg_exe +from PIL import Image, ImageOps, UnidentifiedImageError, features + + +MEDIA_CAPABILITY_VERSION = 1 +SUPPORTED_AUDIO_SAMPLE_RATES = (44100, 48000, 88200, 96000) +_MEDIA_EXPORT_LOCKS = tuple(threading.Lock() for _ in range(64)) + + +def _media_export_lock(path: Path): + return _MEDIA_EXPORT_LOCKS[hash(str(path)) % len(_MEDIA_EXPORT_LOCKS)] + +_AUDIO_FORMATS = { + "wav": { + "label": "WAV", + "extension": ".wav", + "mimeType": "audio/wav", + "encoder": "pcm_s16le", + "preset": "Lossless", + "sampleRates": SUPPORTED_AUDIO_SAMPLE_RATES, + }, + "flac": { + "label": "FLAC", + "extension": ".flac", + "mimeType": "audio/flac", + "encoder": "flac", + "preset": "Lossless", + "sampleRates": SUPPORTED_AUDIO_SAMPLE_RATES, + }, + "mp3": { + "label": "MP3", + "extension": ".mp3", + "mimeType": "audio/mpeg", + "encoder": "libmp3lame", + "preset": "Compatible", + "sampleRates": (44100, 48000), + }, + "m4a": { + "label": "M4A (AAC)", + "extension": ".m4a", + "mimeType": "audio/mp4", + "encoder": "aac", + "preset": "Compatible", + "sampleRates": SUPPORTED_AUDIO_SAMPLE_RATES, + }, + "aac": { + "label": "AAC", + "extension": ".aac", + "mimeType": "audio/aac", + "encoder": "aac", + "preset": "Compatible", + "sampleRates": SUPPORTED_AUDIO_SAMPLE_RATES, + }, + "opus": { + "label": "Opus", + "extension": ".opus", + "mimeType": "audio/ogg", + "encoder": "libopus", + "preset": "Compact", + # Opus is encoded internally at 48 kHz. Do not offer a selector that + # implies a different encoded rate. + "sampleRates": (48000,), + }, +} + +_IMAGE_FORMATS = { + "png": { + "label": "PNG", + "extension": ".png", + "mimeType": "image/png", + "pillowFormat": "PNG", + "preset": "Lossless", + "supportsAlpha": True, + }, + "jpeg": { + "label": "JPEG", + "extension": ".jpg", + "mimeType": "image/jpeg", + "pillowFormat": "JPEG", + "preset": "Compatible", + "supportsAlpha": False, + }, + "webp": { + "label": "WebP", + "extension": ".webp", + "mimeType": "image/webp", + "pillowFormat": "WEBP", + "preset": "Compact", + "supportsAlpha": True, + }, + "avif": { + "label": "AVIF", + "extension": ".avif", + "mimeType": "image/avif", + "pillowFormat": "AVIF", + "preset": "Compact", + "supportsAlpha": True, + "feature": "avif", + }, + "tiff": { + "label": "TIFF", + "extension": ".tiff", + "mimeType": "image/tiff", + "pillowFormat": "TIFF", + "preset": "Editing", + "supportsAlpha": True, + }, +} + +_VIDEO_FORMATS = { + "mp4": { + "label": "MP4 (H.264 + AAC)", + "extension": ".mp4", + "mimeType": "video/mp4", + "encoders": ("libx264", "aac"), + "preset": "Compatible", + }, + "webm": { + "label": "WebM (VP9 + Opus)", + "extension": ".webm", + "mimeType": "video/webm", + "encoders": ("libvpx-vp9", "libopus"), + "preset": "Web", + }, + "mov": { + "label": "MOV (ProRes + PCM)", + "extension": ".mov", + "mimeType": "video/quicktime", + "encoders": ("prores", "pcm_s16le"), + "preset": "Editing", + }, + "gif": { + "label": "Animated GIF", + "extension": ".gif", + "mimeType": "image/gif", + "encoders": ("gif",), + "preset": "Loop", + "hasAudio": False, + }, +} + +_IMPORT_EXTENSIONS = { + "audio": ( + ".wav", + ".wave", + ".bwf", + ".aif", + ".aiff", + ".flac", + ".mp3", + ".m4a", + ".aac", + ".mp4", + ".ogg", + ".oga", + ".opus", + ".wma", + ), + "image": ( + ".png", + ".jpg", + ".jpeg", + ".webp", + ".avif", + ".gif", + ".bmp", + ".tif", + ".tiff", + ".ico", + ), + "video": ( + ".mp4", + ".m4v", + ".mov", + ".webm", + ".mkv", + ".avi", + ".mpeg", + ".mpg", + ".ts", + ".mts", + ".m2ts", + ".wmv", + ".flv", + ), +} + + +def _sha256(path: Path) -> str: + with path.open("rb") as handle: + return hashlib.file_digest(handle, "sha256").hexdigest() + + +@lru_cache(maxsize=1) +def ffmpeg_encoders() -> frozenset[str]: + """Return encoder names exposed by the exact bundled FFmpeg binary.""" + + result = subprocess.run( + [get_ffmpeg_exe(), "-hide_banner", "-encoders"], + check=False, + capture_output=True, + text=True, + timeout=10, + ) + if result.returncode: + return frozenset() + names: set[str] = set() + for line in result.stdout.splitlines(): + match = re.match(r"^\s*[A-Z\.]{6}\s+([^\s]+)", line) + if match: + names.add(match.group(1)) + return frozenset(names) + + +def _format_descriptor(value: str, descriptor: dict[str, Any]) -> dict[str, Any]: + internal_keys = {"encoder", "encoders", "pillowFormat", "feature"} + return { + "value": value, + **{ + key: list(item) if isinstance(item, tuple) else item + for key, item in descriptor.items() + if key not in internal_keys + }, + } + + +def media_capabilities() -> dict[str, Any]: + """Describe only the import and export choices this runtime can fulfill.""" + + encoders = ffmpeg_encoders() + audio = [ + _format_descriptor(value, descriptor) + for value, descriptor in _AUDIO_FORMATS.items() + if descriptor["encoder"] in encoders + ] + image = [ + _format_descriptor(value, descriptor) + for value, descriptor in _IMAGE_FORMATS.items() + if not descriptor.get("feature") or features.check(str(descriptor["feature"])) + ] + video = [ + _format_descriptor(value, descriptor) + for value, descriptor in _VIDEO_FORMATS.items() + if all(encoder in encoders for encoder in descriptor["encoders"]) + ] + return { + "version": MEDIA_CAPABILITY_VERSION, + "media": { + "audio": {"importExtensions": list(_IMPORT_EXTENSIONS["audio"]), "exportFormats": audio}, + "image": {"importExtensions": list(_IMPORT_EXTENSIONS["image"]), "exportFormats": image}, + "video": {"importExtensions": list(_IMPORT_EXTENSIONS["video"]), "exportFormats": video}, + "text": { + "importExtensions": [".txt", ".md", ".json", ".csv", ".srt", ".vtt"], + "exportFormats": [ + { + "value": "original", + "label": "Original", + "extension": "", + "mimeType": "application/octet-stream", + "preset": "Original", + } + ], + }, + }, + } + + +def _ffmpeg_probe(path: Path) -> tuple[str | None, str]: + result = subprocess.run( + [get_ffmpeg_exe(), "-hide_banner", "-nostdin", "-i", str(path), "-t", "0", "-f", "null", "-"], + check=False, + capture_output=True, + text=True, + timeout=15, + ) + detail = result.stderr or result.stdout + has_video = bool(re.search(r"Stream #.*Video:", detail)) + has_audio = bool(re.search(r"Stream #.*Audio:", detail)) + if has_video: + return "video", detail + if has_audio: + return "audio", detail + return None, detail + + +def probe_media_file(path: str | Path, expected_kind: str | None = None) -> dict[str, Any]: + """Decode enough of a file to classify its real content, not its suffix.""" + + source = Path(path).expanduser().resolve() + if not source.is_file(): + raise FileNotFoundError(f"Media file does not exist: {source}") + expected = str(expected_kind or "").rstrip("s").lower() or None + metadata: dict[str, Any] = { + "filename": source.name, + "extension": source.suffix.lower(), + "sizeBytes": source.stat().st_size, + "sha256": _sha256(source), + } + try: + with Image.open(source) as image: + image.verify() + with Image.open(source) as image: + metadata.update( + { + "kind": "image", + "mimeType": Image.MIME.get(image.format, mimetypes.guess_type(source.name)[0]), + "width": image.width, + "height": image.height, + "frames": int(getattr(image, "n_frames", 1)), + } + ) + except (UnidentifiedImageError, OSError, ValueError): + kind, detail = _ffmpeg_probe(source) + if kind is None: + if expected == "text": + try: + source.read_text(encoding="utf-8") + metadata.update({"kind": "text", "mimeType": mimetypes.guess_type(source.name)[0] or "text/plain"}) + except UnicodeDecodeError as exc: + raise ValueError("The selected file is not valid UTF-8 text or supported media.") from exc + else: + message = detail.strip().splitlines()[-1] if detail.strip() else "unsupported or corrupt media" + raise ValueError(f"The selected file could not be decoded: {message}") + else: + metadata.update({"kind": kind, "mimeType": mimetypes.guess_type(source.name)[0]}) + duration = re.search(r"Duration:\s*(\d+):(\d+):([\d.]+)", detail) + if duration: + metadata["durationSeconds"] = ( + int(duration.group(1)) * 3600 + int(duration.group(2)) * 60 + float(duration.group(3)) + ) + sample_rate = re.search(r"Audio:.*?(\d+)\s+Hz", detail) + if sample_rate: + metadata["sampleRate"] = int(sample_rate.group(1)) + dimensions = re.search(r"Video:.*?(\d{2,5})x(\d{2,5})", detail) + if dimensions: + metadata["width"] = int(dimensions.group(1)) + metadata["height"] = int(dimensions.group(2)) + if expected and metadata["kind"] != expected: + raise ValueError(f"The selected file contains {metadata['kind']} data, not {expected} data.") + return metadata + + +def _safe_stem(value: str) -> str: + stem = Path(value).stem + sanitized = re.sub(r"[^A-Za-z0-9._-]+", "-", stem).strip(".-") + return sanitized or "MoDiff-output" + + +def _validated_sample_rate(value: Any, descriptor: dict[str, Any]) -> int: + rates = tuple(int(rate) for rate in descriptor.get("sampleRates", ())) + if not rates: + raise ValueError("This format does not expose a sample-rate choice.") + try: + sample_rate = int(value) + except (TypeError, ValueError): + sample_rate = rates[0] + if sample_rate not in rates: + readable = ", ".join(f"{rate / 1000:g} kHz" for rate in rates) + raise ValueError(f"The selected format supports these sample rates: {readable}.") + return sample_rate + + +def _ffmpeg_export(source: Path, destination: Path, kind: str, format_id: str, options: dict[str, Any]) -> None: + command = [get_ffmpeg_exe(), "-y", "-v", "error", "-nostdin", "-i", str(source)] + if kind == "audio": + descriptor = _AUDIO_FORMATS[format_id] + sample_rate = _validated_sample_rate(options.get("sampleRate"), descriptor) + command.extend(["-vn", "-ar", str(sample_rate)]) + if channels := options.get("channels"): + command.extend(["-ac", str(max(1, min(8, int(channels))))]) + if format_id == "wav": + bit_depth = int(options.get("bitDepth") or 16) + codec = "pcm_s24le" if bit_depth == 24 else "pcm_s16le" + command.extend(["-c:a", codec]) + elif format_id == "flac": + command.extend( + ["-c:a", "flac", "-compression_level", str(max(0, min(12, int(options.get("compression") or 8))))] + ) + elif format_id == "mp3": + command.extend(["-c:a", "libmp3lame", "-b:a", f"{max(64, min(320, int(options.get('bitrate') or 320)))}k"]) + elif format_id == "m4a": + command.extend( + [ + "-c:a", + "aac", + "-b:a", + f"{max(64, min(512, int(options.get('bitrate') or 256)))}k", + "-movflags", + "+faststart", + ] + ) + elif format_id == "aac": + command.extend(["-c:a", "aac", "-b:a", f"{max(64, min(512, int(options.get('bitrate') or 256)))}k"]) + elif format_id == "opus": + command.extend(["-c:a", "libopus", "-b:a", f"{max(32, min(512, int(options.get('bitrate') or 192)))}k"]) + elif kind == "video": + if format_id == "mp4": + command.extend( + [ + "-c:v", + "libx264", + "-preset", + str(options.get("speed") or "medium"), + "-crf", + str(max(0, min(51, int(options.get("quality") or 18)))), + "-pix_fmt", + "yuv420p", + "-c:a", + "aac", + "-b:a", + "192k", + "-movflags", + "+faststart", + ] + ) + elif format_id == "webm": + command.extend( + [ + "-c:v", + "libvpx-vp9", + "-crf", + str(max(0, min(63, int(options.get("quality") or 30)))), + "-b:v", + "0", + "-c:a", + "libopus", + "-b:a", + "160k", + ] + ) + elif format_id == "mov": + command.extend(["-c:v", "prores", "-profile:v", "3", "-c:a", "pcm_s16le"]) + elif format_id == "gif": + fps = max(1, min(30, int(options.get("fps") or 12))) + width = max(160, min(1920, int(options.get("width") or 640))) + command.extend(["-an", "-vf", f"fps={fps},scale={width}:-2:flags=lanczos", "-c:v", "gif"]) + command.append(str(destination)) + result = subprocess.run(command, check=False, capture_output=True, text=True, timeout=3600) + if result.returncode or not destination.is_file(): + destination.unlink(missing_ok=True) + detail = (result.stderr or result.stdout or "unsupported conversion").strip() + raise ValueError(f"Could not create the requested {format_id.upper()} file: {detail}") + + +def _image_export(source: Path, destination: Path, format_id: str, options: dict[str, Any]) -> None: + descriptor = _IMAGE_FORMATS[format_id] + with Image.open(source) as opened: + image = ImageOps.exif_transpose(opened) + image.load() + if format_id == "jpeg": + if image.mode not in {"RGB", "L"}: + background = Image.new("RGB", image.size, str(options.get("background") or "#ffffff")) + if image.mode in {"RGBA", "LA"}: + background.paste(image.convert("RGBA"), mask=image.convert("RGBA").getchannel("A")) + else: + background.paste(image.convert("RGB")) + image = background + else: + image = image.convert("RGB") + save_options: dict[str, Any] = {"format": descriptor["pillowFormat"]} + if format_id in {"jpeg", "webp", "avif"}: + save_options["quality"] = max(1, min(100, int(options.get("quality") or 90))) + if format_id == "png": + save_options["compress_level"] = max(0, min(9, int(options.get("compression") or 6))) + image.save(destination, **save_options) + + +def export_media_file( + source: str | Path, + *, + kind: str, + format_id: str, + options: dict[str, Any] | None = None, + cache_root: str | Path, +) -> tuple[Path, str, str]: + """Create or reuse a delivery export and return path, MIME type, filename.""" + + source_path = Path(source).expanduser().resolve() + normalized_kind = str(kind).rstrip("s").lower() + format_value = str(format_id).lower() + options = dict(options or {}) + metadata = probe_media_file(source_path, normalized_kind) + if format_value == "original": + mime_type = metadata.get("mimeType") or "application/octet-stream" + return source_path, str(mime_type), source_path.name + formats = {"audio": _AUDIO_FORMATS, "image": _IMAGE_FORMATS, "video": _VIDEO_FORMATS}.get(normalized_kind) + if not formats or format_value not in formats: + raise ValueError(f"{format_value or 'The requested format'} is not available for {normalized_kind}.") + available = {item["value"] for item in media_capabilities()["media"][normalized_kind]["exportFormats"]} + if format_value not in available: + raise ValueError(f"{formats[format_value]['label']} export is not available in this MoDiff runtime.") + descriptor = formats[format_value] + request_key = json.dumps( + {"source": metadata["sha256"], "kind": normalized_kind, "format": format_value, "options": options}, + sort_keys=True, + separators=(",", ":"), + ) + digest = hashlib.sha256(request_key.encode("utf-8")).hexdigest() + destination_root = Path(cache_root).expanduser().resolve() + destination_root.mkdir(parents=True, exist_ok=True) + filename = f"{_safe_stem(source_path.name)}-{digest[:10]}{descriptor['extension']}" + destination = destination_root / filename + if not destination.is_file(): + with _media_export_lock(destination): + if not destination.is_file(): + with tempfile.NamedTemporaryFile( + dir=destination_root, + prefix=f".{destination.stem}-", + suffix=destination.suffix, + delete=False, + ) as handle: + temporary = Path(handle.name) + try: + if normalized_kind == "image": + _image_export(source_path, temporary, format_value, options) + else: + _ffmpeg_export(source_path, temporary, normalized_kind, format_value, options) + temporary.replace(destination) + finally: + temporary.unlink(missing_ok=True) + return destination, str(descriptor["mimeType"]), filename + + +def export_media_bytes( + body: bytes, + *, + source_suffix: str, + kind: str, + format_id: str, + options: dict[str, Any] | None, + cache_root: str | Path, +) -> tuple[Path, str, str]: + """Export in-memory node output through the same file-backed contract.""" + + digest = hashlib.sha256(body).hexdigest() + source_root = Path(cache_root).expanduser().resolve() / "sources" + source_root.mkdir(parents=True, exist_ok=True) + suffix = source_suffix if str(source_suffix).startswith(".") else f".{source_suffix}" + source = source_root / f"{digest}{suffix}" + if not source.is_file(): + with tempfile.NamedTemporaryFile(dir=source_root, prefix=".source-", suffix=suffix, delete=False) as handle: + temporary = Path(handle.name) + handle.write(body) + temporary.replace(source) + return export_media_file( + source, + kind=kind, + format_id=format_id, + options=options, + cache_root=Path(cache_root) / "exports", + ) diff --git a/modiff/model_artifact_catalog.py b/modiff/model_artifact_catalog.py new file mode 100644 index 0000000..0d0dbd6 --- /dev/null +++ b/modiff/model_artifact_catalog.py @@ -0,0 +1,213 @@ +from __future__ import annotations + +import json +import re +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +CATALOG_PATH = Path(__file__).resolve().parent.parent / "data" / "model-artifact-catalog.json" +AUTO_TRUST_LEVELS = {"official", "modiff_qualified"} +COMMUNITY_DISPLAY_MIN_DOWNLOADS = 1_000 +COMMUNITY_DISPLAY_MIN_LIKES = 10 +IMMUTABLE_HUB_REVISION = re.compile(r"^[0-9a-f]{40}$", re.IGNORECASE) + + +def read_model_artifact_catalog() -> dict[str, Any]: + with CATALOG_PATH.open("r", encoding="utf-8") as handle: + payload = json.load(handle) + if payload.get("schemaVersion") != 1 or not isinstance(payload.get("models"), list): + raise ValueError("Unsupported model artifact catalog schema.") + defaults = payload.get("artifactDefaults") if isinstance(payload.get("artifactDefaults"), dict) else {} + checked_at = payload.get("checkedAt") + for model in payload["models"]: + if not isinstance(model, dict): + continue + model.setdefault("baseRevision", None) + artifacts = model.get("artifacts") if isinstance(model.get("artifacts"), list) else [] + expanded = [] + for raw in artifacts: + if not isinstance(raw, dict): + continue + artifact = {**defaults, **raw} + artifact["popularitySnapshot"] = { + "checkedAt": checked_at, + "downloads": int(raw.get("downloads") or 0), + "likes": int(raw.get("likes") or 0), + } + trust = str(raw.get("trust") or "community") + if "qualificationEvidence" not in raw: + artifact["qualificationEvidence"] = { + "status": "runtime-qualified" if trust == "modiff_qualified" else "documented" if trust == "official" else "community-option", + "source": "modiff-qualification" if trust == "modiff_qualified" else "publisher" if trust == "official" else "hub-popularity-snapshot", + } + format_name = str(raw.get("format") or "native") + if "supportedBackends" not in raw: + artifact["supportedBackends"] = ( + ["cuda"] + if format_name in {"diffusers-bnb", "nvfp4", "mxfp8"} + else ["cuda", "rocm"] + if format_name == "fp8" + else ["cuda", "rocm", "mps", "cpu"] + if format_name == "gguf" + else defaults.get("supportedBackends", []) + ) + if "supportedPlatforms" not in raw and format_name in {"nvfp4", "mxfp8"}: + artifact["supportedPlatforms"] = ["linux"] + expanded.append(artifact) + model["artifacts"] = expanded + return payload + + +def catalog_model(model_type: str) -> dict[str, Any] | None: + for model in read_model_artifact_catalog()["models"]: + if isinstance(model, dict) and model.get("modelType") == model_type: + return model + return None + + +def catalog_artifact(model_type: str, repo: str) -> dict[str, Any] | None: + model = catalog_model(model_type) + for artifact in (model or {}).get("artifacts") or []: + if isinstance(artifact, dict) and str(artifact.get("repo") or "").lower() == repo.lower(): + return artifact + return None + + +def catalog_repository_pin(repo: str, *, model_type: str | None = None) -> dict[str, Any] | None: + """Return the reviewed immutable pin for a cataloged Hub repository. + + Base models, optional artifacts, and fixed auxiliary repositories all use + the same lookup so loaders cannot accidentally follow a moving branch for + one part of a curated pipeline. Repository matching is case-insensitive, + but the catalog remains the source of truth for the canonical spelling. + """ + + repository = str(repo or "").strip() + if not repository: + return None + repository_lower = repository.lower() + catalog = read_model_artifact_catalog() + models = catalog["models"] + if model_type: + scoped = [model for model in models if model.get("modelType") == model_type] + models = scoped + [model for model in models if model not in scoped] + for model in models: + if str(model.get("baseRepo") or "").lower() == repository_lower: + return { + "repo": model["baseRepo"], + "revision": model.get("baseRevision"), + "license": model.get("baseLicense"), + "kind": "base", + "modelType": model.get("modelType"), + } + for artifact in model.get("artifacts") or []: + if str(artifact.get("repo") or "").lower() == repository_lower: + return {**artifact, "kind": "artifact", "modelType": model.get("modelType")} + for pin in catalog.get("repositoryPins") or []: + if isinstance(pin, dict) and str(pin.get("repo") or "").lower() == repository_lower: + return {**pin, "kind": "auxiliary"} + return None + + +def catalog_revision(repo: str, *, model_type: str | None = None) -> str | None: + """Return and validate the immutable revision recorded for ``repo``.""" + + pin = catalog_repository_pin(repo, model_type=model_type) + if pin is None: + return None + revision = str(pin.get("revision") or "").strip() + if not IMMUTABLE_HUB_REVISION.fullmatch(revision): + raise ValueError(f"Cataloged Hugging Face repository {repo!r} is missing a 40-character commit revision.") + return revision.lower() + + +def resolve_model_revision( + repo: str, + revision: Any = None, + *, + model_type: str | None = None, + source: str | None = None, +) -> str | None: + """Preserve an explicit revision or pin a known curated Hub repository. + + Unknown repositories retain normal user-selected Hugging Face semantics. + Local selections never inherit a Hub pin, even if their display value + happens to match a cataloged repository ID. + """ + + explicit = str(revision or "").strip() or None + if explicit is not None: + return explicit + if source is not None and str(source).strip().lower() != "hub": + return None + return catalog_revision(repo, model_type=model_type) + + +def require_catalog_revision(repo: str, *, model_type: str | None = None) -> str: + """Return a reviewed pin for a fixed built-in repository or fail closed.""" + + revision = catalog_revision(repo, model_type=model_type) + if revision is None: + raise ValueError(f"Built-in Hugging Face repository {repo!r} has no reviewed catalog revision.") + return revision + + +def community_artifact_is_discoverable(artifact: dict[str, Any]) -> bool: + return bool( + int(artifact.get("downloads") or 0) >= COMMUNITY_DISPLAY_MIN_DOWNLOADS + or int(artifact.get("likes") or 0) >= COMMUNITY_DISPLAY_MIN_LIKES + ) + + +def public_model_artifact_catalog() -> dict[str, Any]: + catalog = read_model_artifact_catalog() + return { + **catalog, + "policy": { + "autoTrustLevels": sorted(AUTO_TRUST_LEVELS), + "communityDisplayMinimumDownloads": COMMUNITY_DISPLAY_MIN_DOWNLOADS, + "communityDisplayMinimumLikes": COMMUNITY_DISPLAY_MIN_LIKES, + "popularityIsCompatibilityProof": False, + }, + } + + +def refreshed_hub_metadata(*, model_type: str | None = None, repo: str | None = None) -> dict[str, Any]: + """Return a live discovery overlay; never mutate vetted catalog selection fields.""" + from huggingface_hub import HfApi + from modiff.config import CONFIG + + catalog = read_model_artifact_catalog() + api = HfApi(token=CONFIG.hf.get("token"), library_name="MoDiff") + refreshed = [] + for model in catalog["models"]: + if model_type and model.get("modelType") != model_type: + continue + for artifact in model.get("artifacts") or []: + artifact_repo = str(artifact.get("repo") or "") + if not artifact_repo or (repo and artifact_repo.lower() != repo.lower()): + continue + try: + info = api.model_info(artifact_repo) + refreshed.append({ + "modelType": model.get("modelType"), + "repo": artifact_repo, + "liveRevision": getattr(info, "sha", None), + "downloads": int(getattr(info, "downloads", 0) or 0), + "likes": int(getattr(info, "likes", 0) or 0), + "status": "available", + }) + except Exception as exc: + refreshed.append({ + "modelType": model.get("modelType"), + "repo": artifact_repo, + "status": "unavailable", + "message": str(exc), + }) + return { + "checkedAt": datetime.now(timezone.utc).isoformat(), + "selectionChanged": False, + "artifacts": refreshed, + } diff --git a/modiff/modelstore.py b/modiff/modelstore.py index 277eaec..d29fea8 100644 --- a/modiff/modelstore.py +++ b/modiff/modelstore.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. from utils.huggingface import get_local_models as hf_get_local_models, is_file_cached from utils.paths import list_files from modiff.config import CONFIG diff --git a/modiff/optimization_packages.py b/modiff/optimization_packages.py new file mode 100644 index 0000000..6fc1aed --- /dev/null +++ b/modiff/optimization_packages.py @@ -0,0 +1,1089 @@ +"""Managed optional runtime optimizations. + +Optional accelerator packages are deliberately kept outside MoDiff's active +environment. A package is installed into a staged overlay, validated in a +fresh interpreter against the active Torch/Diffusers ABI, and only becomes +visible after an explicit activation and worker restart. The previous overlay +remains addressable for rollback. + +This module must remain importable with the Python standard library only: +``main.py`` activates the selected overlay before importing Torch or MoDiff's +runtime configuration. +""" + +from __future__ import annotations + +import hashlib +import importlib.metadata +import json +import os +import shutil +import site +import subprocess +import sys +import threading +import time +import uuid +from collections.abc import Callable +from copy import deepcopy +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +MANAGED_ROOT = Path(os.environ.get("MODIFF_MANAGED_ROOT") or ROOT / ".modiff") +OPTIMIZATION_ROOT = MANAGED_ROOT / "optimizations" +ENVIRONMENTS_DIR = OPTIMIZATION_ROOT / "environments" +STAGING_DIR = OPTIMIZATION_ROOT / "staging" +STATE_PATH = OPTIMIZATION_ROOT / "state.json" +RECEIPTS_PATH = OPTIMIZATION_ROOT / "qualification-receipts.json" +CATALOG_SCHEMA_VERSION = 1 +STATE_SCHEMA_VERSION = 1 +RECEIPT_SCHEMA_VERSION = 1 + +_STATE_LOCK = threading.RLock() + + +def _now() -> str: + return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + + +def _atomic_json(path: Path, value: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_suffix(path.suffix + ".tmp") + temporary.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8") + temporary.replace(path) + + +def _read_json(path: Path, fallback: dict[str, Any]) -> dict[str, Any]: + try: + value = json.loads(path.read_text(encoding="utf-8")) + return value if isinstance(value, dict) else deepcopy(fallback) + except (OSError, TypeError, ValueError): + return deepcopy(fallback) + + +def _default_state() -> dict[str, Any]: + return { + "schemaVersion": STATE_SCHEMA_VERSION, + "activeEnvironmentId": None, + "previousEnvironmentId": None, + "enabledCapabilities": [], + "updatedAt": _now(), + } + + +def read_state() -> dict[str, Any]: + with _STATE_LOCK: + state = _read_json(STATE_PATH, _default_state()) + state.setdefault("schemaVersion", STATE_SCHEMA_VERSION) + state.setdefault("enabledCapabilities", []) + return state + + +def _write_state(state: dict[str, Any]) -> dict[str, Any]: + with _STATE_LOCK: + state = deepcopy(state) + state["schemaVersion"] = STATE_SCHEMA_VERSION + state["updatedAt"] = _now() + _atomic_json(STATE_PATH, state) + return state + + +def _safe_environment_path(environment_id: str | None) -> Path | None: + if not environment_id or not isinstance(environment_id, str): + return None + candidate = (ENVIRONMENTS_DIR / environment_id).resolve() + try: + candidate.relative_to(ENVIRONMENTS_DIR.resolve()) + except ValueError: + return None + if not (candidate / "validation.json").is_file(): + return None + validation = _read_json(candidate / "validation.json", {}) + if validation.get("status") != "passed": + return None + site_packages = candidate / "site-packages" + return site_packages if site_packages.is_dir() else None + + +def activate_runtime_overlay() -> str | None: + """Add the validated active overlay before importing heavyweight modules.""" + state = read_state() + environment_id = state.get("activeEnvironmentId") + site_packages = _safe_environment_path(environment_id) + if site_packages is None: + return None + site.addsitedir(str(site_packages)) + # addsitedir appends; optional packages must win over incompatible packages + # from the base environment after the overlay passed the core import probe. + normalized = str(site_packages) + if normalized in sys.path: + sys.path.remove(normalized) + sys.path.insert(0, normalized) + os.environ["MODIFF_OPTIMIZATION_ENVIRONMENT"] = str(environment_id) + return str(environment_id) + + +def _catalog() -> dict[str, dict[str, Any]]: + # Versions are reviewed pins, not an online "latest" resolver. Upgrading a + # pin requires updating the official-source review and qualification tests. + return { + "hub_attention_kernels": { + "label": "Hub attention kernels", + "kind": "package", + "distribution": "kernels", + "importName": "kernels", + "packages": ["kernels==0.16.0"], + "installMode": "binary", + # The regular dependency set contains no Torch package. It is safe + # to resolve the signed-kernel metadata helpers into the overlay. + "includeDependencies": True, + "profiles": ["nvidia-cuda"], + "platforms": ["linux"], + "automaticEligible": True, + "summary": "Prebuilt attention kernels fetched through the Hugging Face kernels runtime.", + "documentation": "https://huggingface.co/docs/kernels/main/installation", + }, + "flash_attention_2": { + "label": "FlashAttention 2", + "kind": "package", + "distribution": "flash-attn", + "importName": "flash_attn", + "buildPackages": ["ninja==1.13.0"], + "packages": ["flash-attn==2.8.3.post1"], + "installMode": "source", + "profiles": ["nvidia-cuda", "amd-rocm-linux"], + "platforms": ["linux"], + "automaticEligible": True, + "summary": "Source-built FlashAttention 2 for qualified CUDA or ROCm hardware.", + "documentation": "https://github.com/Dao-AILab/flash-attention", + }, + "torchao": { + "label": "TorchAO quantization", + "kind": "package", + "distribution": "torchao", + "importName": "torchao", + "packages": ["torchao==0.17.0"], + "installMode": "binary", + "profiles": ["nvidia-cuda", "amd-rocm-linux", "cpu"], + "platforms": ["linux", "windows", "macos"], + "automaticEligible": True, + "summary": "Torch-native weight-only, integer, and floating-point quantization recipes.", + "documentation": "https://docs.pytorch.org/ao/stable/workflows/inference.html", + }, + "optimum_quanto": { + "label": "Optimum Quanto", + "kind": "package", + "distribution": "optimum-quanto", + "importName": "optimum.quanto", + "packages": ["ninja==1.13.0", "optimum-quanto==0.2.7"], + "installMode": "binary", + "profiles": ["nvidia-cuda", "amd-rocm-linux", "cpu"], + "platforms": ["linux", "windows"], + "automaticEligible": True, + "summary": "Quanto float8 and integer weight quantization.", + "documentation": "https://huggingface.co/docs/diffusers/main/api/quantization", + }, + "bitsandbytes": { + "label": "bitsandbytes quantization", + "kind": "package", + "distribution": "bitsandbytes", + "importName": "bitsandbytes", + "packages": ["bitsandbytes==0.50.0"], + "installMode": "binary", + "profiles": ["nvidia-cuda"], + "platforms": ["linux", "windows"], + "automaticEligible": True, + "summary": "Qualified 4-bit and 8-bit linear-layer quantization.", + "documentation": "https://huggingface.co/docs/bitsandbytes/stable/en/installation", + }, + "sage_attention": { + "label": "SageAttention", + "kind": "package", + "distribution": "sageattention", + "importName": "sageattention", + "packages": ["sageattention==1.0.6"], + "installMode": "source", + "profiles": ["nvidia-cuda"], + "platforms": ["linux"], + "automaticEligible": False, + "summary": "Experimental quantized attention kernels; manual opt-in and workload qualification required.", + "documentation": "https://github.com/thu-ml/SageAttention", + }, + "xformers": { + "label": "xFormers", + "kind": "profile", + "distribution": "xformers", + "importName": "xformers", + "profiles": ["nvidia-cuda"], + "platforms": ["linux", "windows"], + "automaticEligible": True, + "summary": "Profile-managed xFormers build matched to the installed PyTorch release.", + "documentation": "https://github.com/facebookresearch/xformers", + }, + "aiter": { + "label": "AMD AITER", + "kind": "external", + "distribution": "amd-aiter", + "importName": "aiter", + "profiles": ["amd-rocm-linux"], + "platforms": ["linux"], + "automaticEligible": False, + "summary": "AMD datacenter-kernel package; only qualified Instinct/ABI combinations are supported.", + "documentation": "https://github.com/ROCm/aiter", + }, + "regional_compile": { + "label": "Regional torch.compile", + "kind": "runtime", + "profiles": ["nvidia-cuda", "amd-rocm-linux", "cpu"], + "platforms": ["linux", "windows"], + "automaticEligible": True, + "summary": "Compile repeated model blocks instead of recompiling a whole pipeline.", + "documentation": "https://huggingface.co/docs/diffusers/main/optimization/fp16", + }, + "denoiser_cache": { + "label": "Denoiser cache", + "kind": "runtime", + "profiles": ["nvidia-cuda", "amd-rocm-linux"], + "platforms": ["linux", "windows"], + "automaticEligible": True, + "summary": "Model-specific block, residual, or timestep caching with explicit output review.", + "documentation": "https://huggingface.co/docs/diffusers/main/optimization/cache", + }, + "layerwise_casting": { + "label": "Layerwise casting", + "kind": "runtime", + "profiles": ["nvidia-cuda", "amd-rocm-linux"], + "platforms": ["linux", "windows"], + "automaticEligible": True, + "summary": "Store compatible weights at lower precision and cast them only for computation.", + "documentation": "https://huggingface.co/docs/diffusers/main/optimization/memory", + }, + "channels_last": { + "label": "Channels-last memory format", + "kind": "runtime", + "profiles": ["nvidia-cuda", "amd-rocm-linux"], + "platforms": ["linux", "windows"], + "automaticEligible": True, + "summary": "Opt-in convolution layout optimization for qualified UNet/VAE workloads.", + "documentation": "https://huggingface.co/docs/diffusers/main/optimization/fp16", + }, + "quantization_offload": { + "label": "Quantization with offload", + "kind": "runtime", + "implemented": False, + "profiles": ["nvidia-cuda", "amd-rocm-linux"], + "platforms": ["linux", "windows"], + "automaticEligible": True, + "summary": "Researched, but not selectable until each quantizer/offload ordering has a qualified runtime contract.", + "documentation": "https://huggingface.co/docs/diffusers/main/optimization/memory", + }, + "context_parallel": { + "label": "Multi-GPU context parallelism", + "kind": "runtime", + "implemented": False, + "profiles": ["nvidia-cuda"], + "platforms": ["linux"], + "automaticEligible": False, + "summary": "Experimental multi-GPU sharding for large transformer/video workloads.", + "documentation": "https://huggingface.co/docs/diffusers/main/training/distributed_inference", + }, + "fused_qkv": { + "label": "Fused QKV projections", + "kind": "runtime", + "implemented": False, + "profiles": ["nvidia-cuda"], + "platforms": ["linux"], + "automaticEligible": False, + "summary": "Experimental model-specific projection fusion.", + "documentation": "https://huggingface.co/docs/diffusers/main/optimization/fp16", + }, + } + + +def _normalized_platform() -> str: + if sys.platform.startswith("win"): + return "windows" + if sys.platform == "darwin": + return "macos" + return "linux" + + +def _package_version(distribution: str) -> str | None: + try: + return importlib.metadata.version(distribution) + except importlib.metadata.PackageNotFoundError: + return None + + +def _profile_id(runtime_profile: dict[str, Any] | None) -> str: + profile = runtime_profile if isinstance(runtime_profile, dict) else {} + return str( + profile.get("installed") or profile.get("installed_profile") or profile.get("installedProfile") or "unverified" + ) + + +def public_catalog( + *, + runtime_profile: dict[str, Any] | None = None, + hardware: dict[str, Any] | None = None, +) -> dict[str, Any]: + state = read_state() + profile_id = _profile_id(runtime_profile) + os_name = _normalized_platform() + hardware = hardware if isinstance(hardware, dict) else {} + torch_state = hardware.get("torch") if isinstance(hardware.get("torch"), dict) else {} + torch_version = str(torch_state.get("version") or "") + devices = hardware.get("devices") if isinstance(hardware.get("devices"), list) else [] + accelerators = hardware.get("accelerators") if isinstance(hardware.get("accelerators"), list) else [] + device_count = len(devices or accelerators) + enabled = {str(item) for item in state.get("enabledCapabilities") or []} + capabilities = [] + for capability_id, raw in _catalog().items(): + item = {"id": capability_id, **deepcopy(raw)} + implemented = item.get("implemented", True) is True + compatible = profile_id in item.get("profiles", []) and os_name in item.get("platforms", []) + reason = None + if profile_id not in item.get("profiles", []): + reason = f"Requires one of: {', '.join(item.get('profiles') or [])}." + elif os_name not in item.get("platforms", []): + reason = f"Not packaged for {os_name}." + if capability_id == "flash_attention_2": + if profile_id == "nvidia-cuda" and torch_version and not torch_version.startswith("2.8"): + compatible = False + reason = "The reviewed FlashAttention build is qualified only with MoDiff's PyTorch 2.8 CUDA profile." + elif profile_id == "amd-rocm-linux": + architectures = { + str(value) + for value in (hardware.get("amd_architectures") or hardware.get("amdArchitectures") or []) + } + supported = { + "gfx90a", + "gfx940", + "gfx941", + "gfx942", + "gfx950", + "gfx1100", + "gfx1101", + "gfx1200", + "gfx1201", + "gfx1151", + } + if architectures and not architectures.intersection(supported): + compatible = False + reason = "The detected AMD architecture is outside FlashAttention's reviewed ROCm set." + if capability_id == "context_parallel" and device_count < 2: + compatible = False + reason = "Requires at least two compatible accelerators." + if not implemented: + compatible = False + reason = "The upstream feature is documented, but MoDiff has not qualified a safe execution contract yet." + installed_version = _package_version(str(item.get("distribution"))) if item.get("distribution") else None + installed = bool(installed_version) if item.get("distribution") else implemented + can_enable = compatible and ( + item.get("kind") == "runtime" or (item.get("kind") in {"package", "profile", "external"} and installed) + ) + item.update( + { + "implemented": implemented, + "compatible": compatible, + "disabledReason": reason, + "enabled": capability_id in enabled, + "installed": installed, + "installedVersion": installed_version, + "canInstall": item.get("kind") == "package" and compatible, + "canEnable": can_enable, + "requiresRestart": item.get("kind") == "package", + } + ) + if item.get("kind") == "external": + item["disabledReason"] = ( + reason or "No generally safe app-managed wheel matches every supported ROCm device and Torch ABI." + ) + capabilities.append(item) + environments = [] + if ENVIRONMENTS_DIR.is_dir(): + for environment in sorted(ENVIRONMENTS_DIR.iterdir()): + if not environment.is_dir(): + continue + manifest = _read_json(environment / "manifest.json", {}) + validation = _read_json(environment / "validation.json", {}) + environments.append( + { + "id": environment.name, + "createdAt": manifest.get("createdAt"), + "capabilities": manifest.get("capabilities") or [], + "validation": validation, + "active": environment.name == state.get("activeEnvironmentId"), + } + ) + receipts = read_receipts().get("receipts") or [] + return { + "schemaVersion": CATALOG_SCHEMA_VERSION, + "state": state, + "profile": profile_id, + "platform": os_name, + "capabilities": capabilities, + "environments": environments, + "qualification": { + "receiptCount": len(receipts), + "qualifiedCount": sum(1 for item in receipts if item.get("status") == "qualified"), + "observedCount": sum(1 for item in receipts if item.get("status") == "observed"), + }, + } + + +def _uv_executable() -> str: + managed = MANAGED_ROOT / "tools" / "uv" + candidates = [managed / "uv.exe", managed / "uv", managed] + if managed.is_dir(): + candidates.extend(sorted(managed.rglob("uv.exe"))) + candidates.extend(sorted(managed.rglob("uv"))) + for candidate in candidates: + if candidate.is_file(): + return str(candidate) + uv = shutil.which("uv") + if uv: + return uv + raise RuntimeError("MoDiff's managed uv installer is unavailable. Run the normal MoDiff setup repair first.") + + +def _validation_environment(site_packages: Path) -> dict[str, str]: + environment = os.environ.copy() + current = environment.get("PYTHONPATH") + environment["PYTHONPATH"] = os.pathsep.join([str(site_packages), *([current] if current else [])]) + return environment + + +def _run_validation(site_packages: Path, capability_ids: list[str], timeout: int = 180) -> dict[str, Any]: + catalog = _catalog() + imports = [str(catalog[item]["importName"]) for item in capability_ids if catalog[item].get("importName")] + script = """ +import importlib +import json +import torch +import diffusers +results = {} +for name in json.loads(__IMPORTS__): + try: + module = importlib.import_module(name) + results[name] = {"ok": True, "version": str(getattr(module, "__version__", "unknown"))} + except Exception as exc: + results[name] = {"ok": False, "error": f"{type(exc).__name__}: {exc}"} +try: + from diffusers.models.attention_dispatch import AttentionBackendName + attention_backends = {str(item.value) for item in AttentionBackendName} +except Exception: + attention_backends = set() +required_attention = { + "hub_attention_kernels": "flash_hub", + "flash_attention_2": "flash", + "sage_attention": "sage", + "xformers": "xformers", + "aiter": "aiter", +} +for capability, backend in required_attention.items(): + if capability in json.loads(__CAPABILITIES__) and backend not in attention_backends: + results[f"diffusers:{backend}"] = { + "ok": False, + "error": f"Diffusers does not expose the required {backend!r} attention backend", + } +print(json.dumps({ + "torch": str(torch.__version__), + "diffusers": str(diffusers.__version__), + "cudaAvailable": bool(torch.cuda.is_available()), + "hip": str(getattr(torch.version, "hip", None)), + "imports": results, +})) +if not all(item["ok"] for item in results.values()): + raise SystemExit(2) +""" + script = script.replace("__IMPORTS__", repr(json.dumps(imports))).replace( + "__CAPABILITIES__", + repr(json.dumps(capability_ids)), + ) + started = time.monotonic() + try: + result = subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + timeout=timeout, + check=False, + env=_validation_environment(site_packages), + ) + except (OSError, subprocess.SubprocessError) as exc: + return {"status": "failed", "error": str(exc), "elapsedSeconds": time.monotonic() - started} + detail = None + stdout = result.stdout.strip() + if stdout: + try: + detail = json.loads(stdout.splitlines()[-1]) + except ValueError: + detail = {"stdout": stdout[-4000:]} + return { + "status": "passed" if result.returncode == 0 else "failed", + "returnCode": result.returncode, + "detail": detail, + "stderr": result.stderr.strip()[-4000:], + "elapsedSeconds": time.monotonic() - started, + "validatedAt": _now(), + } + + +def install_capability( + capability_id: str, + *, + runtime_profile: dict[str, Any] | None, + hardware: dict[str, Any] | None, + progress: Callable[[dict[str, Any]], None] | None = None, +) -> dict[str, Any]: + catalog = _catalog() + capability = catalog.get(capability_id) + if capability is None or capability.get("kind") != "package": + raise ValueError(f"{capability_id!r} is not an app-installable optimization package.") + status = public_catalog(runtime_profile=runtime_profile, hardware=hardware) + public_item = next(item for item in status["capabilities"] if item["id"] == capability_id) + if not public_item.get("compatible"): + raise RuntimeError( + public_item.get("disabledReason") or "This package is not compatible with the active runtime." + ) + + def report(phase: str, message: str, **extra: Any) -> None: + if progress: + progress({"phase": phase, "message": message, "updatedAt": _now(), **extra}) + + state = read_state() + environment_id = f"opt-{int(time.time())}-{uuid.uuid4().hex[:8]}" + staged = STAGING_DIR / environment_id + site_packages = staged / "site-packages" + staged.mkdir(parents=True, exist_ok=False) + active_site = _safe_environment_path(state.get("activeEnvironmentId")) + existing_capabilities: list[str] = [] + if active_site is not None: + report("copying", "Copying the last validated optional environment.") + shutil.copytree(active_site, site_packages) + active_manifest = _read_json(active_site.parent / "manifest.json", {}) + existing_capabilities = [str(item) for item in active_manifest.get("capabilities") or []] + else: + site_packages.mkdir() + + capabilities = list(dict.fromkeys([*existing_capabilities, capability_id])) + manifest = { + "schemaVersion": 1, + "id": environment_id, + "createdAt": _now(), + "basePython": sys.version.split()[0], + "baseExecutable": sys.executable, + "baseProfile": _profile_id(runtime_profile), + "capabilities": capabilities, + "buildPackages": capability.get("buildPackages") or [], + "requestedPackages": capability.get("packages") or [], + } + _atomic_json(staged / "manifest.json", manifest) + report("installing", f"Installing {capability['label']} into a staged environment.") + uv = _uv_executable() + base_command = [ + uv, + "pip", + "install", + "--python", + sys.executable, + "--target", + str(site_packages), + "--upgrade", + ] + install_environment = _validation_environment(site_packages) + target_bin = site_packages / ("Scripts" if os.name == "nt" else "bin") + ninja_bin = site_packages / "ninja" / "data" / "bin" + install_environment["PATH"] = os.pathsep.join( + [str(target_bin), str(ninja_bin), install_environment.get("PATH", "")] + ) + commands: list[list[str]] = [] + build_packages = [str(item) for item in capability.get("buildPackages") or []] + if build_packages: + # Source builds need their toolchain inside the isolated overlay before + # package metadata or extension compilation runs. + commands.append([*base_command, "--no-deps", *build_packages]) + command = list(base_command) + if capability.get("installMode") == "source": + command.append("--no-build-isolation") + if not capability.get("includeDependencies"): + # Optional ABI packages must use the already-qualified base Torch. + # Never let an isolated target resolver install a second Torch build. + command.append("--no-deps") + command.extend(str(item) for item in capability.get("packages") or []) + commands.append(command) + install_started = time.monotonic() + results = [] + for current_command in commands: + result = subprocess.run( + current_command, + capture_output=True, + text=True, + timeout=3600, + check=False, + env=install_environment, + ) + results.append(result) + if result.returncode != 0: + break + install_detail = { + "returnCode": result.returncode, + "elapsedSeconds": time.monotonic() - install_started, + "commands": len(results), + "stdout": "\n".join(item.stdout.strip() for item in results)[-8000:], + "stderr": "\n".join(item.stderr.strip() for item in results)[-8000:], + } + _atomic_json(staged / "install.json", install_detail) + if result.returncode != 0: + report("failed", f"{capability['label']} could not be staged.", error=install_detail["stderr"]) + raise RuntimeError(install_detail["stderr"] or install_detail["stdout"] or "Package installation failed.") + + report("validating", "Validating Torch, Diffusers, and the optional package in a fresh process.") + validation = _run_validation(site_packages, capabilities) + _atomic_json(staged / "validation.json", validation) + if validation.get("status") != "passed": + report("failed", "The staged environment failed validation. The active runtime was not changed.") + raise RuntimeError(validation.get("stderr") or "The staged package failed its compatibility probe.") + + destination = ENVIRONMENTS_DIR / environment_id + ENVIRONMENTS_DIR.mkdir(parents=True, exist_ok=True) + staged.replace(destination) + report("ready", "Validation passed. Activate the staged environment to restart MoDiff with it.") + return { + "environmentId": environment_id, + "capabilities": capabilities, + "validation": validation, + "requiresActivation": True, + "activeRuntimeChanged": False, + } + + +def activate_environment(environment_id: str) -> dict[str, Any]: + site_packages = _safe_environment_path(environment_id) + if site_packages is None: + raise ValueError("Only a validated staged environment can be activated.") + state = read_state() + if state.get("activeEnvironmentId") == environment_id: + return {"state": state, "restartRequired": False} + state["previousEnvironmentId"] = state.get("activeEnvironmentId") + state["activeEnvironmentId"] = environment_id + state = _write_state(state) + return {"state": state, "restartRequired": True} + + +def rollback_environment() -> dict[str, Any]: + state = read_state() + previous = state.get("previousEnvironmentId") + if previous is not None and _safe_environment_path(previous) is None: + raise RuntimeError("The previous optional environment is no longer available.") + current = state.get("activeEnvironmentId") + state["activeEnvironmentId"] = previous + state["previousEnvironmentId"] = current + state = _write_state(state) + return {"state": state, "restartRequired": current != previous} + + +def set_capability_enabled(capability_id: str, enabled: bool) -> dict[str, Any]: + if capability_id not in _catalog(): + raise ValueError(f"Unknown optimization capability {capability_id!r}.") + state = read_state() + values = {str(item) for item in state.get("enabledCapabilities") or []} + if enabled: + values.add(capability_id) + else: + values.discard(capability_id) + state["enabledCapabilities"] = sorted(values) + return _write_state(state) + + +def read_receipts() -> dict[str, Any]: + value = _read_json( + RECEIPTS_PATH, + {"schemaVersion": RECEIPT_SCHEMA_VERSION, "receipts": [], "updatedAt": _now()}, + ) + value.setdefault("receipts", []) + return value + + +def _stable_hash(value: Any) -> str: + encoded = json.dumps(value, sort_keys=True, separators=(",", ":"), default=str).encode("utf-8") + return hashlib.sha256(encoded).hexdigest() + + +def workload_key_for_form(form: dict[str, Any] | None) -> str: + """Hash result-affecting workload fields while excluding runtime tuning.""" + value = form if isinstance(form, dict) else {} + excluded = { + "resourceMode", + "resourcePreference", + "dtype", + "quantizationMode", + "quantizedComponents", + "device", + "deviceMap", + "autoOffload", + "offloadMode", + "attentionBackend", + "attention_backend", + "regionalCompile", + "regional_compile", + "denoiserCache", + "denoiser_cache", + "channelsLast", + "channels_last", + "layerwiseCasting", + "layerwise_casting", + } + return f"workload-{_stable_hash({key: value[key] for key in sorted(value) if key not in excluded})}" + + +def optimization_selections_from_graph(graph: dict[str, Any] | None) -> list[dict[str, Any]]: + """Extract explicit non-default runtime selections from an API graph.""" + found: dict[str, dict[str, Any]] = {} + + def visit(value: Any) -> None: + if isinstance(value, dict): + attention = value.get("attention_backend") + if isinstance(attention, str) and attention not in {"", "auto", "native"}: + capability = { + "flash": "flash_attention_2", + "flash_hub": "hub_attention_kernels", + "flash_varlen": "flash_attention_2", + "flash_varlen_hub": "hub_attention_kernels", + "flash_4_hub": "hub_attention_kernels", + "_flash_3": "flash_attention_2", + "_flash_varlen_3": "flash_attention_2", + "_flash_3_hub": "hub_attention_kernels", + "_flash_3_varlen_hub": "hub_attention_kernels", + "sage": "sage_attention", + "sage_hub": "hub_attention_kernels", + "xformers": "xformers", + "aiter": "aiter", + }.get(attention) + if capability: + found[capability] = { + "capabilityId": capability, + "attentionBackend": attention, + } + if value.get("regional_compile") is True: + found["regional_compile"] = { + "capabilityId": "regional_compile", + "regionalCompile": True, + } + if value.get("layerwise_casting") is True: + found["layerwise_casting"] = { + "capabilityId": "layerwise_casting", + "layerwiseCasting": True, + } + if value.get("channels_last") is True: + found["channels_last"] = { + "capabilityId": "channels_last", + "channelsLast": True, + } + cache = value.get("denoiser_cache") + if isinstance(cache, str) and cache not in {"", "none"}: + found[f"denoiser_cache:{cache}"] = { + "capabilityId": "denoiser_cache", + "denoiserCache": cache, + } + quantization = value.get("quantization_mode") or value.get("backend") + if isinstance(quantization, str) and quantization not in {"", "none"}: + capability = ( + "torchao" + if quantization.startswith("torchao") + else "optimum_quanto" + if quantization.startswith("quanto") + else "bitsandbytes" + if quantization.startswith("bnb") + else None + ) + if capability: + found[f"{capability}:{quantization}"] = { + "capabilityId": capability, + "quantizationMode": quantization, + } + for child in value.values(): + visit(child) + elif isinstance(value, list): + for child in value: + visit(child) + + visit(graph) + return list(found.values()) + + +def record_probe_receipt( + *, + capability_id: str, + runtime_fingerprint: Any, + result: dict[str, Any], + environment_id: str | None = None, +) -> dict[str, Any]: + if capability_id not in _catalog(): + raise ValueError(f"Unknown optimization capability {capability_id!r}.") + receipt = { + "id": f"probe-{uuid.uuid4().hex}", + "schemaVersion": RECEIPT_SCHEMA_VERSION, + "kind": "compatibility_probe", + "status": "probe_passed" if result.get("status") == "passed" else "probe_failed", + "capabilityId": capability_id, + "environmentId": environment_id or read_state().get("activeEnvironmentId"), + "runtimeFingerprintHash": _stable_hash(runtime_fingerprint), + "result": deepcopy(result), + "createdAt": _now(), + # Import/synthetic probes never authorize Auto for a model workload. + "autoEligible": False, + } + with _STATE_LOCK: + document = read_receipts() + document["receipts"] = [receipt, *document.get("receipts", [])][:500] + document["updatedAt"] = _now() + _atomic_json(RECEIPTS_PATH, document) + return receipt + + +def probe_capability(capability_id: str, *, runtime_fingerprint: Any) -> dict[str, Any]: + capability = _catalog().get(capability_id) + if capability is None: + raise ValueError(f"Unknown optimization capability {capability_id!r}.") + state = read_state() + active_site = _safe_environment_path(state.get("activeEnvironmentId")) + if capability.get("importName"): + validation = _run_validation(active_site or Path(), [capability_id]) + else: + script = """ +import json +import torch +from diffusers.hooks import FirstBlockCacheConfig, apply_layerwise_casting +capability = __CAPABILITY__ +checks = { + "regional_compile": callable(getattr(torch, "compile", None)), + "denoiser_cache": FirstBlockCacheConfig is not None, + "layerwise_casting": callable(apply_layerwise_casting) and hasattr(torch, "float8_e4m3fn"), + "channels_last": hasattr(torch, "channels_last"), +} +supported = checks.get(capability, False) +print(json.dumps({ + "torch": str(torch.__version__), + "compileAvailable": callable(getattr(torch, "compile", None)), + "cudaAvailable": bool(torch.cuda.is_available()), + "deviceCount": int(torch.cuda.device_count()) if torch.cuda.is_available() else 0, + "capability": capability, + "supported": supported, +})) +if not supported: + raise SystemExit(2) +""".replace("__CAPABILITY__", repr(capability_id)) + started = time.monotonic() + result = subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + timeout=120, + check=False, + env=os.environ.copy(), + ) + detail = None + try: + detail = json.loads(result.stdout.strip().splitlines()[-1]) if result.stdout.strip() else None + except ValueError: + detail = {"stdout": result.stdout.strip()[-4000:]} + validation = { + "status": "passed" if result.returncode == 0 else "failed", + "returnCode": result.returncode, + "detail": detail, + "stderr": result.stderr.strip()[-4000:], + "elapsedSeconds": time.monotonic() - started, + "validatedAt": _now(), + } + return record_probe_receipt( + capability_id=capability_id, + runtime_fingerprint=runtime_fingerprint, + result=validation, + environment_id=state.get("activeEnvironmentId"), + ) + + +def record_workload_observation( + *, + capability_id: str, + runtime_fingerprint: Any, + model_type: str, + mode: str, + artifact: str, + workload_key: str, + selection: dict[str, Any], + measurement: dict[str, Any], +) -> dict[str, Any]: + if capability_id not in _catalog(): + raise ValueError(f"Unknown optimization capability {capability_id!r}.") + receipt = { + "id": f"workload-{uuid.uuid4().hex}", + "schemaVersion": RECEIPT_SCHEMA_VERSION, + "kind": "workload", + "status": "observed", + "capabilityId": capability_id, + "environmentId": read_state().get("activeEnvironmentId"), + "runtimeFingerprintHash": _stable_hash(runtime_fingerprint), + "modelType": str(model_type), + "mode": str(mode), + "artifact": str(artifact), + "workloadKey": str(workload_key), + "selection": deepcopy(selection), + "measurement": deepcopy(measurement), + "createdAt": _now(), + "autoEligible": False, + } + with _STATE_LOCK: + document = read_receipts() + document["receipts"] = [receipt, *document.get("receipts", [])][:500] + document["updatedAt"] = _now() + _atomic_json(RECEIPTS_PATH, document) + return receipt + + +def record_workload_baseline( + *, + runtime_fingerprint: Any, + model_type: str, + mode: str, + artifact: str, + workload_key: str, + measurement: dict[str, Any], +) -> dict[str, Any]: + receipt = { + "id": f"baseline-{uuid.uuid4().hex}", + "schemaVersion": RECEIPT_SCHEMA_VERSION, + "kind": "workload_baseline", + "status": "observed", + "runtimeFingerprintHash": _stable_hash(runtime_fingerprint), + "modelType": str(model_type), + "mode": str(mode), + "artifact": str(artifact), + "workloadKey": str(workload_key), + "measurement": deepcopy(measurement), + "createdAt": _now(), + "autoEligible": False, + } + with _STATE_LOCK: + document = read_receipts() + document["receipts"] = [receipt, *document.get("receipts", [])][:500] + document["updatedAt"] = _now() + _atomic_json(RECEIPTS_PATH, document) + return receipt + + +def _improvement_evidence(baseline: dict[str, Any], optimized: dict[str, Any]) -> dict[str, Any]: + evidence: dict[str, Any] = {} + baseline_seconds = baseline.get("elapsedSeconds") + optimized_seconds = optimized.get("elapsedSeconds") + if ( + isinstance(baseline_seconds, (int, float)) + and baseline_seconds > 0 + and isinstance(optimized_seconds, (int, float)) + ): + evidence["elapsedRatio"] = float(optimized_seconds) / float(baseline_seconds) + baseline_memory = baseline.get("peakAllocatedBytes") + optimized_memory = optimized.get("peakAllocatedBytes") + if isinstance(baseline_memory, int) and baseline_memory > 0 and isinstance(optimized_memory, int): + evidence["peakMemoryRatio"] = float(optimized_memory) / float(baseline_memory) + evidence["improved"] = bool( + evidence.get("elapsedRatio", 1.0) <= 0.98 or evidence.get("peakMemoryRatio", 1.0) <= 0.98 + ) + return evidence + + +def qualify_receipt(receipt_id: str, *, output_reviewed: bool) -> dict[str, Any]: + if not output_reviewed: + raise ValueError("A workload receipt requires explicit output review before Auto qualification.") + with _STATE_LOCK: + document = read_receipts() + receipt = next((item for item in document.get("receipts", []) if item.get("id") == receipt_id), None) + if not receipt or receipt.get("kind") != "workload" or receipt.get("status") != "observed": + raise ValueError("Only an observed workload receipt can be qualified.") + capability = _catalog().get(str(receipt.get("capabilityId"))) or {} + if not capability.get("automaticEligible"): + raise ValueError("This experimental capability is not eligible for automatic selection.") + baseline = next( + ( + item + for item in document.get("receipts", []) + if item.get("kind") == "workload_baseline" + and item.get("status") == "observed" + and item.get("runtimeFingerprintHash") == receipt.get("runtimeFingerprintHash") + and item.get("modelType") == receipt.get("modelType") + and item.get("mode") == receipt.get("mode") + and item.get("artifact") == receipt.get("artifact") + and item.get("workloadKey") == receipt.get("workloadKey") + ), + None, + ) + if baseline is None: + raise ValueError( + "Run this unchanged workload once without optional optimizations to record a baseline first." + ) + evidence = _improvement_evidence( + baseline.get("measurement") if isinstance(baseline.get("measurement"), dict) else {}, + receipt.get("measurement") if isinstance(receipt.get("measurement"), dict) else {}, + ) + if not evidence.get("improved"): + raise ValueError( + "This optimization did not improve measured runtime or peak accelerator memory by at least 2%." + ) + receipt["status"] = "qualified" + receipt["autoEligible"] = True + receipt["qualifiedAt"] = _now() + receipt["baselineReceiptId"] = baseline.get("id") + receipt["benchmarkEvidence"] = evidence + document["updatedAt"] = _now() + _atomic_json(RECEIPTS_PATH, document) + return deepcopy(receipt) + + +def qualified_auto_overrides( + *, + runtime_fingerprint: Any, + model_type: str, + mode: str, + artifact: str, + workload_key: str | None = None, +) -> dict[str, Any]: + """Return only exact, enabled, workload-qualified optimization selections.""" + state = read_state() + enabled = {str(item) for item in state.get("enabledCapabilities") or []} + fingerprint_hash = _stable_hash(runtime_fingerprint) + combined: dict[str, Any] = {} + for receipt in read_receipts().get("receipts", []): + if ( + receipt.get("status") != "qualified" + or receipt.get("autoEligible") is not True + or receipt.get("capabilityId") not in enabled + or receipt.get("environmentId") != state.get("activeEnvironmentId") + or receipt.get("runtimeFingerprintHash") != fingerprint_hash + or receipt.get("modelType") != str(model_type) + or receipt.get("mode") != str(mode) + or receipt.get("artifact") != str(artifact) + or (workload_key and receipt.get("workloadKey") != workload_key) + ): + continue + selection = receipt.get("selection") + if not isinstance(selection, dict): + continue + # Receipts are newest-first. Keep the newest qualified value when + # several capabilities expose independent runtime fields. + for key, value in selection.items(): + combined.setdefault(str(key), deepcopy(value)) + return combined + + +def delete_environment(environment_id: str) -> None: + state = read_state() + if environment_id in {state.get("activeEnvironmentId"), state.get("previousEnvironmentId")}: + raise RuntimeError("Active and rollback environments cannot be deleted.") + path = (ENVIRONMENTS_DIR / environment_id).resolve() + path.relative_to(ENVIRONMENTS_DIR.resolve()) + if path.is_dir(): + shutil.rmtree(path) diff --git a/modiff/path_identifiers.py b/modiff/path_identifiers.py new file mode 100644 index 0000000..4813950 --- /dev/null +++ b/modiff/path_identifiers.py @@ -0,0 +1,154 @@ +"""Portable identifiers for files managed by MoDiff's configured roots. + +The HTTP API cannot safely use ``Path.relative_to(work_dir)`` for uploads +stored under a separately configured data directory. Besides failing for +separate roots (and for separate drives on Windows), falling back to an +absolute path would disclose a host path and make saved workflows +machine-specific. ``@data/`` is therefore the public identifier for +files rooted at ``data_dir``. +""" + +from __future__ import annotations + +import os +from pathlib import Path, PurePosixPath + + +DATA_PATH_PREFIX = "@data" + + +def _resolved_root(value: str | os.PathLike[str]) -> Path: + return Path(value).expanduser().resolve(strict=False) + + +def is_data_path_identifier(value: object) -> bool: + try: + raw = os.fspath(value) + except TypeError: + return False + return isinstance(raw, str) and (raw == DATA_PATH_PREFIX or raw.startswith(f"{DATA_PATH_PREFIX}/")) + + +def _data_identifier_parts(value: str | os.PathLike[str]) -> tuple[str, ...]: + raw = os.fspath(value) + if not isinstance(raw, str) or not is_data_path_identifier(raw): + raise ValueError("Not a MoDiff data-path identifier.") + if "\x00" in raw or "\\" in raw: + raise ValueError("The data-path identifier is invalid.") + relative = raw.removeprefix(f"{DATA_PATH_PREFIX}/") if raw != DATA_PATH_PREFIX else "" + if not relative: + return () + raw_parts = relative.split("/") + if any(part in {"", ".", ".."} or ":" in part for part in raw_parts): + raise ValueError("The data-path identifier contains an invalid segment.") + pure = PurePosixPath(relative) + if pure.is_absolute() or pure.parts != tuple(raw_parts): + raise ValueError("The data-path identifier is invalid.") + return tuple(raw_parts) + + +def data_path_identifier( + path: str | os.PathLike[str], + data_root: str | os.PathLike[str], +) -> str: + """Return a host-independent identifier for a path contained by data_root.""" + + root = _resolved_root(data_root) + candidate = Path(path).expanduser().resolve(strict=False) + try: + relative = candidate.relative_to(root) + except ValueError as exc: + raise ValueError("The path is outside the configured MoDiff data directory.") from exc + if relative == Path("."): + return DATA_PATH_PREFIX + identifier = f"{DATA_PATH_PREFIX}/{PurePosixPath(*relative.parts).as_posix()}" + _data_identifier_parts(identifier) + return identifier + + +def resolve_data_path_identifier( + value: str | os.PathLike[str], + data_root: str | os.PathLike[str], +) -> Path: + """Resolve an ``@data`` identifier and reject traversal or symlink escape.""" + + parts = _data_identifier_parts(value) + root = _resolved_root(data_root) + candidate = root.joinpath(*parts).resolve(strict=False) + try: + candidate.relative_to(root) + except ValueError as exc: + raise ValueError("The data-path identifier escapes the configured data directory.") from exc + return candidate + + +def resolve_managed_path_identifier( + value: str | os.PathLike[str], + *, + work_root: str | os.PathLike[str], + data_root: str | os.PathLike[str], +) -> Path | None: + """Resolve an HTTP path value inside the configured work or data root. + + Existing work-root-relative values (including the historical ``data/...`` + form when data lives under the workspace) remain valid. Absolute values + are accepted only when already contained by a configured root, which keeps + old local records readable without issuing new absolute identifiers. + """ + + try: + raw = os.fspath(value) + except TypeError: + return None + if not isinstance(raw, str) or not raw or "\x00" in raw: + return None + if raw == DATA_PATH_PREFIX or raw.startswith(f"{DATA_PATH_PREFIX}/"): + try: + return resolve_data_path_identifier(raw, data_root) + except ValueError: + return None + if raw.startswith(DATA_PATH_PREFIX): + return None + + work = _resolved_root(work_root) + data = _resolved_root(data_root) + candidate = Path(raw).expanduser() + if not candidate.is_absolute(): + candidate = work / candidate + candidate = candidate.resolve(strict=False) + for root in (work, data): + try: + candidate.relative_to(root) + return candidate + except ValueError: + continue + return None + + +def resolve_runtime_input_path( + value: str | os.PathLike[str], + *, + work_root: str | os.PathLike[str] | None = None, + data_root: str | os.PathLike[str] | None = None, +) -> Path: + """Resolve a node path while preserving legacy local-path behavior. + + Only the server-issued ``@data`` namespace is containment constrained here. + Existing explicit absolute paths and work-root-relative graph values retain + their historical trusted-local semantics. + """ + + if work_root is None or data_root is None: + from modiff.config import CONFIG + + work_root = work_root or CONFIG.paths["work_dir"] + data_root = data_root or CONFIG.paths["data"] + raw = os.fspath(value) + if is_data_path_identifier(raw): + return resolve_data_path_identifier(raw, data_root) + if isinstance(raw, str) and raw.startswith(DATA_PATH_PREFIX): + raise ValueError("The MoDiff data-path identifier is invalid.") + path = Path(raw).expanduser() + if not path.is_absolute(): + path = Path(work_root).expanduser() / path + return path diff --git a/modiff/preflight.py b/modiff/preflight.py index 92a3a7e..fb25ddb 100644 --- a/modiff/preflight.py +++ b/modiff/preflight.py @@ -11,13 +11,13 @@ import time from modiff.hardware import get_hardware_snapshot, legacy_torch_status +from modiff.runtime_profile import runtime_profile PACKAGE_CHECKS = { "required": [ ("aiohttp", "aiohttp"), ("aiohttp_cors", "aiohttp-cors"), - ("aiofiles", "aiofiles"), ("nanoid", "nanoid"), ("torch", "torch"), ("diffusers", "diffusers"), @@ -44,8 +44,6 @@ ("xformers", "xformers"), ("av", "av"), ("spandrel", "spandrel"), - ("transparent_background", "transparent-background"), - ("rembg", "rembg"), ("nunchaku", "nunchaku"), ("torchao", "torchao"), ], @@ -53,22 +51,37 @@ CANONICAL_ENTRYPOINT = "python -m modiff.preflight" +# These APIs are part of MoDiff's pinned Diffusers contract rather than +# optional feature detection. Treating an older release wheel as healthy can +# otherwise let the app start successfully and fail only after a long model +# load, as happened with ACE-Step LoRA workflows. +REQUIRED_RUNTIME_APIS = { + "diffusers": ( + ("AceStepPipeline", "load_lora_weights"), + ("AceStepPipeline", "set_adapters"), + ("AceStepPipeline", "unload_lora_weights"), + ), +} + def setup_guidance(root): return { - "preferredCommand": "uv sync", - "macosCommand": "uv sync --extra apple-silicon", - "cudaCommand": "uv sync --extra cuda", - "pipCommand": "python -m pip install -U -r requirements.txt", - "pipMacosCommand": "python -m pip install -U -r requirements_macos.txt", - "pipExtrasCommand": "python -m pip install -U -r requirements_extras.txt", - "pipQuantCommand": "python -m pip install -U -r requirements_quant.txt", + "preferredCommand": "./install.sh", + "macosCommand": "./install.sh --accelerator mps", + "cudaCommand": "./install.sh --accelerator nvidia", + "intelCommand": "./install.sh --accelerator intel", + "windowsCommand": r".\install.ps1 -Accelerator auto", + "repairCommand": ( + r".\install.ps1 -Accelerator auto -Repair" + if os.name == "nt" + else "./install.sh --accelerator auto --repair" + ), "projectRoot": str(root), "notes": [ "Run commands from the project root.", - "Use uv run main.py after uv sync, or python main.py after activating a pip-managed environment.", - "On Apple Silicon macOS, use the apple-silicon profile so torch resolves from normal PyPI/MPS-capable wheels.", - "CUDA acceleration extras are intended for Linux/Windows GPU installs and are not part of the macOS setup path.", + "Use the managed installer so PyTorch matches the selected accelerator profile.", + "Do not run a generic dependency sync inside a managed accelerator environment.", + "Use ./install.sh --repair (or install.ps1 --repair on Windows) when the installed profile no longer matches the host.", ], } @@ -166,6 +179,19 @@ def package_status(module_name, distribution_name, import_check=True): status["available"] = True status["import_ms"] = round((time.perf_counter() - started) * 1000) status["version"] = getattr(module, "__version__", status.get("version")) + missing_apis = [] + for owner_name, attribute_name in REQUIRED_RUNTIME_APIS.get(module_name, ()): + owner = getattr(module, owner_name, None) + if owner is None or not callable(getattr(owner, attribute_name, None)): + missing_apis.append(f"{owner_name}.{attribute_name}") + if missing_apis: + status["available"] = False + status["contractMissing"] = missing_apis + status["error"] = ( + "Installed package does not satisfy MoDiff's pinned runtime contract: " + + ", ".join(missing_apis) + + ". Repair the managed environment before starting MoDiff." + ) except Exception as error: status["error"] = str(error) @@ -196,6 +222,7 @@ def build_report(args): missing_required_distributions.append(distribution_name) hardware = get_hardware_snapshot(data_dir, refresh=True) + profile = runtime_profile(hardware, venv=Path(sys.prefix)) torch_status = next((item for item in packages["required"] if item["module"] == "torch"), None) if torch_status is not None: torch_status.update(legacy_torch_status(hardware)) @@ -217,6 +244,11 @@ def build_report(args): issues.append("MoDiff requires Python 3.12 or newer.") if missing_required: issues.append(f"Missing required packages: {', '.join(missing_required)}") + issues.extend( + issue["message"] + for issue in profile.get("issues", []) + if issue.get("severity") == "error" and issue.get("message") + ) return { "error": bool(issues), @@ -251,6 +283,7 @@ def build_report(args): "paths": paths, "packages": packages, "hardware": hardware, + "runtimeProfile": profile, "missingRequiredPackages": missing_required, "missingRequiredDistributions": missing_required_distributions, } @@ -265,24 +298,27 @@ def print_human(report): torch = next((item for item in report["packages"]["required"] if item["module"] == "torch"), None) if torch: cuda = "available" if torch.get("cuda_available") else "not available" + xpu = "available" if torch.get("xpu_available") else "not available" mps = "available" if torch.get("mps_available") else "not available" device = f" ({torch.get('cuda_device_name')})" if torch.get("cuda_device_name") else "" - print(f"Torch: {torch.get('version', 'unknown')} CUDA {cuda}{device}; MPS {mps}") + print(f"Torch: {torch.get('version', 'unknown')} CUDA {cuda}{device}; XPU {xpu}; MPS {mps}") + runtime_profile_status = report.get("runtimeProfile", {}) + print(f"Runtime profile: {runtime_profile_status.get('status', 'unknown')}") if report["issues"]: print("Issues:") for issue in report["issues"]: print(f"- {issue}") - if report["missingRequiredPackages"]: + if report["missingRequiredPackages"] or runtime_profile_status.get("repair_required"): setup = report["setup"] if sys.platform == "darwin": preferred_command = setup["macosCommand"] - pip_command = setup["pipMacosCommand"] + elif os.name == "nt": + preferred_command = setup["windowsCommand"] else: preferred_command = setup["preferredCommand"] - pip_command = setup["pipCommand"] print("Install guidance:") print(f"- Preferred: {preferred_command}") - print(f"- Pip fallback: {pip_command}") + print(f"- Repair: {runtime_profile_status.get('repair_command') or setup['repairCommand']}") print(f"- Recheck: {report['namespace']['canonicalPreflightCommand']} --check-port {report['server']['port']}") print(f"Namespace: use {report['namespace']['canonicalPreflightCommand']}") diff --git a/modiff/runtime_profile.py b/modiff/runtime_profile.py index 0409641..a6f81b4 100644 --- a/modiff/runtime_profile.py +++ b/modiff/runtime_profile.py @@ -1,14 +1,20 @@ """Resolve and validate the managed accelerator environment.""" + from __future__ import annotations import hashlib +import importlib import json import os import platform +from functools import lru_cache from pathlib import Path from typing import Any MANIFEST_PATH = Path(__file__).with_name("compatibility") / "accelerators.v1.json" +PROJECT_ROOT = MANIFEST_PATH.parents[2] +PROJECT_METADATA_PATH = PROJECT_ROOT / "pyproject.toml" +RUNTIME_CONTRACT_SCHEMA = 2 STATE_NAME = "modiff-profile.json" INSTALL_JOURNAL_PATH = MANIFEST_PATH.parents[2] / ".modiff" / "install-state.json" @@ -52,10 +58,24 @@ def read_install_journal() -> dict[str, Any] | None: for step in value.get("steps", []): if not isinstance(step, dict): continue - normalized = {key: step.get(key) for key in ( - "id", "title", "phase", "status", "explanation", "automatic", "requires_admin", - "requires_reboot", "command", "verification", "documentation_url", "failure_help", - ) if step.get(key) is not None} + normalized = { + key: step.get(key) + for key in ( + "id", + "title", + "phase", + "status", + "explanation", + "automatic", + "requires_admin", + "requires_reboot", + "command", + "verification", + "documentation_url", + "failure_help", + ) + if step.get(key) is not None + } phase_status = value.get("phases", {}).get(step.get("phase"), {}).get("status") if phase_status and normalized.get("status") != "skipped": normalized["status"] = phase_status @@ -81,12 +101,106 @@ def profile_for_installed_torch(torch_state: dict[str, Any], os_name: str | None return "nvidia-cuda" if torch_state.get("mps_built"): return "apple-mps" + if torch_state.get("xpu_available"): + return "intel-xpu" if torch_state.get("available"): return "cpu" return None -def runtime_profile(hardware: dict[str, Any], requested: str | None = None, venv: Path | None = None) -> dict[str, Any]: +def _runtime_contract_status( + saved: dict[str, Any] | None, + *, + selected: str | None, + spec: dict[str, Any] | None, +) -> dict[str, Any]: + """Compare installed profile state with the current reviewed inputs.""" + + if not saved: + return { + "status": "unmanaged", + "verified": False, + "matches": None, + "requirements": spec.get("requirements") if spec else None, + } + + requirement_value = spec.get("requirements") if spec else None + if not selected or not isinstance(requirement_value, str) or not requirement_value: + return { + "status": "unavailable", + "verified": False, + "matches": False, + "requirements": requirement_value, + } + + requirement = PROJECT_ROOT / requirement_value + try: + current_digest = lock_digest( + requirement, + contract_paths=runtime_contract_paths(requirement), + profile=selected, + ) + except OSError: + return { + "status": "unavailable", + "verified": False, + "matches": False, + "requirements": requirement_value, + } + + saved_digest = saved.get("lock_digest") + verified = ( + isinstance(saved_digest, str) + and len(saved_digest) == 64 + and all(character in "0123456789abcdef" for character in saved_digest) + ) + if saved.get("runtime_contract_schema") != RUNTIME_CONTRACT_SCHEMA: + legacy_files = saved.get("runtime_contract_files") + if verified and isinstance(legacy_files, list) and legacy_files: + return { + "status": "legacy", + "verified": False, + "matches": None, + "requirements": requirement_value, + "installed_digest": saved_digest, + "current_digest": current_digest, + } + matches = verified and saved_digest == current_digest + return { + "status": "verified" if matches else ("drifted" if verified else "unverified"), + "verified": verified, + "matches": matches, + "requirements": requirement_value, + "installed_digest": saved_digest if verified else None, + "current_digest": current_digest, + } + + +@lru_cache(maxsize=8) +def _device_tensor_probe(profile: str, torch_version: str | None) -> dict[str, Any]: + del torch_version + try: + torch = importlib.import_module("torch") + if profile in {"nvidia-cuda", "amd-rocm-linux", "amd-pytorch-windows"}: + device = "cuda:0" + elif profile == "apple-mps": + device = "mps:0" + elif profile == "intel-xpu": + device = "xpu:0" + else: + device = "cpu" + value = torch.ones(1, device=device) + observed = float(value.detach().cpu().item()) + if observed != 1.0: + raise RuntimeError(f"device tensor returned {observed!r}") + return {"ready": True, "device": device, "message": None} + except Exception as exc: + return {"ready": False, "device": None, "message": str(exc) or type(exc).__name__} + + +def runtime_profile( + hardware: dict[str, Any], requested: str | None = None, venv: Path | None = None +) -> dict[str, Any]: manifest = load_manifest() saved = read_state(venv) requested = requested or (saved or {}).get("profile") @@ -94,43 +208,209 @@ def runtime_profile(hardware: dict[str, Any], requested: str | None = None, venv detected = hardware.get("detected_profile") or installed or "cpu" issues: list[dict[str, str]] = [] if not saved: - issues.append({"code": "profile-unverified", "severity": "warning", "message": "This environment predates managed accelerator profiles."}) + issues.append( + { + "code": "profile-unverified", + "severity": "warning", + "message": "This environment predates managed accelerator profiles.", + } + ) if requested and installed and requested != installed: - issues.append({"code": "profile-mismatch", "severity": "error", "message": f"Requested {requested}, but installed Torch resolves to {installed}."}) + issues.append( + { + "code": "profile-mismatch", + "severity": "error", + "message": f"Requested {requested}, but installed Torch resolves to {installed}.", + } + ) if detected and installed and detected != installed and detected != "cpu": - issues.append({"code": "hardware-profile-mismatch", "severity": "error", "message": f"Detected hardware resolves to {detected}, but installed Torch resolves to {installed}."}) + issues.append( + { + "code": "hardware-profile-mismatch", + "severity": "error", + "message": f"Detected hardware resolves to {detected}, but installed Torch resolves to {installed}.", + } + ) selected = requested or installed or detected spec = manifest["profiles"].get(selected) + contract = _runtime_contract_status(saved, selected=selected, spec=spec) + if saved and contract["status"] == "unavailable": + issues.append( + { + "code": "runtime-contract-unavailable", + "severity": "error", + "message": ( + f"MoDiff cannot verify the files that define the managed {selected or 'runtime'} environment. " + "Repair it before running workflows." + ), + } + ) + elif saved and contract["status"] == "unverified": + issues.append( + { + "code": "runtime-contract-unverified", + "severity": "error", + "message": ( + "This managed environment has no verifiable installation record. " + "Repair it before running workflows." + ), + } + ) + elif saved and contract["status"] == "drifted": + issues.append( + { + "code": "runtime-contract-drift", + "severity": "error", + "message": ( + f"The managed {selected} environment is out of date for this MoDiff checkout. " + "Repair it before running workflows." + ), + } + ) + elif saved and contract["status"] == "legacy": + issues.append( + { + "code": "runtime-contract-legacy", + "severity": "warning", + "message": ( + "This environment has a legacy installation record. " + "It remains runnable; repair it when convenient to upgrade future integrity checks." + ), + } + ) if spec and (normalized_os() not in spec["os"] or normalized_arch() not in spec["architectures"]): - issues.append({"code": "unsupported-platform", "severity": "error", "message": f"{selected} is not qualified on this OS/architecture."}) + issues.append( + { + "code": "unsupported-platform", + "severity": "error", + "message": f"{selected} is not qualified on this OS/architecture.", + } + ) torch_state = hardware.get("torch", {}) backend_usable = bool(torch_state.get("available")) if installed in {"nvidia-cuda", "amd-rocm-linux", "amd-pytorch-windows"}: backend_usable = backend_usable and bool(torch_state.get("cuda_available")) elif installed == "apple-mps": backend_usable = backend_usable and bool(torch_state.get("mps_available")) + elif installed == "intel-xpu": + backend_usable = backend_usable and bool(torch_state.get("xpu_available")) if installed and not backend_usable: - issues.append({"code": "installed-backend-unavailable", "severity": "error", "message": f"Installed {installed} Torch cannot execute on its accelerator."}) + issues.append( + { + "code": "installed-backend-unavailable", + "severity": "error", + "message": f"Installed {installed} Torch cannot execute on its accelerator.", + } + ) + device_validation = None + if installed and backend_usable: + device_validation = _device_tensor_probe(installed, str(torch_state.get("version") or "")) + if not device_validation["ready"]: + issues.append( + { + "code": "device-tensor-failed", + "severity": "error", + "message": f"Installed {installed} Torch failed a device tensor: {device_validation['message']}", + } + ) if saved and selected == "amd-rocm-linux": - if not str(torch_state.get("version") or "").startswith("2.9.1+rocm7.2") or not str(torch_state.get("hip_version") or "").startswith("7.2"): - issues.append({"code": "profile-version-mismatch", "severity": "error", "message": "The managed AMD profile requires Torch 2.9.1 built for ROCm 7.2."}) + if not str(torch_state.get("version") or "").startswith("2.9.1+rocm7.2") or not str( + torch_state.get("hip_version") or "" + ).startswith("7.2"): + issues.append( + { + "code": "profile-version-mismatch", + "severity": "error", + "message": "The managed AMD profile requires Torch 2.9.1 built for ROCm 7.2.", + } + ) + if saved and selected == "intel-xpu" and not str(torch_state.get("version") or "").startswith("2.12.1"): + issues.append( + { + "code": "profile-version-mismatch", + "severity": "error", + "message": "The managed Intel XPU profile requires Torch 2.12.1 from the reviewed XPU index.", + } + ) ready = backend_usable and not any(i["severity"] == "error" for i in issues) - repair_accelerator = {"nvidia-cuda": "nvidia", "amd-rocm-linux": "amd", "amd-pytorch-windows": "amd", "apple-mps": "mps"}.get(selected, "cpu") + repair_accelerator = { + "nvidia-cuda": "nvidia", + "amd-rocm-linux": "amd", + "amd-pytorch-windows": "amd", + "apple-mps": "mps", + "intel-xpu": "intel", + }.get(selected, "cpu") + experimental_repair = (saved or {}).get("support_tier") == "experimental" + if normalized_os() == "windows": + repair_command = f".\\install.ps1 -Accelerator {repair_accelerator} -Repair" + if experimental_repair: + repair_command += " -AllowExperimental" + else: + repair_command = f"./install.sh --accelerator {repair_accelerator} --repair" + if experimental_repair: + repair_command += " --allow-experimental" installation = read_install_journal() + contract_repair_required = contract["status"] in {"unavailable", "unverified", "drifted"} return { "requested": requested, "detected": detected, "installed": installed, - "status": "ready" if ready else ("mismatch" if any(i["code"] == "profile-mismatch" for i in issues) else "setup-required"), + "status": "ready" + if ready + else ( + "mismatch" + if any(i["code"] == "profile-mismatch" for i in issues) + else ("repair-required" if contract_repair_required else "setup-required") + ), "execution_ready": ready, "manifest_revision": manifest["revision"], "support_tier": (saved or {}).get("support_tier") or (spec.get("tier") if spec else "unqualified"), "capabilities": spec.get("capabilities", []) if spec else [], "issues": issues, - "repair_command": f"python -m modiff.install --accelerator {repair_accelerator} --repair", + "repair_command": repair_command, + "repair_required": contract_repair_required, + "runtime_contract": contract, + "device_validation": device_validation, "installation": installation, } -def lock_digest(path: Path) -> str: - return hashlib.sha256(path.read_bytes()).hexdigest() +def runtime_contract_paths(requirement: Path) -> tuple[Path, ...]: + """Files shared by the selected profile's runtime contract.""" + + return (Path(requirement), PROJECT_METADATA_PATH) + + +def lock_digest( + path: Path, + *, + contract_paths: tuple[Path, ...] | None = None, + profile: str | None = None, +) -> str: + """Hash the reviewed profile input and its central dependency contracts.""" + + hasher = hashlib.sha256() + paths = contract_paths or runtime_contract_paths(path) + for contract_path in paths: + contract_path = Path(contract_path) + body = contract_path.read_bytes() + name = contract_path.name.encode("utf-8") + hasher.update(len(name).to_bytes(4, "big")) + hasher.update(name) + hasher.update(len(body).to_bytes(8, "big")) + hasher.update(body) + if profile: + manifest = load_manifest() + profile_contract = { + "schema_version": manifest["schema_version"], + "python": manifest.get("python"), + "profile": profile, + "spec": manifest["profiles"].get(profile), + } + body = json.dumps(profile_contract, sort_keys=True, separators=(",", ":")).encode("utf-8") + name = f"accelerator-profile:{profile}".encode("utf-8") + hasher.update(len(name).to_bytes(4, "big")) + hasher.update(name) + hasher.update(len(body).to_bytes(8, "big")) + hasher.update(body) + return hasher.hexdigest() diff --git a/modiff/server.py b/modiff/server.py index 8225e81..4954886 100644 --- a/modiff/server.py +++ b/modiff/server.py @@ -1,15 +1,15 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging import asyncio from aiohttp import web, WSMsgType from aiohttp.web_fileresponse import CONTENT_TYPES as AIOHTTP_CONTENT_TYPES from aiohttp_cors import setup as cors_setup, ResourceOptions -import aiofiles import mimetypes mimetypes.add_type("image/webp", ".webp") AIOHTTP_CONTENT_TYPES.add_type("image/webp", ".webp") -logging.getLogger('asyncio').setLevel(logging.WARNING) +logging.getLogger("asyncio").setLevel(logging.WARNING) from functools import partial from importlib import import_module, metadata, invalidate_caches import os @@ -18,13 +18,17 @@ import csv import hashlib import html +import ipaddress +import io import json import nanoid import random import re import shutil +import stat import subprocess import configparser +import threading from utils.paths import list_files from pathlib import Path import sys @@ -33,7 +37,169 @@ import gc from urllib.parse import quote, unquote, unquote_to_bytes, urlparse, parse_qs from copy import deepcopy -logger = logging.getLogger('modiff') +from modiff.path_identifiers import ( + data_path_identifier, + is_data_path_identifier, + resolve_data_path_identifier, + resolve_managed_path_identifier, +) +from modiff.disk_activity import DiskActivitySampler +from modiff.supervisor_control import compact_task_history + +logger = logging.getLogger("modiff") + +SUPERVISED_RESTART_EXIT_CODE = 75 +SUPPORTED_AUDIO_DOWNLOAD_SAMPLE_RATES = {44100, 48000, 88200, 96000} +DEFAULT_CLIENT_MAX_SIZE = 1024**3 +MAX_WORKFLOW_SHARE_MEDIA_BYTES = 256 * 1024 * 1024 +TEMPLATE_GALLERY_ROOT = Path("web/template-gallery") +MAX_PREVIEW_IMAGE_PIXELS = 40_000_000 +_PREVIEW_IMAGE_FORMATS = { + "jpeg": ("JPEG", "image/jpeg"), + "jpg": ("JPEG", "image/jpeg"), + "png": ("PNG", "image/png"), + "webp": ("WEBP", "image/webp"), + "bmp": ("BMP", "image/bmp"), + "ico": ("ICO", "image/x-icon"), + "gif": ("GIF", "image/gif"), + "tiff": ("TIFF", "image/tiff"), +} + + +def render_image_preview(file_path, width=0, height=0, format_id="jpeg", quality=95): + """Decode and resize a bounded image for the async preview route.""" + + from PIL import Image, ImageOps + from utils.image import cover + + descriptor = _PREVIEW_IMAGE_FORMATS.get(str(format_id).lower()) + if descriptor is None: + raise ValueError(f"Unsupported preview image format: {format_id}.") + pillow_format, content_type = descriptor + try: + with Image.open(file_path) as opened: + pixel_count = int(opened.width) * int(opened.height) + if pixel_count > MAX_PREVIEW_IMAGE_PIXELS: + raise ValueError( + f"The image is too large to preview safely ({pixel_count:,} pixels; " + f"limit {MAX_PREVIEW_IMAGE_PIXELS:,})." + ) + opened.load() + image = ImageOps.exif_transpose(opened).copy() + except Image.DecompressionBombError as error: + raise ValueError("The image exceeds Pillow's safe decompression limit.") from error + + requested_width = int(width) + requested_height = int(height) + if requested_width > 0 or requested_height > 0: + requested_width = min(2048, requested_width) if requested_width > 0 else min(2048, requested_height) + requested_height = min(2048, requested_height) if requested_height > 0 else min(2048, requested_width) + else: + requested_width = min(2048, image.width) + requested_height = min(2048, image.height) + + if requested_width != image.width or requested_height != image.height: + image = cover(image, requested_width, requested_height, resample="BICUBIC") + if image.mode != "RGB" and pillow_format in {"JPEG", "BMP", "ICO"}: + image = image.convert("RGB") + + output = io.BytesIO() + save_options = {"format": pillow_format} + if pillow_format in {"JPEG", "WEBP"}: + save_options["quality"] = max(1, min(100, int(quality))) + image.save(output, **save_options) + return output.getvalue(), content_type + + +def is_hidden_path(path): + """Return dotfile or native Windows hidden-attribute state.""" + + if path.name.startswith("."): + return True + file_attributes = getattr(path.stat(), "st_file_attributes", 0) + hidden_attribute = getattr(stat, "FILE_ATTRIBUTE_HIDDEN", 0) + return bool(hidden_attribute and file_attributes & hidden_attribute) + + +def parse_audio_download_sample_rate(value): + if value in (None, ""): + return None + try: + sample_rate = int(value) + except (TypeError, ValueError) as exc: + raise ValueError("Audio download sample rate must be an integer.") from exc + if sample_rate not in SUPPORTED_AUDIO_DOWNLOAD_SAMPLE_RATES: + supported = ", ".join(str(rate) for rate in sorted(SUPPORTED_AUDIO_DOWNLOAD_SAMPLE_RATES)) + raise ValueError(f"Audio download sample rate must be one of: {supported}.") + return sample_rate + + +def resample_wav_bytes(body, target_sample_rate): + """Return WAV bytes whose encoded sample rate matches the requested export rate.""" + from math import gcd + + import numpy as np + from scipy.io import wavfile + from scipy.signal import resample_poly + + source = io.BytesIO(bytes(body)) + source_sample_rate, samples = wavfile.read(source) + source_sample_rate = int(source_sample_rate) + target_sample_rate = int(target_sample_rate) + if source_sample_rate == target_sample_rate: + return bytes(body) + + divisor = gcd(source_sample_rate, target_sample_rate) + resampled = resample_poly( + samples.astype(np.float64), + target_sample_rate // divisor, + source_sample_rate // divisor, + axis=0, + ) + if np.issubdtype(samples.dtype, np.integer): + limits = np.iinfo(samples.dtype) + resampled = np.clip(np.rint(resampled), limits.min, limits.max).astype(samples.dtype) + else: + resampled = resampled.astype(samples.dtype) + + output = io.BytesIO() + wavfile.write(output, target_sample_rate, resampled) + return output.getvalue() + + +def audio_download_filename(filename, sample_rate): + path = Path(str(filename or "MoDiff-audio.wav")) + label = { + 44100: "44.1kHz", + 48000: "48kHz", + 88200: "88.2kHz", + 96000: "96kHz", + }[int(sample_rate)] + suffix = path.suffix if path.suffix.lower() == ".wav" else ".wav" + return f"{path.stem}-{label}{suffix}" + + +def classify_hf_download_error(error: Exception) -> tuple[int, str, str, bool]: + """Map Hub failures to stable, actionable API errors without discarding cache data.""" + name = error.__class__.__name__.lower() + message = str(error) + status_code = getattr(getattr(error, "response", None), "status_code", None) + # Hugging Face's RepositoryNotFoundError text includes a generic suggestion + # about private or gated repositories. Classify the concrete 404/name first + # so a misspelled or retired repo is not incorrectly shown as "Add HF token". + if "repositorynotfound" in name or status_code == 404: + return (404, "huggingface_repo_not_found", "The Hugging Face repository was not found or is private.", False) + if status_code in {401, 403} or "gatedrepo" in name: + return ( + 403, + "huggingface_access_required", + "Hugging Face access is required. Accept the repository license, then add a read token in Model Manager and retry.", + False, + ) + if isinstance(error, (TimeoutError, ConnectionError)) or status_code in {408, 429, 500, 502, 503, 504}: + return (503, "huggingface_network_error", f"Download interrupted by a retryable Hub error: {message}", True) + return (500, "huggingface_download_failed", message, True) + def node_execution_phase(module: str, action: str) -> str: name = f"{module}.{action}".lower() @@ -45,12 +211,13 @@ def node_execution_phase(module: str, action: str) -> str: return "denoising" if "decode" in name: return "decoding" - if "save" in name: - return "saving" + if "save" in name or "export" in name: + return "export" if "preview" in name: return "previewing" return "unknown" + def node_execution_message(module: str, action: str, phase: str) -> str: name = f"{module}.{action}" if phase == "loading": @@ -61,12 +228,13 @@ def node_execution_message(module: str, action: str, phase: str) -> str: return f"Running {name}" if phase == "decoding": return f"Decoding {name}" - if phase == "saving": - return f"Saving {name}" + if phase == "export": + return f"Exporting {name}" if phase == "previewing": return f"Previewing {name}" return f"Running {name}" + def node_execution_weight(module: str, action: str) -> float: phase = node_execution_phase(module, action) if phase == "denoising": @@ -79,20 +247,23 @@ def node_execution_weight(module: str, action: str) -> float: return 0.5 return 1.0 + def is_image_data_type(data_type): - if data_type == 'image': + if data_type == "image": return True if isinstance(data_type, (list, tuple, set)): - return any(item == 'image' for item in data_type) + return any(item == "image" for item in data_type) return False + def image_dimensions(value): - width = getattr(value, 'width', None) - height = getattr(value, 'height', None) + width = getattr(value, "width", None) + height = getattr(value, "height", None) if isinstance(width, int) and isinstance(height, int): return width, height return None, None + def cache_image_artifact(node_id, field_key, index, url, value, image_format): mime_type = f"image/{str(image_format or 'WEBP').lower()}" width, height = image_dimensions(value) @@ -109,6 +280,7 @@ def cache_image_artifact(node_id, field_key, index, url, value, image_format): "source": "cache", } + def attach_run_identity_to_artifact(artifact, *, task_id=None, attempt_index=None, runtime_hints=None): if not isinstance(artifact, dict): return artifact @@ -125,6 +297,89 @@ def attach_run_identity_to_artifact(artifact, *, task_id=None, attempt_index=Non artifact["run_input_hash"] = run_input_hash return artifact + +def file_backed_media_preview(value): + """Return a browser URL for a file-backed media output. + + UI audio/video fields whose source is a string path must point at the file + route. Sending them through /cache serves the path itself as text because + the source field's declared type is ``str``. + """ + if not isinstance(value, (str, os.PathLike)): + return None + value = os.fspath(value) + if value.startswith(("http://", "https://", "data:", "blob:", "/file?")): + return value + return f"/file?file={quote(value, safe='')}&t={time.time()}" + + +def byte_range_response(request, body, *, content_type, charset=None, filename=None): + """Serve generated in-memory media with single-range HTTP semantics. + + Browser media controls seek by requesting a byte range. Returning the + entire cached WAV with ``200`` makes Chromium briefly move the playhead and + then snap back because the resource has no seekable range. + """ + body = bytes(body) + total_length = len(body) + headers = { + "Accept-Ranges": "bytes", + "Cache-Control": "no-cache", + "Pragma": "no-cache", + "Expires": "0", + } + if filename: + headers["Content-Disposition"] = f'inline; filename="{filename}"' + + range_header = request.headers.get("Range") + if not range_header: + headers["Content-Length"] = str(total_length) + return web.Response( + body=body, + content_type=content_type, + charset=charset, + headers=headers, + ) + + match = re.fullmatch(r"bytes=(\d*)-(\d*)", range_header.strip()) + if not match or total_length == 0: + headers["Content-Range"] = f"bytes */{total_length}" + return web.Response(status=416, headers=headers) + + start_text, end_text = match.groups() + if not start_text and not end_text: + headers["Content-Range"] = f"bytes */{total_length}" + return web.Response(status=416, headers=headers) + + if start_text: + start = int(start_text) + if start >= total_length: + headers["Content-Range"] = f"bytes */{total_length}" + return web.Response(status=416, headers=headers) + end = total_length - 1 if not end_text else min(int(end_text), total_length - 1) + if end < start: + headers["Content-Range"] = f"bytes */{total_length}" + return web.Response(status=416, headers=headers) + else: + suffix_length = int(end_text) + if suffix_length <= 0: + headers["Content-Range"] = f"bytes */{total_length}" + return web.Response(status=416, headers=headers) + start = max(total_length - suffix_length, 0) + end = total_length - 1 + + partial = body[start : end + 1] + headers["Content-Range"] = f"bytes {start}-{end}/{total_length}" + headers["Content-Length"] = str(len(partial)) + return web.Response( + status=206, + body=partial, + content_type=content_type, + charset=charset, + headers=headers, + ) + + from modiff.config import CONFIG from modiff.diffusers_offload import ( OFFLOAD_MODE_GROUP_CPU, @@ -133,10 +388,17 @@ def attach_run_identity_to_artifact(artifact, *, task_id=None, attempt_index=Non OFFLOAD_MODE_NONE, OFFLOAD_MODE_SEQUENTIAL_CPU, ) -from modiff.diffusers_profiles import QWEN_IMAGE_2512_PREQUANTIZED_REPO, public_execution_profiles +from modiff.diffusers_profiles import ( + QWEN_IMAGE_2512_PREQUANTIZED_REPO, + VERIFIED_REPAIR_SOURCES, + public_execution_profiles, + public_experimental_pipelines, +) from modiff.hardware import format_hardware_summary, get_hardware_snapshot, legacy_torch_status +from modiff.runtime_profile import runtime_profile from modiff.auto_resource import ( PROVEN_PROOF_STATUSES, + artifact_cache_status, build_auto_resource_plan, build_auto_resource_plans, clear_auto_resource_history, @@ -144,517 +406,953 @@ def attach_run_identity_to_artifact(artifact, *, task_id=None, attempt_index=Non record_auto_resource_failure, record_auto_resource_success, ) +from modiff.model_artifact_catalog import public_model_artifact_catalog, refreshed_hub_metadata +from modiff.optimization_packages import ( + activate_environment as activate_optimization_environment, + install_capability as install_optimization_capability, + optimization_selections_from_graph, + probe_capability as probe_optimization_capability, + public_catalog as public_optimization_catalog, + qualify_receipt as qualify_optimization_receipt, + read_receipts as read_optimization_receipts, + record_workload_observation as record_optimization_workload_observation, + record_workload_baseline as record_optimization_workload_baseline, + rollback_environment as rollback_optimization_environment, + set_capability_enabled as set_optimization_capability_enabled, + workload_key_for_form as optimization_workload_key_for_form, +) from modiff.modelstore import modelstore from modules import MODULE_MAP, parse_module_map -from utils.huggingface import get_local_models, delete_model, search_hub, download_hub_model, get_local_model_ids, get_cache_diagnostics +from utils.huggingface import ( + delete_model, + download_hub_model, + get_cache_diagnostics, + get_local_models, + search_hub, + validate_hf_repo_id, +) from utils.memory_menager import memory_manager from utils.torch_utils import reset_memory_stats, get_memory_stats MODULAR_OFFLOAD_SUPPORT = { - 'default': OFFLOAD_MODE_MODEL_CPU, - 'lowVram': OFFLOAD_MODE_MODEL_CPU, - 'emergency': OFFLOAD_MODE_GROUP_DISK, - 'modes': [OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK], + "default": OFFLOAD_MODE_MODEL_CPU, + "lowVram": OFFLOAD_MODE_MODEL_CPU, + "emergency": OFFLOAD_MODE_GROUP_DISK, + "modes": [OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK], } QWEN_MODULAR_OFFLOAD_SUPPORT = { - 'default': OFFLOAD_MODE_MODEL_CPU, - 'lowVram': OFFLOAD_MODE_MODEL_CPU, - 'emergency': OFFLOAD_MODE_GROUP_DISK, - 'modes': [OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK], + "default": OFFLOAD_MODE_MODEL_CPU, + "lowVram": OFFLOAD_MODE_MODEL_CPU, + "emergency": OFFLOAD_MODE_GROUP_DISK, + "modes": [ + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ], } +# The official 0.9.8 13B repository duplicates pipeline components under a +# nested VAE tree and includes large preview media. A full snapshot is about +# 93 GB; the root Diffusers pipeline needs only these component files. This +# allow-list is published as capability metadata and automatically applied by +# every app Model Manager entry point. +LTX_VIDEO_DIFFUSERS_FILES = [ + "model_index.json", + "scheduler/scheduler_config.json", + "text_encoder/config.json", + "text_encoder/model-00001-of-00004.safetensors", + "text_encoder/model-00002-of-00004.safetensors", + "text_encoder/model-00003-of-00004.safetensors", + "text_encoder/model-00004-of-00004.safetensors", + "text_encoder/model.safetensors.index.json", + "tokenizer/added_tokens.json", + "tokenizer/special_tokens_map.json", + "tokenizer/spiece.model", + "tokenizer/tokenizer_config.json", + "transformer/config.json", + "transformer/diffusion_pytorch_model-00001-of-00006.safetensors", + "transformer/diffusion_pytorch_model-00002-of-00006.safetensors", + "transformer/diffusion_pytorch_model-00003-of-00006.safetensors", + "transformer/diffusion_pytorch_model-00004-of-00006.safetensors", + "transformer/diffusion_pytorch_model-00005-of-00006.safetensors", + "transformer/diffusion_pytorch_model-00006-of-00006.safetensors", + "transformer/diffusion_pytorch_model.safetensors.index.json", + "vae/config.json", + "vae/diffusion_pytorch_model.safetensors", +] + DIRECT_OFFLOAD_SUPPORT = { - 'default': OFFLOAD_MODE_MODEL_CPU, - 'lowVram': OFFLOAD_MODE_MODEL_CPU, - 'emergency': OFFLOAD_MODE_GROUP_DISK, - 'modes': [OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_SEQUENTIAL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK], + "default": OFFLOAD_MODE_MODEL_CPU, + "lowVram": OFFLOAD_MODE_MODEL_CPU, + "emergency": OFFLOAD_MODE_GROUP_DISK, + "modes": [ + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ], } QWEN_IMAGE_EDIT_INPAINT_CONTRACT = { - 'available': True, - 'status': 'supported', - 'reason': 'Direct Diffusers Qwen Image Edit inpaint is available through modules.QwenImage.LoadInpaintPipeline -> modules.QwenImage.Inpaint with source image and mask_image inputs. Outpaint uses modules.QwenImage.OutpaintCanvas to build the expanded canvas and boundary mask before the same inpaint node.', - 'source': 'modules.QwenImage.Inpaint', - 'checkedInputs': { - 'loader': ['model_id', 'dtype', 'device', 'auto_offload', 'offload_mode', 'quant_config'], - 'outpaint': ['image', 'width', 'height', 'left', 'right', 'top', 'bottom', 'overlap', 'feather', 'fill_color', 'canvas', 'mask_image'], - 'inpaint': ['pipeline', 'image', 'mask_image', 'prompt', 'negative_prompt', 'true_cfg_scale', 'strength', 'num_inference_steps'], + "available": True, + "status": "supported", + "reason": "Qwen Image Edit inpaint is available through the generic modules.DiffusersImage LoadPipeline -> Inpaint contract. Outpaint additionally uses the model-neutral Outpaint Canvas node to build the expanded image and boundary mask.", + "source": "modules.DiffusersImage.Inpaint", + "checkedInputs": { + "loader": ["model_id", "dtype", "device", "auto_offload", "offload_mode", "quant_config"], + "outpaint": [ + "image", + "width", + "height", + "left", + "right", + "top", + "bottom", + "overlap", + "feather", + "fill_color", + "canvas", + "mask_image", + ], + "inpaint": [ + "pipeline", + "image", + "mask_image", + "prompt", + "negative_prompt", + "true_cfg_scale", + "strength", + "num_inference_steps", + ], }, - 'missingInputs': [], + "missingInputs": [], } QWEN_IMAGE_EDIT_PLUS_INPAINT_CONTRACT = { - 'available': False, - 'status': 'blocked', - 'reason': 'Qwen Image Edit Plus does not yet have a confirmed native mask, mask_image, or masked_image_latents execution contract in MoDiff.', - 'source': 'modules.ModularDiffusers.modular_utils.QWEN_IMAGE_EDIT_PLUS_NODE_SPECS', - 'checkedInputs': { - 'denoise': ['embeddings', 'seed', 'num_inference_steps', 'guidance_scale', 'image_latents'], - 'vae_encoder': ['image'], - 'text_encoder': ['prompt', 'negative_prompt', 'image'], + "available": False, + "status": "blocked", + "reason": "Qwen Image Edit Plus does not yet have a confirmed native mask, mask_image, or masked_image_latents execution contract in MoDiff.", + "source": "modules.ModularDiffusers.modular_utils.QWEN_IMAGE_EDIT_PLUS_NODE_SPECS", + "checkedInputs": { + "denoise": ["embeddings", "seed", "num_inference_steps", "guidance_scale", "image_latents"], + "vae_encoder": ["image"], + "text_encoder": ["prompt", "negative_prompt", "image"], }, - 'missingInputs': ['mask', 'mask_image', 'masked_image_latents'], + "missingInputs": ["mask", "mask_image", "masked_image_latents"], } class MissingConnectedOutputError(RuntimeError): pass + STUDIO_MODEL_CAPABILITIES = { - 'ZImageModularPipeline': { - 'modelType': 'ZImageModularPipeline', - 'label': 'Z-Image Turbo', - 'displayName': 'Z-Image-Turbo', - 'family': 'Z-Image', - 'defaultRepo': 'Tongyi-MAI/Z-Image-Turbo', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 8, - 'recommendedGuidance': 1, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': False, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': False, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': QWEN_MODULAR_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_MODEL_CPU, 'steps': 8}, - 'modes': ['text_to_image'], - 'executionStatus': 'supported', + "ZImageModularPipeline": { + "modelType": "ZImageModularPipeline", + "label": "Z-Image Turbo", + "displayName": "Z-Image-Turbo", + "family": "Z-Image", + "defaultRepo": "Tongyi-MAI/Z-Image-Turbo", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 8, + "recommendedGuidance": 1, + "guidanceLabel": "Guidance", + "supportsNegativePrompt": False, + "supportsImageInput": False, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": QWEN_MODULAR_OFFLOAD_SUPPORT, + "lowVram": {"dtype": "bfloat16", "autoOffload": True, "offloadMode": OFFLOAD_MODE_MODEL_CPU, "steps": 8}, + "modes": ["text_to_image"], + "executionStatus": "supported", }, - 'QwenImageModularPipeline': { - 'modelType': 'QwenImageModularPipeline', - 'label': 'Qwen-Image-2512', - 'displayName': 'Qwen-Image-2512', - 'family': 'Qwen Image', - 'defaultRepo': 'Qwen/Qwen-Image-2512', - 'artifactLabel': 'bfloat16 Diffusers repo', - 'comfyArtifact': 'qwen_image_2512_fp8_e4m3fn.safetensors', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 50, - 'recommendedGuidance': 4.5, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': False, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': True, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': QWEN_MODULAR_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'quantizationMode': 'bnb_4bit', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_MODEL_CPU, 'steps': 28}, - 'modes': ['text_to_image', 'control_image'], - 'executionStatus': 'supported_with_model', - 'additionalRequirements': [ + "QwenImageModularPipeline": { + "modelType": "QwenImageModularPipeline", + "label": "Qwen-Image-2512", + "displayName": "Qwen-Image-2512", + "family": "Qwen Image", + "defaultRepo": "Qwen/Qwen-Image-2512", + "artifactLabel": "bfloat16 Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 50, + "recommendedGuidance": 4.5, + "guidanceLabel": "Guidance", + "supportsImageInput": False, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": True, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": QWEN_MODULAR_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "quantizationMode": "bnb_4bit", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 28, + }, + "modes": ["text_to_image", "control_image"], + "executionStatus": "supported_with_model", + "additionalRequirements": [ { - 'id': 'qwen-controlnet-union', - 'label': 'Qwen ControlNet Union', - 'repo': 'InstantX/Qwen-Image-ControlNet-Union', - 'kind': 'controlnet', - 'requiredForModes': ['control_image'], - 'description': 'Required for Qwen Image Control image workflows.', + "id": "qwen-controlnet-union", + "label": "Qwen ControlNet Union", + "repo": "InstantX/Qwen-Image-ControlNet-Union", + "kind": "controlnet", + "requiredForModes": ["control_image"], + "description": "Required for Qwen Image Control image workflows.", } ], - 'modeRequirements': { - 'control_image': { - 'modelRequirements': [ + "modeRequirements": { + "control_image": { + "modelRequirements": [ { - 'id': 'qwen-controlnet-union', - 'label': 'Qwen ControlNet Union', - 'repo': 'InstantX/Qwen-Image-ControlNet-Union', - 'kind': 'controlnet', - 'requiredForModes': ['control_image'], - 'description': 'Required for Qwen Image Control image workflows.', + "id": "qwen-controlnet-union", + "label": "Qwen ControlNet Union", + "repo": "InstantX/Qwen-Image-ControlNet-Union", + "kind": "controlnet", + "requiredForModes": ["control_image"], + "description": "Required for Qwen Image Control image workflows.", } ], - 'requiredImages': ['controlImage'], - 'note': 'Requires the Qwen ControlNet Union model plus one control image.', + "requiredImages": ["controlImage"], + "note": "Requires the Qwen ControlNet Union model plus one control image.", } }, }, - 'QwenImageEditModularPipeline': { - 'modelType': 'QwenImageEditModularPipeline', - 'label': 'Qwen-Image-Edit', - 'displayName': 'Qwen-Image-Edit', - 'family': 'Qwen Image', - 'defaultRepo': 'Qwen/Qwen-Image-Edit', - 'artifactLabel': 'bfloat16 Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 40, - 'recommendedGuidance': 4, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': True, - 'supportsMask': True, - 'supportsMultiImage': False, - 'supportsControlImage': False, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'quantizationMode': 'bnb_4bit', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_MODEL_CPU, 'steps': 24}, - 'modes': ['edit_image', 'inpaint', 'outpaint'], - 'executionStatus': 'supported_with_model', - 'notes': ['Inpaint and outpaint use the direct Diffusers QwenImageEditInpaintPipeline backend nodes.'], - 'inpaintContract': QWEN_IMAGE_EDIT_INPAINT_CONTRACT, - 'modeRequirements': { - 'inpaint': { - 'requiredImages': ['referenceImages', 'maskImage'], - 'note': 'Requires one source image and one mask image.', + "QwenImageEditModularPipeline": { + "modelType": "QwenImageEditModularPipeline", + "label": "Qwen-Image-Edit", + "displayName": "Qwen-Image-Edit", + "family": "Qwen Image", + "defaultRepo": "Qwen/Qwen-Image-Edit", + "artifactLabel": "bfloat16 Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 40, + "recommendedGuidance": 4, + "guidanceLabel": "Guidance", + "supportsImageInput": True, + "supportsMask": True, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "quantizationMode": "bnb_4bit", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 24, + }, + "modes": ["edit_image", "inpaint", "outpaint"], + "executionStatus": "supported_with_model", + "notes": [ + "Inpaint and outpaint use generic Diffusers image nodes with the QwenImageEditInpaintPipeline adapter." + ], + "inpaintContract": QWEN_IMAGE_EDIT_INPAINT_CONTRACT, + "modeRequirements": { + "inpaint": { + "requiredImages": ["referenceImages", "maskImage"], + "note": "Requires one source image and one mask image.", + }, + "outpaint": { + "requiredImages": ["referenceImages"], + "note": "Requires one source image; MoDiff builds the expanded canvas and boundary mask.", }, - 'outpaint': { - 'requiredImages': ['referenceImages'], - 'note': 'Requires one source image; MoDiff builds the expanded canvas and boundary mask.', - } }, }, - 'QwenImageEditPlusModularPipeline': { - 'modelType': 'QwenImageEditPlusModularPipeline', - 'label': 'Qwen-Image-Edit-2511', - 'displayName': 'Qwen-Image-Edit-2511', - 'family': 'Qwen Image', - 'defaultRepo': 'Qwen/Qwen-Image-Edit-2511', - 'artifactLabel': 'bfloat16 Diffusers repo', - 'comfyArtifact': 'qwen_image_edit_2511_bf16.safetensors', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 40, - 'recommendedGuidance': 4, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': True, - 'supportsMask': False, - 'supportsMultiImage': True, - 'supportsControlImage': False, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': QWEN_MODULAR_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'quantizationMode': 'bnb_4bit', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_MODEL_CPU, 'steps': 24}, - 'modes': ['edit_image', 'multi_image_reference_edit', 'inpaint'], - 'executionStatus': 'supported_with_model', - 'notes': ['Inpaint mask execution still requires a confirmed backend mask graph contract.'], - 'inpaintContract': QWEN_IMAGE_EDIT_PLUS_INPAINT_CONTRACT, - 'modeRequirements': { - 'inpaint': { - 'requiredImages': ['referenceImages', 'maskImage'], - 'note': QWEN_IMAGE_EDIT_PLUS_INPAINT_CONTRACT['reason'], + "QwenImageEditPlusModularPipeline": { + "modelType": "QwenImageEditPlusModularPipeline", + "label": "Qwen-Image-Edit-2511", + "displayName": "Qwen-Image-Edit-2511", + "family": "Qwen Image", + "defaultRepo": "Qwen/Qwen-Image-Edit-2511", + "artifactLabel": "bfloat16 Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 40, + "recommendedGuidance": 4, + "guidanceLabel": "Guidance", + "supportsImageInput": True, + "supportsMask": False, + "supportsMultiImage": True, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": QWEN_MODULAR_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "quantizationMode": "bnb_4bit", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 24, + }, + "modes": ["edit_image", "multi_image_reference_edit", "inpaint"], + "executionStatus": "supported_with_model", + "notes": ["Inpaint mask execution still requires a confirmed backend mask graph contract."], + "inpaintContract": QWEN_IMAGE_EDIT_PLUS_INPAINT_CONTRACT, + "modeRequirements": { + "inpaint": { + "requiredImages": ["referenceImages", "maskImage"], + "note": QWEN_IMAGE_EDIT_PLUS_INPAINT_CONTRACT["reason"], } }, }, - 'QwenImageLayeredModularPipeline': { - 'modelType': 'QwenImageLayeredModularPipeline', - 'label': 'Qwen-Image-Layered', - 'displayName': 'Qwen-Image-Layered', - 'family': 'Qwen Image', - 'defaultRepo': 'Qwen/Qwen-Image-Layered', - 'artifactLabel': 'bfloat16 Diffusers repo', - 'comfyArtifact': 'qwen_image_layered_bf16.safetensors', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 50, - 'recommendedGuidance': 4, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': True, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': False, - 'supportsLayers': True, - 'supportsLora': True, - 'offloadSupport': MODULAR_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'quantizationMode': 'bnb_4bit', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_MODEL_CPU, 'steps': 30}, - 'modes': ['layer_decomposition'], - 'executionStatus': 'supported_with_model', + "QwenImageLayeredModularPipeline": { + "modelType": "QwenImageLayeredModularPipeline", + "label": "Qwen-Image-Layered", + "displayName": "Qwen-Image-Layered", + "family": "Qwen Image", + "defaultRepo": "Qwen/Qwen-Image-Layered", + "artifactLabel": "bfloat16 Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 50, + "recommendedGuidance": 4, + "guidanceLabel": "Guidance", + "supportsImageInput": True, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": True, + "supportsLora": True, + "offloadSupport": MODULAR_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "quantizationMode": "bnb_4bit", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 30, + }, + "modes": ["layer_decomposition"], + "executionStatus": "supported_with_model", + }, + "WanVACEPipeline": { + "modelType": "WanVACEPipeline", + "label": "Wan VACE 1.3B", + "displayName": "Wan2.1-VACE-1.3B-diffusers", + "family": "Wan Video", + "qualificationStatus": "qualified", + "qualifiedModes": ["text_to_video", "video_inpaint", "video_outpaint", "control_to_video"], + "defaultRepo": "Wan-AI/Wan2.1-VACE-1.3B-diffusers", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 832, "height": 480, "aspectRatio": "16:9"}, + "recommendedSteps": 30, + "recommendedGuidance": 5.0, + "guidanceLabel": "Guidance", + "supportsImageInput": True, + "supportsMask": True, + "supportsMultiImage": True, + "supportsControlImage": True, + "supportsLayers": False, + "supportsLora": True, + "supportsVideoInput": True, + "supportsVideoMask": True, + "outputKind": "video", + "recommendedFrames": 81, + "recommendedFps": 16, + "conditioningScale": 1.0, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 24, + "width": 832, + "height": 480, + "numFrames": 49, + }, + "modes": [ + "text_to_video", + "video_inpaint", + "video_outpaint", + "control_to_video", + ], + "executionStatus": "supported_with_model", + "notes": [ + "Wan VACE is exposed as a direct Diffusers pipeline because this runtime does not expose a WanVACEModularPipeline.", + "Exact color correction is provided by deterministic Video Color nodes; Wan VACE color edits are generative.", + "Image-only and free-reference conditioning remain planning contracts after failing source-fidelity qualification and are not advertised as runnable modes.", + ], + "modeRequirements": { + "video_inpaint": { + "requiredVideos": ["sourceVideo", "maskVideo"], + "note": "Requires source video and matching mask video.", + }, + "video_outpaint": { + "requiredVideos": ["sourceVideo", "maskVideo"], + "note": "Requires source video and boundary/generation mask video.", + }, + "control_to_video": {"requiredVideos": ["controlVideo"], "note": "Requires a prepared control video."}, + }, + }, + "WanVideoPipeline": { + "modelType": "WanVideoPipeline", + "label": "Wan 2.1 T2V 1.3B", + "displayName": "Wan2.1-T2V-1.3B-Diffusers", + "family": "Wan Video", + "defaultRepo": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 832, "height": 480, "aspectRatio": "16:9"}, + "supportTier": "supported", + "qualificationStatus": "qualified", + "qualifiedModes": ["text_to_video"], + "recommendedSteps": 50, + "recommendedGuidance": 5.0, + "guidanceLabel": "Guidance", + "supportsImageInput": False, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "supportsVideoInput": True, + "supportsVideoMask": False, + "outputKind": "video", + "recommendedFrames": 81, + "recommendedFps": 15, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 24, + "width": 832, + "height": 480, + "numFrames": 49, + }, + "modes": ["text_to_video", "video_to_video", "video_color_edit"], + "executionStatus": "supported_with_model", + "notes": [ + "Uses the generic Diffusers video node with WanPipeline for text generation and WanVideoToVideoPipeline when strength is a real denoise control.", + "VACE-only mask, control, and reference inputs are rejected before model load.", + "The 832x480, 81-frame, 15 fps, 50-step text-to-video contract is mechanically qualified on Radeon 8060S; human creative review remains pending.", + ], + "modeRequirements": { + "video_to_video": {"requiredVideos": ["sourceVideo"], "note": "Requires one source video."}, + "video_color_edit": {"requiredVideos": ["sourceVideo"], "note": "Requires one source video."}, + }, + }, + "WanImageToVideoPipeline": { + "modelType": "WanImageToVideoPipeline", + "label": "Wan 2.2 I2V A14B", + "displayName": "Wan2.2-I2V-A14B-Diffusers", + "family": "Wan Video", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-execution-pending", + "qualifiedModes": [], + "defaultRepo": "Wan-AI/Wan2.2-I2V-A14B-Diffusers", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 832, "height": 480, "aspectRatio": "16:9"}, + "recommendedSteps": 40, + "recommendedGuidance": 3.5, + "guidanceLabel": "High-noise guidance", + "supportsImageInput": True, + "supportsMask": False, + "supportsMultiImage": True, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": False, + "supportsVideoInput": False, + "supportsVideoMask": False, + "outputKind": "video", + "recommendedFrames": 81, + "recommendedFps": 16, + "conditioningScale": 1.0, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 40, + "width": 832, + "height": 480, + "numFrames": 81, + }, + "modes": ["image_to_video"], + "executionStatus": "supported_with_model", + "notes": [ + "Uses the generic Diffusers video facade with the official dual-expert WanImageToVideoPipeline.", + "The quality workflow quantizes both denoising experts to Quanto INT8 and runs five-second shots sequentially.", + "Human review remains required before generated examples are promoted to the gallery.", + ], + "modeRequirements": { + "image_to_video": { + "requiredImages": ["referenceImages"], + "note": "The story workflow requires one ordered opening keyframe per shot.", + }, + }, }, - 'WanVACEPipeline': { - 'modelType': 'WanVACEPipeline', - 'label': 'Wan VACE 1.3B', - 'displayName': 'Wan2.1-VACE-1.3B-diffusers', - 'family': 'Wan Video', - 'defaultRepo': 'Wan-AI/Wan2.1-VACE-1.3B-diffusers', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 832, 'height': 480, 'aspectRatio': '16:9'}, - 'recommendedSteps': 30, - 'recommendedGuidance': 5.0, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': True, - 'supportsMask': True, - 'supportsMultiImage': True, - 'supportsControlImage': True, - 'supportsLayers': False, - 'supportsLora': True, - 'supportsVideoInput': True, - 'supportsVideoMask': True, - 'outputKind': 'video', - 'recommendedFrames': 81, - 'recommendedFps': 16, - 'conditioningScale': 1.0, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_MODEL_CPU, 'steps': 24, 'width': 832, 'height': 480, 'numFrames': 49}, - 'modes': [ - 'text_to_video', - 'image_to_video', - 'video_to_video', - 'video_inpaint', - 'video_outpaint', - 'reference_to_video', - 'control_to_video', - 'video_color_edit', + "WanTI2VPipeline": { + "modelType": "WanTI2VPipeline", + "label": "Wan 2.2 TI2V 5B", + "displayName": "Wan2.2-TI2V-5B-Diffusers", + "family": "Wan Video", + "supportTier": "supported", + "qualificationStatus": "graph-qualified-execution-pending", + "qualifiedModes": [], + "defaultRepo": "Wan-AI/Wan2.2-TI2V-5B-Diffusers", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1280, "height": 704, "aspectRatio": "16:9"}, + "recommendedSteps": 50, + "recommendedGuidance": 5.0, + "guidanceLabel": "Guidance", + "supportsImageInput": False, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "supportsVideoInput": False, + "supportsVideoMask": False, + "outputKind": "video", + "recommendedFrames": 121, + "recommendedFps": 24, + "conditioningScale": 1.0, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 50, + "width": 1280, + "height": 704, + "numFrames": 121, + }, + "modes": ["text_to_video"], + "executionStatus": "supported_with_model", + "notes": [ + "Uses the official dense Wan 2.2 5B high-compression video model for five-second 720p shots.", + "The current Diffusers WanPipeline exposes text-to-video; A14B remains the image-to-video adapter.", + "Human review remains required before generated examples are promoted to the gallery.", ], - 'executionStatus': 'supported_with_model', - 'notes': [ - 'Wan VACE is exposed as a direct Diffusers pipeline because this runtime does not expose a WanVACEModularPipeline.', - 'Exact color correction is provided by deterministic Video Color nodes; Wan VACE color edits are generative.', + "modeRequirements": {}, + }, + "LTXVideoPipeline": { + "modelType": "LTXVideoPipeline", + "label": "LTX-Video", + "displayName": "LTX-Video Diffusers", + "family": "LTX Video", + "supportTier": "supported", + "qualificationStatus": "qualified", + "qualifiedModes": ["text_to_video", "image_to_video", "video_to_video", "reference_to_video"], + "defaultRepo": "Lightricks/LTX-Video-0.9.8-13B-distilled", + "artifactLabel": "Diffusers repo", + "downloadFiles": LTX_VIDEO_DIFFUSERS_FILES, + "defaultDtype": "bfloat16", + "defaultSize": {"width": 704, "height": 480, "aspectRatio": "22:15"}, + "recommendedSteps": 8, + "recommendedGuidance": 1.0, + "guidanceLabel": "Guidance", + "maxPromptTokens": 128, + "supportsImageInput": True, + "supportsMask": False, + "supportsMultiImage": True, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "supportsVideoInput": True, + "supportsVideoMask": False, + "outputKind": "video", + "recommendedFrames": 81, + "recommendedFps": 16, + "conditioningScale": 1.0, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 8, + "width": 704, + "height": 480, + "numFrames": 65, + }, + "modes": ["text_to_video", "image_to_video", "video_to_video", "reference_to_video"], + "executionStatus": "supported_with_model", + "notes": [ + "LTX uses the generic Diffusers video nodes and the official LTXConditionPipeline contract.", + "Text, image, source-video, and multi-reference conditioning use the same model-neutral graph contract.", + "Prompts are validated against the artifact tokenizer and rejected above 128 tokens before diffusion begins.", + "Mask and control-video modes are not advertised until a matching official Diffusers adapter is qualified.", ], - 'modeRequirements': { - 'image_to_video': {'requiredImages': ['referenceImages'], 'note': 'Requires at least one starting/reference image.'}, - 'video_to_video': {'requiredVideos': ['sourceVideo'], 'note': 'Requires one source video.'}, - 'video_inpaint': {'requiredVideos': ['sourceVideo', 'maskVideo'], 'note': 'Requires source video and matching mask video.'}, - 'video_outpaint': {'requiredVideos': ['sourceVideo', 'maskVideo'], 'note': 'Requires source video and boundary/generation mask video.'}, - 'reference_to_video': {'requiredImages': ['referenceImages'], 'note': 'Requires one or more reference images.'}, - 'control_to_video': {'requiredVideos': ['controlVideo'], 'note': 'Requires a prepared control video.'}, - 'video_color_edit': {'requiredVideos': ['sourceVideo'], 'note': 'Requires one source video.'}, + "modeRequirements": { + "image_to_video": {"requiredImages": ["referenceImages"], "note": "Requires one starting image."}, + "video_to_video": {"requiredVideos": ["sourceVideo"], "note": "Requires one source video."}, + "reference_to_video": { + "requiredImages": ["referenceImages"], + "note": "Requires one or more frame references.", + }, + }, + }, + "AceStepAudioPipeline": { + "modelType": "AceStepAudioPipeline", + "label": "ACE-Step Audio", + "displayName": "acestep-v15-xl-turbo-diffusers", + "family": "ACE Audio", + "defaultRepo": "ACE-Step/acestep-v15-xl-turbo-diffusers", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 0, "height": 0, "aspectRatio": "custom"}, + "recommendedSteps": 8, + "recommendedGuidance": 1.0, + "guidanceLabel": "Guidance", + "supportsImageInput": False, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": False, + "supportsAudioInput": True, + "outputKind": "audio", + "recommendedSampleRate": 48000, + "recommendedDuration": 30, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": {"dtype": "bfloat16", "autoOffload": True, "offloadMode": OFFLOAD_MODE_MODEL_CPU, "steps": 8}, + "modes": ["text_to_audio", "audio_variation", "audio_continuation", "audio_repaint"], + "executionStatus": "supported_with_model", + "modeRequirements": { + "audio_variation": {"requiredAudio": ["sourceAudio"], "note": "Requires a source audio clip."}, + "audio_continuation": { + "requiredAudio": ["sourceAudio"], + "note": "Requires a source audio clip to continue.", + }, + "audio_repaint": {"requiredAudio": ["sourceAudio"], "note": "Requires source audio plus repaint timing."}, }, }, - 'AceStepAudioPipeline': { - 'modelType': 'AceStepAudioPipeline', - 'label': 'ACE-Step Audio', - 'displayName': 'acestep-v15-xl-turbo-diffusers', - 'family': 'ACE Audio', - 'defaultRepo': 'ACE-Step/acestep-v15-xl-turbo-diffusers', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 0, 'height': 0, 'aspectRatio': 'custom'}, - 'recommendedSteps': 8, - 'recommendedGuidance': 1.0, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': False, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': False, - 'supportsLayers': False, - 'supportsLora': False, - 'supportsAudioInput': True, - 'outputKind': 'audio', - 'recommendedSampleRate': 48000, - 'recommendedDuration': 30, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_MODEL_CPU, 'steps': 8}, - 'modes': ['text_to_audio', 'audio_variation', 'audio_continuation', 'audio_repaint'], - 'executionStatus': 'supported_with_model', - 'modeRequirements': { - 'audio_variation': {'requiredAudio': ['sourceAudio'], 'note': 'Requires a source audio clip.'}, - 'audio_continuation': {'requiredAudio': ['sourceAudio'], 'note': 'Requires a source audio clip to continue.'}, - 'audio_repaint': {'requiredAudio': ['sourceAudio'], 'note': 'Requires source audio plus repaint timing.'}, + "FluxSchnellPipeline": { + "modelType": "FluxSchnellPipeline", + "label": "FLUX.1 schnell", + "displayName": "FLUX.1-schnell", + "family": "FLUX Image", + "defaultRepo": "black-forest-labs/FLUX.1-schnell", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 4, + "recommendedGuidance": 0.0, + "guidanceLabel": "Guidance", + "supportsImageInput": False, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 4, + "width": 1024, + "height": 1024, }, + "modes": ["text_to_image"], + "executionStatus": "supported_with_model", }, - 'FluxSchnellPipeline': { - 'modelType': 'FluxSchnellPipeline', - 'label': 'FLUX.1 schnell', - 'displayName': 'FLUX.1-schnell', - 'family': 'FLUX Image', - 'defaultRepo': 'black-forest-labs/FLUX.1-schnell', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 4, - 'recommendedGuidance': 0.0, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': False, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': False, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_MODEL_CPU, 'steps': 4, 'width': 1024, 'height': 1024}, - 'modes': ['text_to_image'], - 'executionStatus': 'supported_with_model', + "FluxDevPipeline": { + "modelType": "FluxDevPipeline", + "label": "FLUX.1 dev", + "displayName": "FLUX.1-dev", + "family": "FLUX Image", + "defaultRepo": "black-forest-labs/FLUX.1-dev", + "alternateArtifact": "black-forest-labs/FLUX.1-dev-FP8", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 768, "height": 768, "aspectRatio": "1:1"}, + "recommendedSteps": 20, + "recommendedGuidance": 3.5, + "guidanceLabel": "Guidance", + "supportsImageInput": False, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_GROUP_DISK, + "steps": 20, + "width": 768, + "height": 768, + }, + "modes": ["text_to_image"], + "executionStatus": "supported_with_model", + "notes": ["Auto prefers the FP8 artifact on 16 GB CUDA when available."], }, - 'FluxDevPipeline': { - 'modelType': 'FluxDevPipeline', - 'label': 'FLUX.1 dev', - 'displayName': 'FLUX.1-dev', - 'family': 'FLUX Image', - 'defaultRepo': 'black-forest-labs/FLUX.1-dev', - 'alternateArtifact': 'black-forest-labs/FLUX.1-dev-FP8', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 768, 'height': 768, 'aspectRatio': '1:1'}, - 'recommendedSteps': 20, - 'recommendedGuidance': 3.5, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': False, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': False, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_GROUP_DISK, 'steps': 20, 'width': 768, 'height': 768}, - 'modes': ['text_to_image'], - 'executionStatus': 'supported_with_model', - 'notes': ['Auto prefers the FP8 artifact on 16 GB CUDA when available.'], + "FluxKreaPipeline": { + "modelType": "FluxKreaPipeline", + "label": "FLUX.1 Krea dev", + "displayName": "FLUX.1-Krea-dev", + "family": "FLUX Image", + "defaultRepo": "black-forest-labs/FLUX.1-Krea-dev", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 28, + "recommendedGuidance": 3.5, + "guidanceLabel": "Guidance", + "supportsImageInput": False, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_GROUP_DISK, + "steps": 20, + "width": 768, + "height": 768, + }, + "modes": ["text_to_image"], + "executionStatus": "expert_only", }, - 'FluxKreaPipeline': { - 'modelType': 'FluxKreaPipeline', - 'label': 'FLUX.1 Krea dev', - 'displayName': 'FLUX.1-Krea-dev', - 'family': 'FLUX Image', - 'defaultRepo': 'black-forest-labs/FLUX.1-Krea-dev', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 28, - 'recommendedGuidance': 3.5, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': False, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': False, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_GROUP_DISK, 'steps': 20, 'width': 768, 'height': 768}, - 'modes': ['text_to_image'], - 'executionStatus': 'expert_only', + "FluxKontextPipeline": { + "modelType": "FluxKontextPipeline", + "label": "FLUX.1 Kontext dev", + "displayName": "FLUX.1-Kontext-dev", + "family": "FLUX Image", + "defaultRepo": "black-forest-labs/FLUX.1-Kontext-dev", + "alternateArtifact": "black-forest-labs/FLUX.1-Kontext-dev-NVFP4", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 28, + "recommendedGuidance": 3.5, + "guidanceLabel": "Guidance", + "supportsImageInput": True, + "supportsMask": False, + "supportsMultiImage": True, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_GROUP_DISK, + "steps": 20, + "width": 768, + "height": 768, + }, + "modes": ["edit_image", "multi_image_reference_edit"], + "executionStatus": "expert_only", + "modeRequirements": { + "edit_image": {"requiredImages": ["referenceImages"], "note": "Requires a source image."} + }, }, - 'FluxKontextPipeline': { - 'modelType': 'FluxKontextPipeline', - 'label': 'FLUX.1 Kontext dev', - 'displayName': 'FLUX.1-Kontext-dev', - 'family': 'FLUX Image', - 'defaultRepo': 'black-forest-labs/FLUX.1-Kontext-dev', - 'alternateArtifact': 'black-forest-labs/FLUX.1-Kontext-dev-NVFP4', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 28, - 'recommendedGuidance': 3.5, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': True, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': False, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_GROUP_DISK, 'steps': 20, 'width': 768, 'height': 768}, - 'modes': ['edit_image'], - 'executionStatus': 'expert_only', - 'modeRequirements': {'edit_image': {'requiredImages': ['referenceImages'], 'note': 'Requires a source image.'}}, + "FluxFillPipeline": { + "modelType": "FluxFillPipeline", + "label": "FLUX.1 Fill dev", + "displayName": "FLUX.1-Fill-dev", + "family": "FLUX Image", + "defaultRepo": "black-forest-labs/FLUX.1-Fill-dev", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 28, + "recommendedGuidance": 3.5, + "guidanceLabel": "Guidance", + "supportsImageInput": True, + "supportsMask": True, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_GROUP_DISK, + "steps": 20, + "width": 768, + "height": 768, + }, + "modes": ["inpaint", "outpaint"], + "executionStatus": "expert_only", + "modeRequirements": { + "inpaint": {"requiredImages": ["referenceImages", "maskImage"], "note": "Requires source and mask images."} + }, }, - 'FluxFillPipeline': { - 'modelType': 'FluxFillPipeline', - 'label': 'FLUX.1 Fill dev', - 'displayName': 'FLUX.1-Fill-dev', - 'family': 'FLUX Image', - 'defaultRepo': 'black-forest-labs/FLUX.1-Fill-dev', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 28, - 'recommendedGuidance': 3.5, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': True, - 'supportsMask': True, - 'supportsMultiImage': False, - 'supportsControlImage': False, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_GROUP_DISK, 'steps': 20, 'width': 768, 'height': 768}, - 'modes': ['inpaint', 'outpaint'], - 'executionStatus': 'expert_only', - 'modeRequirements': {'inpaint': {'requiredImages': ['referenceImages', 'maskImage'], 'note': 'Requires source and mask images.'}}, + "FluxDepthPipeline": { + "modelType": "FluxDepthPipeline", + "label": "FLUX.1 Depth dev", + "displayName": "FLUX.1-Depth-dev", + "family": "FLUX Image", + "defaultRepo": "black-forest-labs/FLUX.1-Depth-dev", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 28, + "recommendedGuidance": 3.5, + "guidanceLabel": "Guidance", + "supportsImageInput": True, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": True, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_GROUP_DISK, + "steps": 20, + "width": 768, + "height": 768, + }, + "modes": ["control_image"], + "executionStatus": "expert_only", }, - 'FluxDepthPipeline': { - 'modelType': 'FluxDepthPipeline', - 'label': 'FLUX.1 Depth dev', - 'displayName': 'FLUX.1-Depth-dev', - 'family': 'FLUX Image', - 'defaultRepo': 'black-forest-labs/FLUX.1-Depth-dev', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 28, - 'recommendedGuidance': 3.5, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': True, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': True, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_GROUP_DISK, 'steps': 20, 'width': 768, 'height': 768}, - 'modes': ['control_image'], - 'executionStatus': 'expert_only', + "FluxCannyPipeline": { + "modelType": "FluxCannyPipeline", + "label": "FLUX.1 Canny dev", + "displayName": "FLUX.1-Canny-dev", + "family": "FLUX Image", + "defaultRepo": "black-forest-labs/FLUX.1-Canny-dev", + "artifactCandidates": [ + "black-forest-labs/FLUX.1-Canny-dev", + "fuliucansheng/FLUX.1-Canny-dev-diffusers", + ], + "verifiedRepairSources": [ + { + "repo": "fuliucansheng/FLUX.1-Canny-dev-diffusers", + "verification": "matching filename, size, and LFS SHA-256 plus local byte verification", + } + ], + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 28, + "recommendedGuidance": 3.5, + "guidanceLabel": "Guidance", + "supportsImageInput": True, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": True, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_GROUP_DISK, + "steps": 20, + "width": 768, + "height": 768, + }, + "modes": ["control_image"], + "executionStatus": "expert_only", }, - 'FluxCannyPipeline': { - 'modelType': 'FluxCannyPipeline', - 'label': 'FLUX.1 Canny dev', - 'displayName': 'FLUX.1-Canny-dev', - 'family': 'FLUX Image', - 'defaultRepo': 'black-forest-labs/FLUX.1-Canny-dev', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 28, - 'recommendedGuidance': 3.5, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': True, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': True, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_GROUP_DISK, 'steps': 20, 'width': 768, 'height': 768}, - 'modes': ['control_image'], - 'executionStatus': 'expert_only', + "FluxReduxPipeline": { + "modelType": "FluxReduxPipeline", + "label": "FLUX.1 Redux dev", + "displayName": "FLUX.1-Redux-dev", + "family": "FLUX Image", + "defaultRepo": "black-forest-labs/FLUX.1-Redux-dev", + "artifactCandidates": ["black-forest-labs/FLUX.1-Redux-dev", "black-forest-labs/FLUX.1-dev"], + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 28, + "recommendedGuidance": 3.5, + "guidanceLabel": "Guidance", + "supportsImageInput": True, + "supportsMask": False, + "supportsMultiImage": False, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_GROUP_DISK, + "steps": 20, + "width": 768, + "height": 768, + }, + "modes": ["edit_image"], + "executionStatus": "expert_only", }, - 'FluxReduxPipeline': { - 'modelType': 'FluxReduxPipeline', - 'label': 'FLUX.1 Redux dev', - 'displayName': 'FLUX.1-Redux-dev', - 'family': 'FLUX Image', - 'defaultRepo': 'black-forest-labs/FLUX.1-Redux-dev', - 'artifactLabel': 'Diffusers repo', - 'defaultDtype': 'bfloat16', - 'defaultSize': {'width': 1024, 'height': 1024, 'aspectRatio': '1:1'}, - 'recommendedSteps': 28, - 'recommendedGuidance': 3.5, - 'guidanceLabel': 'Guidance', - 'supportsImageInput': True, - 'supportsMask': False, - 'supportsMultiImage': False, - 'supportsControlImage': False, - 'supportsLayers': False, - 'supportsLora': True, - 'offloadSupport': DIRECT_OFFLOAD_SUPPORT, - 'lowVram': {'dtype': 'bfloat16', 'autoOffload': True, 'offloadMode': OFFLOAD_MODE_GROUP_DISK, 'steps': 20, 'width': 768, 'height': 768}, - 'modes': ['edit_image'], - 'executionStatus': 'expert_only', + "Flux2KleinPipeline": { + "modelType": "Flux2KleinPipeline", + "label": "FLUX.2 Klein 4B", + "displayName": "FLUX.2-klein-4B", + "family": "FLUX Image", + "defaultRepo": "black-forest-labs/FLUX.2-klein-4B", + "artifactLabel": "Diffusers repo", + "defaultDtype": "bfloat16", + "defaultSize": {"width": 1024, "height": 1024, "aspectRatio": "1:1"}, + "recommendedSteps": 4, + "recommendedGuidance": 1.0, + "guidanceLabel": "Guidance", + "supportsImageInput": True, + "supportsMask": False, + "supportsMultiImage": True, + "supportsControlImage": False, + "supportsLayers": False, + "supportsLora": True, + "offloadSupport": DIRECT_OFFLOAD_SUPPORT, + "lowVram": { + "dtype": "bfloat16", + "autoOffload": True, + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "steps": 4, + "width": 768, + "height": 768, + }, + "modes": ["text_to_image", "edit_image", "multi_image_reference_edit"], + "executionStatus": "supported_with_model", + "modeRequirements": { + "edit_image": {"requiredImages": ["referenceImages"], "note": "Requires one source/reference image."}, + "multi_image_reference_edit": { + "requiredImages": ["referenceImages"], + "note": "Requires two or more reference images.", + }, + }, + "notes": [ + "Qualified through the generic Diffusers image facade for text, single-reference, and multi-reference generation." + ], }, } + class WebServer: def __init__( - self, - modules: dict = {}, - host: str = '127.0.0.1', - port: int = 8088, - secure: bool = False, - certfile: str = None, - keyfile: str = None, - cors: bool = False, - cors_routes: list = [], - client_max_size: int = 1024**4, - work_dir: str = 'data', - data_dir: str = 'data' - ): + self, + modules: dict = {}, + host: str = "127.0.0.1", + port: int = 8088, + secure: bool = False, + certfile: str = None, + keyfile: str = None, + cors: bool = False, + cors_routes: list = [], + client_max_size: int = DEFAULT_CLIENT_MAX_SIZE, + work_dir: str = "data", + data_dir: str = "data", + ): self.instance = nanoid.generate(size=10) self.modules = modules @@ -662,18 +1360,76 @@ def __init__( self.pending_ws_requests = {} self.interrupt_flag = False + self._forced_restart_timer = None + supervisor_queue_state = os.environ.get("MODIFF_SUPERVISOR_QUEUE_STATE") + # The queue snapshot is a single-writer contract owned by a worker + # explicitly launched by `main.py`'s process supervisor. Standalone + # WebServer instances (tests, embeddings, tools) must never guess the + # production snapshot path and overwrite an active run. + self._supervisor_queue_state_path = Path(supervisor_queue_state) if supervisor_queue_state else None + self._supervisor_queue_state_lock = threading.RLock() if supervisor_queue_state else None + self._supervisor_queue_last_write = 0.0 self.node_cache = {} + self._active_graph_node_ids = set() + self._last_auto_model_family = None + self._last_auto_resource_signature = None + self._last_runtime_fingerprint = None + self._runtime_resource_lock = threading.RLock() + self._runtime_resource_process = None + self._runtime_resource_cached_at = 0.0 + self._runtime_resource_cached_snapshot = None + self._runtime_disk_activity_sampler = DiskActivitySampler() + try: + import psutil + + # cpu_percent is interval based. Keep one Process instance and + # prime both counters once so later samples describe the interval + # between requests instead of repeatedly returning a first-call 0. + psutil.cpu_percent(interval=None) + self._runtime_resource_process = psutil.Process() + self._runtime_resource_process.cpu_percent(interval=None) + except Exception: + self._runtime_resource_process = None self.queued_tasks = {} self.current_task = {} self.recent_tasks = [] + if supervisor_queue_state: + try: + persisted_queue = json.loads(self._supervisor_queue_state_path.read_text(encoding="utf-8")) + persisted_recent = persisted_queue.get("recent") if isinstance(persisted_queue, dict) else None + if isinstance(persisted_recent, list): + self.recent_tasks = [item for item in persisted_recent if isinstance(item, dict)][:30] + except (OSError, TypeError, ValueError): + pass + self.task_graphs = {} + self.optimization_jobs = {} self.main_queue = asyncio.Queue() self.background_queue = asyncio.Queue() self._shutdown_event = asyncio.Event() self.studio_history_lock = asyncio.Lock() + # Graph execution runs in an executor thread while the Studio output + # routes run on the aiohttp loop. The asyncio lock cannot serialize + # those two callers, so protect the shared history file with a small + # process-local lock as well. + self.studio_history_file_lock = threading.RLock() self.hf_download_semaphore = asyncio.Semaphore(2) self.hf_download_tasks = {} + # ROCm and MPS commonly use system RAM as accelerator memory. Loading a + # large pipeline while hf-xet is assembling another model can exhaust + # the same physical pool and let the kernel kill the app. Keep graph + # execution and app-managed model I/O mutually exclusive on those + # shared-memory runtimes; discrete CUDA retains concurrent downloads. + try: + import torch + + self.serialize_model_io = bool(getattr(torch.version, "hip", None)) or bool( + getattr(getattr(torch.backends, "mps", None), "is_available", lambda: False)() + ) + except Exception: + self.serialize_model_io = False + self.model_io_lock = asyncio.Lock() self.main_worker_task = None self.background_worker_task = None @@ -685,87 +1441,129 @@ def __init__( self.ssl_context = None if secure and certfile and keyfile: import ssl + self.ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) self.ssl_context.load_cert_chain(certfile, keyfile) self.client_max_size = client_max_size self.work_dir = work_dir self.data_dir = data_dir - self.app = web.Application(client_max_size=self.client_max_size) - - # set up the routes - self.app.add_routes([ - web.static('/assets', 'web/assets', append_version=True), - web.static('/template-gallery', 'web/template-gallery', append_version=True), - web.get('/', self.index), - web.get('/favicon.ico', self.favicon), - web.get('/ws', self.websocket), - web.get(r'/nodes{id:/?([\w\d_-]+/[\w\d_-]+)?}', self.nodes), - web.post('/fields/action', self.field_action), - web.get('/cache/{node}/{field}', self.cache), - web.get('/cache/{node}/{field}/{index}', self.cache), - web.delete('/cache', self.delete_cache), - web.get('/listdir', self.listdir), - web.get('/listgraphs', self.listgraphs), - web.get('/file', self.fileGet), - web.post('/file', self.filePost), - web.get('/preview', self.preview), - web.post('/graph', self.graph), - web.get('/queue', self.get_queue), - web.delete('/queue/{task_id}', self.delete_task), - web.get('/stop', self.stop_execution), - web.get('/health', self.runtime_status), - web.get('/runtime/status', self.runtime_status), - web.get('/system_stats', self.system_stats), - web.get('/runtime/gpu_processes', self.runtime_gpu_processes), - web.post('/runtime/gpu_cleanup', self.runtime_gpu_cleanup), - web.get('/model_capabilities', self.model_capabilities), - web.post('/auto_resource/plan', self.auto_resource_plan), - web.post('/auto_resource/plans', self.auto_resource_plans), - web.get('/auto_resource/history', self.auto_resource_history), - web.delete('/auto_resource/history', self.auto_resource_history_clear), - web.get('/model_fingerprints', self.model_fingerprints), - web.get('/local_models', self.local_models), - web.get('/hf_cache', self.hf_cache), - web.post('/hf_token', self.hf_token), - web.get('/model_cache/diagnostics', self.model_cache_diagnostics), - web.get('/custom_modules', self.custom_modules_list), - web.post('/custom_modules/refresh', self.custom_modules_refresh), - web.post('/custom_modules/install', self.custom_modules_install), - web.post('/custom_modules/{name}/update', self.custom_modules_update), - web.post('/custom_modules/{name}/disable', self.custom_modules_disable), - web.post('/custom_modules/{name}/enable', self.custom_modules_enable), - web.get('/studio_outputs', self.studio_outputs_get), - web.post('/studio_outputs', self.studio_outputs_post), - web.patch('/studio_outputs/{output_id}', self.studio_outputs_patch), - web.delete('/studio_outputs/{output_id}', self.studio_outputs_delete), - web.get('/studio/blocks', self.studio_blocks_get), - web.post('/studio/blocks', self.studio_blocks_post), - web.get('/studio/blocks/{block_id}', self.studio_block_get), - web.delete('/studio/blocks/{block_id}', self.studio_block_delete), - web.get('/workflow_shares', self.workflow_shares_list), - web.post('/workflows/share', self.workflow_share_post), - web.get('/workflows/share/{share_id}/media/{filename}', self.workflow_share_media_get), - web.get('/workflows/share/{share_id}', self.workflow_share_get), - web.delete('/hf_cache/{hash}', self.hf_cache_delete), - web.get('/hf_hub', self.hf_hub), - web.get('/hf_download', self.hf_download), - web.get('/static/{module}/{file}', self.user_assets), - web.get('/stream', self.stream) - ]) + # Prime interval counters at startup so the first browser request can + # usually report active time instead of waiting for a second poll. + self._runtime_disk_activity_sampler.sample(self.data_dir) + self.app = web.Application( + client_max_size=self.client_max_size, + middlewares=[self._mutation_origin_middleware], + ) + + # A remote Gallery build resolves media directly from its immutable + # public Hugging Face Dataset and intentionally has no local Gallery + # directory. Offline/local builds materialize that directory and keep + # the same-origin route. + routes = [ + web.static("/assets", "web/assets", append_version=True), + ] + if TEMPLATE_GALLERY_ROOT.is_dir(): + routes.append(web.static("/template-gallery", str(TEMPLATE_GALLERY_ROOT), append_version=True)) + routes.extend( + [ + web.get("/", self.index), + web.get("/favicon.ico", self.favicon), + web.get("/ws", self.websocket), + web.get(r"/nodes{id:/?([\w\d_-]+/[\w\d_-]+)?}", self.nodes), + web.post("/fields/action", self.field_action), + web.get("/cache/{node}/{field}", self.cache), + web.get("/cache/{node}/{field}/{index}", self.cache), + web.delete("/cache", self.delete_cache), + web.get("/listdir", self.listdir), + web.get("/listgraphs", self.listgraphs), + web.get("/workflows", self.workflows_list), + web.get("/workflows/{workflow_id}", self.workflow_get), + web.put("/workflows/{workflow_id}", self.workflow_put), + web.delete("/workflows/{workflow_id}", self.workflow_delete), + web.get("/file", self.fileGet), + web.post("/file", self.filePost), + web.get("/media/capabilities", self.media_capabilities), + web.get("/media/probe", self.media_probe), + web.get("/media/export", self.media_export), + web.get("/media/preview", self.media_preview), + web.get("/preview", self.preview), + web.post("/graph", self.graph), + web.get("/queue", self.get_queue), + web.get("/runs/{task_id}", self.get_run), + web.delete("/queue/{task_id}", self.delete_task), + web.post("/stop", self.stop_execution), + web.get("/health", self.runtime_status), + web.get("/runtime/status", self.runtime_status), + web.get("/runtime/resources", self.runtime_resources), + web.get("/runtime/options", self.runtime_options), + web.get("/runtime/optimizations", self.runtime_optimizations), + web.post("/runtime/optimizations/install", self.runtime_optimization_install), + web.get("/runtime/optimizations/jobs/{job_id}", self.runtime_optimization_job), + web.post("/runtime/optimizations/activate", self.runtime_optimization_activate), + web.post("/runtime/optimizations/rollback", self.runtime_optimization_rollback), + web.post("/runtime/optimizations/enable", self.runtime_optimization_enable), + web.post("/runtime/optimizations/probe", self.runtime_optimization_probe), + web.get("/runtime/optimizations/receipts", self.runtime_optimization_receipts), + web.post("/runtime/optimizations/qualify", self.runtime_optimization_qualify), + web.get("/system_stats", self.system_stats), + web.get("/runtime/gpu_processes", self.runtime_gpu_processes), + web.post("/runtime/gpu_cleanup", self.runtime_gpu_cleanup), + web.get("/media_assets", self.media_assets_list), + web.delete("/media_assets", self.media_assets_cleanup), + web.get("/model_capabilities", self.model_capabilities), + web.get("/model_artifact_catalog", self.model_artifact_catalog), + web.post("/auto_resource/plan", self.auto_resource_plan), + web.post("/auto_resource/plans", self.auto_resource_plans), + web.get("/auto_resource/history", self.auto_resource_history), + web.delete("/auto_resource/history", self.auto_resource_history_clear), + web.get("/model_fingerprints", self.model_fingerprints), + web.get("/local_models", self.local_models), + web.get("/hf_cache", self.hf_cache), + web.post("/hf_token", self.hf_token), + web.get("/model_cache/diagnostics", self.model_cache_diagnostics), + web.get("/custom_modules", self.custom_modules_list), + web.post("/custom_modules/refresh", self.custom_modules_refresh), + web.post("/custom_modules/install", self.custom_modules_install), + web.post("/custom_modules/{name}/update", self.custom_modules_update), + web.post("/custom_modules/{name}/disable", self.custom_modules_disable), + web.post("/custom_modules/{name}/enable", self.custom_modules_enable), + web.get("/studio_outputs", self.studio_outputs_get), + web.post("/studio_outputs", self.studio_outputs_post), + web.patch("/studio_outputs/{output_id}", self.studio_outputs_patch), + web.delete("/studio_outputs/{output_id}", self.studio_outputs_delete), + web.get("/studio/blocks", self.studio_blocks_get), + web.post("/studio/blocks", self.studio_blocks_post), + web.get("/studio/blocks/{block_id}", self.studio_block_get), + web.delete("/studio/blocks/{block_id}", self.studio_block_delete), + web.get("/workflow_shares", self.workflow_shares_list), + web.post("/workflows/share", self.workflow_share_post), + web.get("/workflows/share/{share_id}/media/{filename}", self.workflow_share_media_get), + web.get("/workflows/share/{share_id}", self.workflow_share_get), + web.delete("/hf_cache/{hash}", self.hf_cache_delete), + web.get("/hf_hub", self.hf_hub), + web.post("/hf_download", self.hf_download), + web.get("/static/{module}/{file}", self.user_assets), + web.get("/stream", self.stream), + ] + ) + self.app.add_routes(routes) # serve the user assets try: - self.app.add_routes(web.static('/user', 'web/user', append_version=True)) - except Exception as e: + self.app.add_routes(web.static("/user", "web/user", append_version=True)) + except Exception: pass # set up the cors routes if cors: - cors = cors_setup(self.app, defaults={ - cors_route: ResourceOptions(allow_credentials=True, expose_headers="*", allow_headers="*") - for cors_route in cors_routes - }) + cors = cors_setup( + self.app, + defaults={ + cors_route: ResourceOptions(allow_credentials=True, expose_headers="*", allow_headers="*") + for cors_route in cors_routes + }, + ) for route in list(self.app.router.routes()): cors.add(route) @@ -787,6 +1585,7 @@ async def run(self): await self.runner.setup() self.site = web.TCPSite(self.runner, host=self.host, port=self.port, ssl_context=self.ssl_context) await self.site.start() + self._persist_supervisor_queue_state(force=True) async def cleanup(self): # Signal shutdown to workers @@ -808,12 +1607,9 @@ async def cleanup(self): if close_coroutines: try: - results = await asyncio.wait_for( - asyncio.gather(*close_coroutines, return_exceptions=True), - timeout=0.5 - ) + await asyncio.wait_for(asyncio.gather(*close_coroutines, return_exceptions=True), timeout=0.5) except asyncio.TimeoutError: - logger.warning(f"Websocket connections did not close within timeout, forcing shutdown") + logger.warning("Websocket connections did not close within timeout, forcing shutdown") # Clear the sessions dict self.ws_sessions.clear() @@ -838,10 +1634,7 @@ async def cleanup(self): tasks_to_wait = [task for task in [self.main_worker_task, self.background_worker_task] if task] if tasks_to_wait: try: - await asyncio.wait_for( - asyncio.gather(*tasks_to_wait, return_exceptions=True), - timeout=2.0 - ) + await asyncio.wait_for(asyncio.gather(*tasks_to_wait, return_exceptions=True), timeout=2.0) except asyncio.TimeoutError: logger.warning("Worker tasks did not finish within timeout, forcing shutdown") @@ -854,14 +1647,16 @@ async def cleanup(self): Queue ╰───────────────╯ """ + def _current_task_snapshot(self): if not self.current_task: return None + runtime_hints = self.current_task.get("runtimeHints") return { - "task_id": self.current_task["task_id"], - "name": self.current_task["name"], - "sid": self.current_task["sid"], - "started_at": self.current_task["started_at"], + "task_id": self.current_task.get("task_id"), + "name": self.current_task.get("name") or "Graph execution", + "sid": self.current_task.get("sid"), + "started_at": self.current_task.get("started_at"), "updated_at": self.current_task.get("updated_at"), "progress": self.current_task.get("progress", 0), "status": "running", @@ -873,17 +1668,69 @@ def _current_task_snapshot(self): "message": self.current_task.get("message"), "current_step": self.current_task.get("current_step"), "total_steps": self.current_task.get("total_steps"), + "component": self.current_task.get("component"), + "shard_current": self.current_task.get("shard_current"), + "shard_total": self.current_task.get("shard_total"), "elapsed_seconds": self.current_task.get("elapsed_seconds"), "average_step_seconds": self.current_task.get("average_step_seconds"), "eta_seconds": self.current_task.get("eta_seconds"), + "last_heartbeat_at": self.current_task.get("last_heartbeat_at"), + "resource_snapshot": self.current_task.get("resource_snapshot"), + "phase_timings": self.current_task.get("phase_timings"), "runtimeFingerprint": self.current_task.get("runtimeFingerprint"), + "resourceCandidateId": self.current_task.get("resourceCandidateId"), + "runtimeMeasurement": self.current_task.get("runtimeMeasurement"), "deterministicMode": self.current_task.get("deterministicMode"), + **self._current_run_identity_payload(), + **self._run_navigation_payload(runtime_hints), + } + + def _initial_graph_execution_state(self, args): + """Describe the first executable node before the worker enters model code.""" + graph = args[0] if isinstance(args, tuple) and args else None + if not isinstance(graph, dict): + return {} + nodes = graph.get("nodes") + paths = graph.get("paths") + if not isinstance(nodes, dict) or not isinstance(paths, list): + return {} + first_node_id = next( + ( + node_id + for path in paths + if isinstance(path, list) + for node_id in path + if node_id in nodes and isinstance(nodes[node_id], dict) + ), + None, + ) + if first_node_id is None: + return {} + node = nodes[first_node_id] + module = str(node.get("module") or "") + action = str(node.get("action") or "") + phase = node_execution_phase(module, action) + return { + "current_node": first_node_id, + "current_node_name": f"{module}.{action}".strip("."), + "node_progress": -1, + "phase": phase, + "message": node_execution_message(module, action, phase), + "updated_at": time.time(), } def _record_terminal_task(self, status, *, error_payload=None): if not self.current_task: return None completed_at = time.time() + active_phase = self.current_task.get("phase") + active_phase_started_at = self.current_task.get("_phase_started_at") + phase_timings = self.current_task.setdefault("phase_timings", {}) + if active_phase and isinstance(phase_timings, dict) and isinstance(active_phase_started_at, (int, float)): + phase_timings[active_phase] = float(phase_timings.get(active_phase, 0.0)) + max( + 0.0, completed_at - float(active_phase_started_at) + ) + self.current_task["_phase_started_at"] = completed_at entry = { "task_id": self.current_task.get("task_id"), "name": self.current_task.get("name"), @@ -901,16 +1748,47 @@ def _record_terminal_task(self, status, *, error_payload=None): "message": self.current_task.get("message"), "current_step": self.current_task.get("current_step"), "total_steps": self.current_task.get("total_steps"), + "component": self.current_task.get("component"), + "shard_current": self.current_task.get("shard_current"), + "shard_total": self.current_task.get("shard_total"), "elapsed_seconds": self.current_task.get("elapsed_seconds"), "average_step_seconds": self.current_task.get("average_step_seconds"), "eta_seconds": self.current_task.get("eta_seconds"), + "last_heartbeat_at": self.current_task.get("last_heartbeat_at"), + "resource_snapshot": self.current_task.get("resource_snapshot"), + "phase_timings": self.current_task.get("phase_timings"), "runtimeFingerprint": self.current_task.get("runtimeFingerprint"), + "resourceCandidateId": self.current_task.get("resourceCandidateId"), + "runtimeMeasurement": self.current_task.get("runtimeMeasurement"), + **self._current_run_identity_payload(), + # Retain navigation metadata with recent runs as well as the live + # queue snapshot. A refreshed client can then open the originating + # workflow (or its failure details) even after execution completed + # while it was disconnected. + **self._run_navigation_payload(self.current_task.get("runtimeHints")), } if isinstance(error_payload, dict): - for key in ("message", "error", "exception_type", "category", "error_code", "recovery_hint", "node", "node_name"): + for key in ( + "message", + "error", + "exception_type", + "category", + "error_code", + "recovery_hint", + "node", + "node_name", + "oom", + ): if error_payload.get(key) is not None: entry[key] = error_payload.get(key) - self.recent_tasks = [entry, *[item for item in self.recent_tasks if item.get("task_id") != entry["task_id"]]][:30] + self.recent_tasks = [entry, *[item for item in self.recent_tasks if item.get("task_id") != entry["task_id"]]][ + :30 + ] + retained_ids = {str(item.get("task_id")) for item in self.recent_tasks if item.get("task_id")} + retained_ids.update(str(value) for value in self.queued_tasks) + if self.current_task.get("task_id"): + retained_ids.add(str(self.current_task["task_id"])) + self.task_graphs = {key: value for key, value in self.task_graphs.items() if key in retained_ids} return entry def record_node_progress(self, payload): @@ -921,13 +1799,33 @@ def record_node_progress(self, payload): return payload now = time.time() node_progress = payload.get("progress") - self.current_task.update({ - "updated_at": now, - "current_node": payload.get("node") or self.current_task.get("current_node"), - "node_progress": node_progress, - "phase": payload.get("phase") or self.current_task.get("phase"), - "message": payload.get("message") or self.current_task.get("message"), - }) + prior_node_progress = self.current_task.get("node_progress") + prior_current_step = self.current_task.get("current_step") + prior_phase = self.current_task.get("phase") + next_phase = payload.get("phase") or prior_phase + phase_started_at = self.current_task.get("_phase_started_at") + phase_timings = self.current_task.setdefault("phase_timings", {}) + if not isinstance(phase_timings, dict): + phase_timings = {} + self.current_task["phase_timings"] = phase_timings + if next_phase != prior_phase: + if prior_phase and isinstance(phase_started_at, (int, float)): + phase_timings[prior_phase] = float(phase_timings.get(prior_phase, 0.0)) + max( + 0.0, now - float(phase_started_at) + ) + self.current_task["_phase_started_at"] = now + elif not isinstance(phase_started_at, (int, float)): + self.current_task["_phase_started_at"] = now + self.current_task.update( + { + "updated_at": now, + "last_heartbeat_at": payload.get("last_heartbeat_at") or now, + "current_node": payload.get("node") or self.current_task.get("current_node"), + "node_progress": node_progress, + "phase": next_phase, + "message": payload.get("message") or self.current_task.get("message"), + } + ) # Node lifecycle events without step metrics must not erase the latest # denoising sample. Preserving the final sample gives reconnects and # terminal receipts an honest duration/step record through decode/save. @@ -937,6 +1835,10 @@ def record_node_progress(self, payload): "elapsed_seconds", "average_step_seconds", "eta_seconds", + "component", + "shard_current", + "shard_total", + "resource_snapshot", ): if payload.get(field) is not None: self.current_task[field] = payload.get(field) @@ -947,18 +1849,36 @@ def record_node_progress(self, payload): self.current_task["progress"] = int(overall) payload["overall_progress"] = int(overall) payload["updated_at"] = now + payload["last_heartbeat_at"] = self.current_task.get("last_heartbeat_at") + payload["phase_timings"] = deepcopy(phase_timings) + # The node-start snapshot is force-written with indeterminate progress. + # A generator often publishes 0/N immediately afterward, inside the + # normal 200 ms write throttle, and may then spend minutes in its first + # offloaded model step. Force only that indeterminate-to-measurable + # transition so a refreshed client and the process-external + # notification shelf retain honest denoising state throughout it. + first_measurable_sample = ( + isinstance(node_progress, (int, float)) + and node_progress >= 0 + and (not isinstance(prior_node_progress, (int, float)) or prior_node_progress < 0) + ) or ( + payload.get("current_step") == 0 and prior_current_step is None and payload.get("total_steps") is not None + ) + self._persist_supervisor_queue_state(force=first_measurable_sample) return payload def _get_queue(self): - #task_list_sorted = {k: v for k, v in sorted(self.queued_tasks.items(), key=lambda x: x[1]['queued_at'], reverse=True)} + # task_list_sorted = {k: v for k, v in sorted(self.queued_tasks.items(), key=lambda x: x[1]['queued_at'], reverse=True)} # filter out keys that are not needed for the client queued_tasks = { k: { - 'name': v['name'], - 'sid': v['sid'], - 'queued_at': v['queued_at'], - 'task_id': k, - 'queue_position': index + 1, + "name": v["name"], + "sid": v["sid"], + "queued_at": v["queued_at"], + "task_id": k, + "queue_position": index + 1, + **self._run_identity_payload(v.get("runtimeHints")), + **self._run_navigation_payload(v.get("runtimeHints")), } for index, (k, v) in enumerate(self.queued_tasks.items()) } @@ -967,29 +1887,88 @@ def _get_queue(self): return queued_tasks, current_task - async def queue_task(self, task, args, future, sid, name=None): + def _persist_supervisor_queue_state(self, *, force=False): + """Expose queue truth to the process-external emergency control plane.""" + path = getattr(self, "_supervisor_queue_state_path", None) + lock = getattr(self, "_supervisor_queue_state_lock", None) + if path is None or lock is None: + # Lightweight WebServer fixtures and unsupervised embeddings do + # not configure the process-external control plane. + return + now = time.monotonic() + last_write = getattr(self, "_supervisor_queue_last_write", 0.0) + if not force and now - last_write < 0.2: + return + with lock: + now = time.monotonic() + last_write = getattr(self, "_supervisor_queue_last_write", 0.0) + if not force and now - last_write < 0.2: + return + queued, current = self._get_queue() + payload = { + "workerPid": os.getpid(), + "updatedAt": time.time(), + "queued": queued, + "current": current, + "recent": self.recent_tasks, + } + temporary = path.with_suffix(path.suffix + ".tmp") + try: + path.parent.mkdir(parents=True, exist_ok=True) + temporary.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8") + os.replace(temporary, path) + self._supervisor_queue_last_write = now + except Exception: + logger.warning("Could not persist supervisor queue state", exc_info=True) + try: + temporary.unlink(missing_ok=True) + except OSError: + pass + + async def queue_task(self, task, args, future, sid, name=None, runtime_hints=None): task_id = nanoid.generate(size=12) - task_name = name or f'Unnamed task ({task.__name__})' + task_name = name or f"Unnamed task ({task.__name__})" + graph = ( + args[0] + if task_name == "Graph execution" and isinstance(args, tuple) and args and isinstance(args[0], dict) + else None + ) + runtime_hints = ( + self._coerce_runtime_hints(graph.get("runtimeHints")) + if graph + else self._coerce_runtime_hints(runtime_hints) + ) self.queued_tasks[task_id] = { - 'task': task, - 'args': args, - 'future': future, + "task": task, + "args": args, + "future": future, "sid": sid, "queued_at": time.time(), "name": task_name, + "runtimeHints": runtime_hints, } + preview_state = None + if graph is not None: + self.task_graphs[task_id] = deepcopy(graph) + preview_state = self._mark_studio_preview_slots_pending(graph, task_id) await self.main_queue.put((task, args, future, task_id)) + self._persist_supervisor_queue_state(force=True) task_list, current_task = self._get_queue() - self.queue_message({ + queued_message = { "type": "task_queued", "task_id": task_id, "sid": sid, + **self._run_identity_payload(runtime_hints), "queued": task_list, "current": current_task, - }) + } + if preview_state and preview_state["previewSlots"]: + queued_message["preview_slots"] = preview_state["previewSlots"] + queued_message["preview_state_revision"] = preview_state["revision"] + self.queue_message(queued_message) return task_id @@ -998,32 +1977,141 @@ async def get_queue(self, _): HTTP endpoint to return the tasks queue and the current task. """ task_list, current_task = self._get_queue() - return web.json_response({ - "queued": task_list, - "current": current_task, - "recent": self.recent_tasks, - }) + return web.json_response( + { + "queued": task_list, + "current": current_task, + # Completed workflow snapshots are available lazily from + # /runs/{task_id}; do not resend dozens of full graphs on every + # queue poll. + "recent": compact_task_history(self.recent_tasks), + } + ) + + def _studio_outputs_for_run(self, task_id, client_run_id=None): + """Return persisted outputs whose recorded run identity matches exactly.""" + normalized_task_id = str(task_id or "").strip() + normalized_client_run_id = str(client_run_id or "").strip() + if not normalized_task_id: + return [] + + def identity_values(output, key, provenance_key): + values = set() + + def add(value): + if value is None: + return + normalized = str(value).strip() + if normalized: + values.add(normalized) + + add(output.get(key)) + for container_key in ("provenance", "backendProvenance"): + container = output.get(container_key) + if isinstance(container, dict): + add(container.get(provenance_key)) + media_items = output.get("mediaItems") + if isinstance(media_items, list): + for item in media_items: + if isinstance(item, dict): + add(item.get(key)) + return values + + matches = [] + for output in self._read_studio_outputs(): + if not isinstance(output, dict): + continue + task_ids = identity_values(output, "taskId", "backendExecutionId") + if task_ids != {normalized_task_id}: + continue + if normalized_client_run_id: + client_run_ids = identity_values(output, "clientRunId", "clientRunId") + # Exact task identity is sufficient for legacy records that + # predate client-run IDs. When present, however, every recorded + # client identity must agree with the originating run. + if client_run_ids and client_run_ids != {normalized_client_run_id}: + continue + matches.append(output) + return matches + + async def get_run(self, request): + task_id = request.match_info.get("task_id") + task = None + if self.current_task and self.current_task.get("task_id") == task_id: + task = self._current_task_snapshot() + elif task_id in self.queued_tasks: + task = self._get_queue()[0].get(task_id) + else: + task = next((item for item in self.recent_tasks if item.get("task_id") == task_id), None) + if task is None: + return web.json_response({"error": True, "message": "Run not found."}, status=404) + graph = self.task_graphs.get(task_id) + runtime_hints = graph.get("runtimeHints") if isinstance(graph, dict) else None + client_run_id = runtime_hints.get("clientRunId") if isinstance(runtime_hints, dict) else None + if not client_run_id and isinstance(task, dict): + client_run_id = task.get("client_run_id") + outputs = self._studio_outputs_for_run(task_id, client_run_id) + if not isinstance(runtime_hints, dict): + for output in reversed(outputs): + api_graph = output.get("apiGraphSnapshot") if isinstance(output, dict) else None + persisted_hints = api_graph.get("runtimeHints") if isinstance(api_graph, dict) else None + if isinstance(persisted_hints, dict): + runtime_hints = self._coerce_runtime_hints(persisted_hints) + break + workflow_id = runtime_hints.get("workflowTabId") if isinstance(runtime_hints, dict) else None + workflow_title = runtime_hints.get("workflowTitle") if isinstance(runtime_hints, dict) else None + workflow_snapshot = runtime_hints.get("workflowSnapshot") if isinstance(runtime_hints, dict) else None + if isinstance(task, dict): + workflow_id = workflow_id or task.get("workflow_tab_id") + workflow_title = workflow_title or task.get("workflow_title") + workflow_snapshot = workflow_snapshot or task.get("workflow_snapshot") + return web.json_response( + { + "task": task, + "workflow_id": workflow_id, + "workflow_title": workflow_title, + "workflow_snapshot": workflow_snapshot, + "outputs": outputs, + } + ) async def delete_task(self, request): """ HTTP endpoint to delete a task from the queue. """ - task_id = request.match_info.get('task_id') + task_id = request.match_info.get("task_id") if task_id in self.queued_tasks: task = self.queued_tasks.pop(task_id) + self.task_graphs.pop(task_id, None) + preview_state = self._mark_studio_preview_run_terminal(task_id, "cancelled") + self._persist_supervisor_queue_state(force=True) logger.info(f"Task {task_id} {task['name']} deleted from queue.") task_list, current_task = self._get_queue() - self.queue_message({ - "type": "task_cancelled", - "task_id": task_id, - "queued": task_list, - "current": current_task, - }) - return web.json_response({"error": False, "task_id": task_id, "queued": task_list, "current": current_task}) + self.queue_message( + { + "type": "task_cancelled", + "task_id": task_id, + **self._run_identity_payload(task.get("runtimeHints")), + "queued": task_list, + "current": current_task, + } + ) + response = {"error": False, "task_id": task_id, "queued": task_list, "current": current_task} + if preview_state: + response.update( + preview_slots=preview_state["previewSlots"], + preview_state_revision=preview_state["revision"], + ) + return web.json_response(response) elif self.current_task and self.current_task["task_id"] == task_id: - return web.json_response({"error": True, "message": f"Task is already running and cannot be cancelled.", "task_id": task_id}, status=400) + return web.json_response( + {"error": True, "message": "Task is already running and cannot be cancelled.", "task_id": task_id}, + status=400, + ) - return web.json_response({"error": True, "message": f"Task not found in queue.", "task_id": task_id}, status=404) + return web.json_response( + {"error": True, "message": "Task not found in queue.", "task_id": task_id}, status=404 + ) async def _main_worker(self): try: @@ -1047,6 +2135,7 @@ async def _main_worker(self): continue current_task = self.queued_tasks.pop(task_id) + runtime_hints = current_task.get("runtimeHints") self.current_task = { "task_id": task_id, @@ -1057,24 +2146,70 @@ async def _main_worker(self): "progress": 0, "attempt_index": 0, "args": args, + "runtimeHints": runtime_hints, + # The selected Auto recipe is known at admission time. + # Expose it throughout execution instead of leaving the + # supervisor/notification snapshot blank until the + # terminal receipt is assembled. + "resourceCandidateId": ( + runtime_hints.get("autoResourceCandidateId") if isinstance(runtime_hints, dict) else None + ), } + if current_task.get("name") == "Graph execution": + self.current_task.update(self._initial_graph_execution_state(args)) + self.current_task["_phase_started_at"] = time.time() + self.current_task["phase_timings"] = {} + self._persist_supervisor_queue_state(force=True) task_list, current_task = self._get_queue() - self.queue_message({ - "type": "task_started", - "task_id": task_id, - "attempt_index": 0, - "queued": task_list, - "current": current_task, - }) + self.queue_message( + { + "type": "task_started", + "task_id": task_id, + "attempt_index": 0, + **self._current_run_identity_payload(), + "queued": task_list, + "current": current_task, + } + ) + # Give aiohttp one scheduling turn to flush the graph-queued + # response before model loading begins in the executor. Some + # pipeline loaders hold the GIL for long stretches; without + # this grace period the client can time out even though the + # graph was accepted and is already running. + if current_task.get("name") == "Graph execution": + await asyncio.sleep(0.05) terminal_status = "completed" failure_payload = None try: if isinstance(args, tuple): - result = await self.loop.run_in_executor(None, partial(task, *args)) + callback = partial(task, *args) elif isinstance(args, dict): - result = await self.loop.run_in_executor(None, partial(task, **args)) + callback = partial(task, **args) else: - result = await self.loop.run_in_executor(None, partial(task, args)) + callback = partial(task, args) + serialize_model_io = current_task.get("name") == "Graph execution" + if serialize_model_io and self.serialize_model_io and self.model_io_lock.locked(): + self.current_task.update( + { + "phase": "waiting_for_model_io", + "message": "Waiting for the active app-managed model download to finish safely.", + "updated_at": time.time(), + } + ) + self.queue_message( + { + "type": "task_progress", + "task_id": task_id, + **self._current_run_identity_payload(), + "progress": 0, + "phase": self.current_task["phase"], + "message": self.current_task["message"], + } + ) + result = await self._run_executor_callback( + callback, + serialize_model_io=serialize_model_io, + ) if self.current_task and self.current_task.get("interrupt_requested"): terminal_status = "cancelled" @@ -1083,38 +2218,62 @@ async def _main_worker(self): elif future and terminal_status == "cancelled": future.set_exception(asyncio.CancelledError("Execution interrupted by the user.")) except Exception as e: - terminal_status = "failed" - traceback_text = ( - getattr(e, 'modiff_traceback', None) - or traceback.format_exc() - ) + interrupted_by_user = bool(self.current_task and self.current_task.get("interrupt_requested")) + terminal_status = "cancelled" if interrupted_by_user else "failed" + if interrupted_by_user: + if future: + future.set_exception(asyncio.CancelledError("Execution interrupted by the user.")) + continue + traceback_text = getattr(e, "modiff_traceback", None) or traceback.format_exc() logger.error(f"Error occurred in {traceback_text}") task_list, _ = self._get_queue() failure_payload = self._exception_payload( e, task_id=task_id, sid=self.current_task["sid"] if self.current_task else None, - node_id=getattr(e, 'modiff_node_id', None), - node_name=getattr(e, 'modiff_node_name', None), + node_id=getattr(e, "modiff_node_id", None), + node_name=getattr(e, "modiff_node_name", None), traceback_text=traceback_text, ) - self._record_auto_resource_failure(e, { - 'category': failure_payload.get('category'), - 'error_code': failure_payload.get('error_code'), - 'message': failure_payload.get('message'), - 'recovery_hint': failure_payload.get('recovery_hint'), - }) + self._record_auto_resource_failure( + e, + { + "category": failure_payload.get("category"), + "error_code": failure_payload.get("error_code"), + "message": failure_payload.get("message"), + "recovery_hint": failure_payload.get("recovery_hint"), + }, + ) if future: future.set_exception(e) finally: + runtime_cleanup = None + if terminal_status in {"cancelled", "failed"}: + # A failed or cancelled graph must not leave model, + # node, component, or allocator ownership behind for + # the next queued run. Cancellation is cooperative + # inside third-party model loading, so teardown runs + # immediately after that call returns and before the + # worker advances the queue. + runtime_cleanup = await self.loop.run_in_executor( + None, + self._release_runtime_caches_for_retry, + ) + self._last_auto_model_family = None + self._last_auto_resource_signature = None if self.current_task: task_sid = self.current_task.get("sid") task_name = self.current_task.get("name") attempt_index = self.current_task.get("attempt_index") terminal_entry = self._record_terminal_task(terminal_status, error_payload=failure_payload) + preview_state = self._mark_studio_preview_run_terminal(task_id, terminal_status) task_list, _ = self._get_queue() terminal_message = { - "type": "task_completed" if terminal_status == "completed" else "task_cancelled" if terminal_status == "cancelled" else "task_failed", + "type": "task_completed" + if terminal_status == "completed" + else "task_cancelled" + if terminal_status == "cancelled" + else "task_failed", "task_id": task_id, "name": task_name, "attempt_index": attempt_index, @@ -1132,8 +2291,14 @@ async def _main_worker(self): terminal_message["message"] = "Execution interrupted by the user." elif isinstance(failure_payload, dict): terminal_message.update(failure_payload) - self.queue_message(terminal_message, task_sid) + if runtime_cleanup is not None: + terminal_message["runtimeCleanup"] = runtime_cleanup + if preview_state: + terminal_message["preview_slots"] = preview_state["previewSlots"] + terminal_message["preview_state_revision"] = preview_state["revision"] + self.queue_message(terminal_message) self.current_task = None + self._persist_supervisor_queue_state(force=True) self.main_queue.task_done() self.interrupt_flag = False @@ -1146,6 +2311,11 @@ async def _main_worker(self): finally: logger.debug("Main worker shutting down") + async def _run_executor_callback(self, callback, *, serialize_model_io=False): + if serialize_model_io and self.serialize_model_io: + async with self.model_io_lock: + return await self.loop.run_in_executor(None, callback) + return await self.loop.run_in_executor(None, callback) async def _background_worker(self): try: @@ -1170,7 +2340,7 @@ async def _background_worker(self): await task(args) except Exception as e: logger.error(f"Error processing background task: {e}") - #logger.error(f"Error occurred in {traceback.format_exc()}") + # logger.error(f"Error occurred in {traceback.format_exc()}") finally: self.background_queue.task_done() @@ -1183,7 +2353,6 @@ async def _background_worker(self): finally: logger.debug("Background worker shutting down") - """ ╭─────────────────────╮ Basic HTTP Routes @@ -1191,103 +2360,322 @@ async def _background_worker(self): """ async def index(self, _): - response = web.FileResponse('web/index.html') - response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' + response = web.FileResponse("web/index.html") + response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" response.headers["Pragma"] = "no-cache" response.headers["Expires"] = "0" return response async def favicon(self, _): - return web.FileResponse('web/favicon.ico') + return web.FileResponse("web/favicon.ico") async def user_assets(self, request): - module = request.match_info.get('module') - file = request.match_info.get('file') + module = request.match_info.get("module") + file = request.match_info.get("file") fileName = f"custom/{module}/web/{file}" if not Path(fileName).exists(): - return web.HTTPNotFound(text='File not found') + return web.HTTPNotFound(text="File not found") response = web.FileResponse(fileName) - #response.headers["Content-Type"] = "application/javascript" - response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' + # response.headers["Content-Type"] = "application/javascript" + response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" response.headers["Pragma"] = "no-cache" response.headers["Expires"] = "0" return response - """ ╭─────────────────────────╮ Nodes & Fields Routes ╰─────────────────────────╯ """ + + def _available_runtime_devices(self): + # Keep both spellings because older node contracts expose ``cpu:0`` + # while newer Diffusers loaders use the canonical ``cpu`` spelling. + # PyTorch accepts both and neither should be presented as unavailable. + devices = ["cpu", "cpu:0"] + try: + import torch as torch_runtime + + if torch_runtime.cuda.is_available(): + devices.extend(f"cuda:{index}" for index in range(max(1, torch_runtime.cuda.device_count()))) + except Exception: + torch_runtime = None + try: + xpu = getattr(torch_runtime, "xpu", None) + if xpu is not None and callable(getattr(xpu, "is_available", None)) and xpu.is_available(): + count = xpu.device_count() if callable(getattr(xpu, "device_count", None)) else 1 + devices.extend(f"xpu:{index}" for index in range(max(1, int(count)))) + except Exception: + pass + try: + mps = getattr(getattr(torch_runtime, "backends", None), "mps", None) + if mps is not None and callable(getattr(mps, "is_available", None)) and mps.is_available(): + devices.append("mps") + except Exception: + pass + return list(dict.fromkeys(devices)) + + @staticmethod + def _option_label(value, fallback): + if isinstance(value, dict): + label = value.get("label") or value.get("name") or value.get("title") or fallback + if isinstance(label, (list, tuple)): + label = next((item for item in label if str(item).strip()), fallback) + return str(label) + text = str(value) + return text if text else str(fallback) + + def _runtime_choice_capabilities(self): + cached = getattr(self, "_runtime_choice_capabilities_cache", None) + if isinstance(cached, dict): + return cached + try: + import torch as torch_runtime + from modules.DiffusersRuntime.main import build_runtime_capabilities + + devices = self._available_runtime_devices() + if any(value.startswith("cuda:") for value in devices): + device = {"type": "cuda", "device": next(value for value in devices if value.startswith("cuda:"))} + elif any(value.startswith("xpu:") for value in devices): + device = {"type": "xpu", "device": next(value for value in devices if value.startswith("xpu:"))} + elif "mps" in devices: + device = {"type": "mps", "device": "mps"} + else: + device = {"type": "cpu", "device": "cpu"} + cached = build_runtime_capabilities( + {"devices": [device]}, + torch_module=torch_runtime, + ) + except Exception as exc: + logger.debug("Could not build runtime option capabilities: %s", exc) + cached = {} + self._runtime_choice_capabilities_cache = cached + return cached + + def _runtime_choice_compatibility(self, field_name, value): + capabilities = self._runtime_choice_capabilities() + normalized_field = str(field_name or "").strip().lower() + normalized_value = str(value or "").strip() + if normalized_field == "attention_backend": + if normalized_value == "auto": + return True, None + option = (capabilities.get("attention_backends") or {}).get(normalized_value) + if isinstance(option, dict) and not option.get("available", False): + return False, str(option.get("reason") or "This attention backend is unavailable.") + quantization_fields = {"backend", "quant_type", "quantization_mode"} + if normalized_field in quantization_fields: + option = (capabilities.get("quantization_backends") or {}).get(normalized_value) + if isinstance(option, dict) and not option.get("available", False): + return False, str(option.get("reason") or "This quantization backend is unavailable.") + if normalized_field in {"dtype", "compute_dtype", "bnb_4bit_compute_dtype"} and normalized_value: + option = (capabilities.get("dtypes") or {}).get(normalized_value) + if option is False: + return False, f"{normalized_value} is not supported by the active runtime." + return True, None + + def _option_descriptors(self, field_name, field_definition): + options = field_definition.get("options") + if not isinstance(options, (list, tuple, dict)): + return None + # UI groups use ``options`` as an ordered list of child field keys, + # not as user-selectable choices. Keep that structural contract intact. + if str(field_definition.get("display") or "").strip().lower() == "ui_group": + return None + dependencies = field_definition.get("optionDependencies") + if dependencies is None: + source = field_definition.get("optionsSource") + dependencies = source if isinstance(source, dict) else None + + entries = [] + normalized_field_name = str(field_name or "").strip().lower() + is_device_field = normalized_field_name in { + "device", + "execution_device", + "generator_device", + "offload_device", + } or normalized_field_name.endswith("_device") + available_devices = set(self._available_runtime_devices()) if is_device_field else None + if available_devices is not None: + declared = options.keys() if isinstance(options, dict) else options + declared_values = {str(value) for value in declared if not str(value).startswith("__")} + runtime_values = sorted( + available_devices, + key=lambda value: ( + 0 if value.startswith(("cuda:", "xpu:")) or value == "mps" else 1, + value, + ), + ) + if isinstance(options, dict): + options = { + **options, + **{value: value for value in runtime_values if value not in declared_values}, + } + else: + options = [ + *options, + *(value for value in runtime_values if value not in declared_values), + ] + iterable = options.items() if isinstance(options, dict) else ((str(value), value) for value in options) + for value, option in iterable: + if str(value).startswith("__"): + entries.append((str(value), option)) + continue + option_value = str(value) if isinstance(options, dict) else str(option) + compatible = available_devices is None or option_value in available_devices + disabled_reason = None + if compatible: + compatible, disabled_reason = self._runtime_choice_compatibility(field_name, option_value) + descriptor = { + "schemaVersion": 1, + "value": option_value, + "label": self._option_label(option, option_value), + "compatibility": "compatible" if compatible else "incompatible", + "availability": "installed" if compatible else "unavailable", + "installationState": "installed" if compatible else "unavailable", + } + if dependencies: + descriptor["dependencies"] = deepcopy(dependencies) + if not compatible: + descriptor["disabledReason"] = disabled_reason or ( + "This device is not available in the current runtime." + if available_devices is not None + else "This option is not available in the current runtime." + ) + entries.append((str(value), descriptor)) + if isinstance(options, dict): + return {key: value for key, value in entries} + return [value for _, value in entries] + + def _runtime_option_catalog(self): + catalog = {} + for module, actions in self.modules.items(): + for action, values in actions.items(): + if values.get("hidden", False): + continue + node_options = {} + for field_name, field_definition in (values.get("params") or {}).items(): + descriptors = self._option_descriptors(field_name, field_definition) + if descriptors is not None: + node_options[field_name] = descriptors + if node_options: + catalog[f"{module}.{action}"] = node_options + return catalog + + def describe_node_params(self, params): + """Return browser-safe fields using the same versioned option contract.""" + described = deepcopy(params or {}) + for field_name, field_definition in described.items(): + if not isinstance(field_definition, dict): + continue + field_definition.pop("postProcess", None) + option_descriptors = self._option_descriptors(field_name, field_definition) + if option_descriptors is not None: + field_definition["options"] = option_descriptors + return described + + async def runtime_options(self, _request): + return web.json_response( + { + "schemaVersion": 1, + "generatedAt": time.time(), + "nodes": self._runtime_option_catalog(), + } + ) + async def nodes(self, request): - id = request.match_info.get('id', '').strip('/') + id = request.match_info.get("id", "").strip("/") modules = self.modules if id: - m, n = id.split('/') + m, n = id.split("/") if m not in modules: - return web.json_response({"error": f"The module {m} was not found. Try refreshing the page and restarting the server."}, status=404) + return web.json_response( + {"error": f"The module {m} was not found. Try refreshing the page and restarting the server."}, + status=404, + ) if n not in modules[m]: - return web.json_response({"error": f"The node {n} was not found in the module {m}. Try refreshing the page and restarting the server."}, status=404) + return web.json_response( + { + "error": f"The node {n} was not found in the module {m}. Try refreshing the page and restarting the server." + }, + status=404, + ) modules = {m: {n: modules[m][n]}} output = {} for module, actions in modules.items(): for action, values in actions.items(): - params = deepcopy(values.get('params', {})) - #spawn_fields = [] - for p in params: - if 'postProcess' in params[p]: - del params[p]["postProcess"] - #if 'spawn' in params[p] and params[p]['spawn']: - # spawn_fields.append(p) - # spawn fields are identified by the '>>>' suffix in the field key - #for p in spawn_fields: - # params[f"{p}>>>0"] = params[p] - # del params[p] + if values.get("hidden", False) and not id: + continue + params = self.describe_node_params(values.get("params", {})) output[f"{module}.{action}"] = { - 'module': module, - 'action': action, - 'type': values.get('type', 'custom'), - 'label': values.get('label', f"{module}: {action}"), - 'category': values.get('category', 'default'), - 'description': values.get('description', ''), - 'resizable': values.get('resizable', False), - 'skipParamsCheck': values.get('skipParamsCheck', False), - 'style': values.get('style', ''), - 'params': params, - 'time': [0,0,0], - 'memory': [0,0,0], - 'cache': False, + "module": module, + "action": action, + "type": values.get("type", "custom"), + "label": values.get("label", f"{module}: {action}"), + "category": values.get("category", "default"), + "description": values.get("description", ""), + "resizable": values.get("resizable", False), + "skipParamsCheck": values.get("skipParamsCheck", False), + "style": values.get("style", ""), + "params": params, + "time": [0, 0, 0], + "memory": [0, 0, 0], + "cache": False, } - return web.json_response({ - 'instance': self.instance, - 'nodes': output - }) + return web.json_response({"instance": self.instance, "nodes": output}) + + def _field_action_runtime_hints(self, data): + if not isinstance(data, dict): + return None + return self._coerce_runtime_hints( + { + "workflowTabId": data.get("workflowTabId"), + "workflowCanvasEpoch": data.get("workflowCanvasEpoch"), + } + ) + + def _execute_field_action(self, fn, identity, include_current_task, values, ref): + from modiff.NodeBase import node_message_context + + message_identity = dict(identity) if isinstance(identity, dict) else {} + if include_current_task and self.current_task: + task_id = self.current_task.get("task_id") + attempt_index = self.current_task.get("attempt_index") + if task_id: + message_identity["task_id"] = task_id + if attempt_index is not None: + message_identity["attempt_index"] = attempt_index + with node_message_context(message_identity): + return fn(values, ref) async def field_action(self, request): data = await request.json() - node = data.get('node') - sid = data.get('sid') - fn = data.get('fn') - values = data.get('values') - key = data.get('fieldKey', None) - queue = data.get('queue', False) + node = data.get("node") + sid = data.get("sid") + fn = data.get("fn") + values = data.get("values") + key = data.get("fieldKey", None) + queue = data.get("queue", False) + runtime_hints = self._field_action_runtime_hints(data) + message_identity = self._run_identity_payload(runtime_hints) + if sid: + message_identity["sid"] = sid if node not in self.node_cache: - module = data.get('module') - action = data.get('action') + module = data.get("module") + action = data.get("action") work_module = import_module(f"{module}.main") work_action = getattr(work_module, action) work_action = work_action(node_id=node) self.node_cache[node] = work_action - self.node_cache[node]._sid = sid # always update the sid as it may change over time + self.node_cache[node]._sid = sid # always update the sid as it may change over time fn = getattr(self.node_cache[node], fn) ref = { @@ -1297,30 +2685,46 @@ async def field_action(self, request): } if queue: - task_id = await self.queue_task(fn, (values, ref), None, sid, name=f"Field action") + task = partial(self._execute_field_action, fn, message_identity, True) + task_id = await self.queue_task( + task, + (values, ref), + None, + sid, + name="Field action", + runtime_hints=runtime_hints, + ) else: # Run field action in executor to avoid blocking the event loop - if not getattr(self, 'loop', None): + if not getattr(self, "loop", None): self.loop = asyncio.get_event_loop() try: - await self.loop.run_in_executor(None, partial(fn, values, ref)) + await self.loop.run_in_executor( + None, + partial(self._execute_field_action, fn, message_identity, False, values, ref), + ) except Exception as e: logger.error(f"Error executing field action synchronously: {e}") - return web.json_response({ - "error": True, - "message": f"Field action error: {e}", - "sid": sid, - "ref": ref, - }, status=500) + return web.json_response( + { + "error": True, + "message": f"Field action error: {e}", + "sid": sid, + "ref": ref, + }, + status=500, + ) task_id = None - return web.json_response({ - "error": False, - "message": f"Field action `{fn}` for node `{node}` queued for processing", - "sid": sid, - "task_id": task_id, - "ref": ref, - }) + return web.json_response( + { + "error": False, + "message": f"Field action `{fn}` for node `{node}` queued for processing", + "sid": sid, + "task_id": task_id, + "ref": ref, + } + ) """ ╭─────────────────────╮ @@ -1329,9 +2733,9 @@ async def field_action(self, request): """ async def cache(self, request): - node = request.match_info.get('node') - field = request.match_info.get('field') - index = request.match_info.get('index', None) + node = request.match_info.get("node") + field = request.match_info.get("field") + index = request.match_info.get("index", None) if node not in self.node_cache: return web.HTTPNotFound(text=f"Node {node} not found in cache.") @@ -1354,50 +2758,155 @@ async def cache(self, request): # check the registry for the type of the field module = self.node_cache[node].module_name action = self.node_cache[node].class_name - type = self.modules[module][action]['params'][field].get('type') - - filename = request.query.get('filename', f"{field}") + field_definition = self.modules[module][action]["params"][field] + type = field_definition.get("type") + fieldOptions = field_definition.get("fieldOptions", {}) + + filename = request.query.get("filename", f"{field}") + download_format = str(request.query.get("download_format") or "").strip().lower() + export_options = None + if download_format: + try: + export_options = self._media_export_options(request) + except ValueError as exc: + return web.json_response({"error": str(exc)}, status=400) charset = None if is_image_data_type(type): - format = request.query.get('format', 'WEBP').upper() - quality = request.query.get('quality', 100) - out = to_bytes(type, data, {'format': format, 'quality': quality}) - content_type = f'image/{format.lower()}' + format = request.query.get("format", "WEBP").upper() + quality = request.query.get("quality", 100) + out = to_bytes(type, data, {"format": format, "quality": quality}) + if download_format: + from modiff.media_io import export_media_bytes + + try: + export_path, content_type, export_filename = await asyncio.to_thread( + export_media_bytes, + out, + source_suffix=f".{format.lower()}", + kind="image", + format_id=download_format, + options=export_options, + cache_root=Path(self.data_dir) / ".media-exports", + ) + except (OSError, ValueError, subprocess.SubprocessError) as exc: + return web.json_response({"error": str(exc)}, status=422) + return web.FileResponse( + export_path, + headers={ + "Content-Disposition": f'attachment; filename="{export_filename}"', + "Content-Type": content_type, + "Cache-Control": "private, max-age=31536000, immutable", + }, + ) + content_type = f"image/{format.lower()}" if not str(filename).lower().endswith(f".{format.lower()}"): filename = f"{filename}.{format.lower()}" - elif type == 'text' or any(t.startswith('str') for t in type): - out = str(data).encode('utf-8') - content_type = f'text/plain' - charset = 'utf-8' - filename = f"{filename}.txt" - else: - resp = web.FileResponse(data) - resp.headers['Content-Disposition'] = f'inline; filename="{filename}"' - resp.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' - resp.headers['Pragma'] = 'no-cache' - resp.headers['Expires'] = '0' - return resp + elif type == "audio" or (isinstance(type, list) and "audio" in type): + out = to_bytes("audio", data, fieldOptions) + if download_format: + from modiff.media_io import export_media_bytes - return web.Response( - body=out, - content_type=content_type, - charset=charset, - headers={ - 'Content-Disposition': f'inline; filename="{filename}"', - 'Content-Length': str(len(out)), - 'Cache-Control': 'no-cache', - 'Pragma': 'no-cache', - 'Expires': '0' - } - ) + try: + export_path, content_type, export_filename = await asyncio.to_thread( + export_media_bytes, + out, + source_suffix=".wav", + kind="audio", + format_id=download_format, + options=export_options, + cache_root=Path(self.data_dir) / ".media-exports", + ) + except (OSError, ValueError, subprocess.SubprocessError) as exc: + return web.json_response({"error": str(exc)}, status=422) + return web.FileResponse( + export_path, + headers={ + "Content-Disposition": f'attachment; filename="{export_filename}"', + "Content-Type": content_type, + "Cache-Control": "private, max-age=31536000, immutable", + }, + ) + content_type = "audio/wav" + if not str(filename).lower().endswith(".wav"): + filename = f"{filename}.wav" + try: + download_sample_rate = parse_audio_download_sample_rate(request.query.get("download_sample_rate")) + except ValueError as exc: + return web.json_response({"error": str(exc)}, status=400) + if download_sample_rate is not None: + try: + out = resample_wav_bytes(out, download_sample_rate) + except (OSError, ValueError) as exc: + return web.json_response( + {"error": f"Could not prepare the requested WAV download: {exc}"}, + status=422, + ) + filename = audio_download_filename(filename, download_sample_rate) + elif type == "video" or (isinstance(type, list) and "video" in type): + data_path = self._resolve_managed_path_identifier(data) if isinstance(data, (str, os.PathLike)) else None + if download_format and data_path is not None and data_path.is_file(): + from modiff.media_io import export_media_file + + try: + export_path, content_type, export_filename = await asyncio.to_thread( + export_media_file, + data_path, + kind="video", + format_id=download_format, + options=export_options, + cache_root=Path(self.data_dir) / ".media-exports", + ) + except (OSError, ValueError, subprocess.SubprocessError) as exc: + return web.json_response({"error": str(exc)}, status=422) + return web.FileResponse( + export_path, + headers={ + "Content-Disposition": f'attachment; filename="{export_filename}"', + "Content-Type": content_type, + "Cache-Control": "private, max-age=31536000, immutable", + }, + ) + if data_path is not None and data_path.is_file(): + resp = web.FileResponse(data_path) + resp.headers["Content-Disposition"] = f'inline; filename="{filename}"' + resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" + return resp + return web.json_response( + {"error": "Run an Export Video node before downloading this in-memory video."}, + status=422, + ) + elif ( + type == "text" + or (isinstance(type, str) and type.startswith("str")) + or (isinstance(type, list) and any(isinstance(t, str) and t.startswith("str") for t in type)) + ): + out = str(data).encode("utf-8") + content_type = "text/plain" + charset = "utf-8" + filename = f"{filename}.txt" + else: + resp = web.FileResponse(data) + resp.headers["Content-Disposition"] = f'inline; filename="{filename}"' + resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" + resp.headers["Pragma"] = "no-cache" + resp.headers["Expires"] = "0" + return resp + + return byte_range_response( + request, + out, + content_type=content_type, + charset=charset, + filename=filename, + ) async def delete_cache(self, request): data = await request.json() - nodes = data.get('nodes', []) + nodes = data.get("nodes", []) if isinstance(nodes, str): - nodes = list(self.node_cache.keys()) if nodes == '*' else [nodes] + nodes = list(self.node_cache.keys()) if nodes == "*" else [nodes] # this might take a while because it could be freeing up VRAM for node in nodes: @@ -1408,70 +2917,259 @@ async def delete_cache(self, request): return web.json_response({"error": False, "nodes": nodes}) - """ ╭───────────────────╮ File Management ╰───────────────────╯ """ + @staticmethod + def _resolve_path_under_root(value, root): + """Resolve ``value`` and reject traversal or symlink escapes from ``root``.""" + + root_path = Path(root).expanduser().resolve(strict=False) + candidate = Path(str(value)) + if not candidate.is_absolute(): + candidate = root_path / candidate + candidate = candidate.expanduser().resolve(strict=False) + try: + candidate.relative_to(root_path) + except ValueError: + return None + return candidate + + def _resolve_managed_path_identifier(self, value): + """Resolve a public file identifier within the configured local roots.""" + + return resolve_managed_path_identifier( + value, + work_root=self.work_dir, + data_root=self.data_dir, + ) + + def _public_path_identifier(self, path): + """Return a portable identifier without exposing an absolute host path.""" + + candidate = Path(path).expanduser().resolve(strict=False) + work_root = Path(self.work_dir).expanduser().resolve(strict=False) + try: + return candidate.relative_to(work_root).as_posix() + except ValueError: + return data_path_identifier(candidate, self.data_dir) + + @staticmethod + def _loopback_host(host): + normalized = str(host or "").strip().strip("[]").lower() + if normalized == "localhost": + return True + try: + return ipaddress.ip_address(normalized).is_loopback + except ValueError: + return False + + @staticmethod + def _request_host(request): + headers = getattr(request, "headers", {}) or {} + try: + return urlparse(f"//{getattr(request, 'host', None) or headers.get('Host', '')}").hostname + except (TypeError, ValueError): + return None + + @staticmethod + def _request_peer_host(request): + try: + peer_host = getattr(request, "remote", None) + except (AttributeError, RuntimeError): + peer_host = None + if peer_host: + return peer_host + transport = getattr(request, "transport", None) + peer = transport.get_extra_info("peername") if transport is not None else None + return peer[0] if isinstance(peer, tuple) and peer else peer + + def _loopback_request_boundary(self, request): + return self._loopback_host(self._request_host(request)) and self._loopback_host( + self._request_peer_host(request) + ) + + def _trusted_browser_origin(self, request): + """Authorize one local browser or native client mutation. + + Comparing two attacker-controlled DNS hostnames is not a trust check: + a DNS-rebinding page can make its Origin and Host names equal. Require + literal loopback request/peer addresses, plus a literal loopback HTTP + Origin whenever a browser supplied one. + """ + + headers = getattr(request, "headers", {}) or {} + if not self._loopback_request_boundary(request): + return False + origin = headers.get("Origin") + if not origin: + return True + try: + parsed_origin = urlparse(origin) + except (TypeError, ValueError): + return False + return parsed_origin.scheme in {"http", "https"} and self._loopback_host(parsed_origin.hostname) + + def _trusted_websocket_origin(self, request): + """Restrict the unauthenticated WebSocket to the local trust boundary. + + Browsers always send an ``Origin`` header for a WebSocket handshake, so + a present origin must itself be loopback. Native clients, including + :class:`modiff.client.WebSocketClient`, do not necessarily send one; + those clients remain supported only when both the HTTP destination and + the connected peer are loopback. + """ + + headers = getattr(request, "headers", {}) or {} + if not self._loopback_request_boundary(request): + return False + + origin = headers.get("Origin") + if not origin: + return True + try: + parsed_origin = urlparse(origin) + except (TypeError, ValueError): + return False + return parsed_origin.scheme in {"http", "https"} and self._loopback_host(parsed_origin.hostname) + + @web.middleware + async def _mutation_origin_middleware(self, request, handler): + # Every endpoint, including read-only workflow/queue/media metadata, + # belongs to the same unauthenticated loopback trust boundary. Checking + # only mutations would still let a DNS-rebinding hostname read local + # state through an attacker-controlled Host header. + origin_error = self._untrusted_origin_response(request) + if origin_error is not None: + return origin_error + return await handler(request) + + def _untrusted_origin_response(self, request): + if self._trusted_browser_origin(request): + return None + return web.json_response( + { + "error": "Requests require a loopback client, loopback Host, and loopback browser Origin.", + "code": "untrusted_request_boundary", + }, + status=403, + ) + + def _untrusted_websocket_origin_response(self, request): + if self._trusted_websocket_origin(request): + return None + return web.json_response( + {"error": "WebSocket connections require a trusted loopback client and browser origin."}, + status=403, + ) + async def listdir(self, request): - req_basepath = self.data_dir if request.query.get('basepath') == 'data' else self.work_dir - req_path = request.query.get('path', req_basepath) - req_type = request.query.get('type', None) + from modiff.media_io import media_capabilities + + req_basepath = self.data_dir if request.query.get("basepath") == "data" else self.work_dir + req_path = request.query.get("path", req_basepath) + data_namespace = request.query.get("basepath") == "data" or is_data_path_identifier(req_path) + req_type = request.query.get("type", None) if req_type: - req_type = [t.strip() for t in req_type.lower().split(',')] + req_type = [t.strip() for t in req_type.lower().split(",")] - full_path = Path(req_path) - if not full_path.is_absolute(): - full_path = Path(req_basepath) / full_path + base_root = Path(req_basepath).expanduser().resolve(strict=False) + if is_data_path_identifier(req_path): + base_root = Path(self.data_dir).expanduser().resolve(strict=False) + try: + full_path = resolve_data_path_identifier(req_path, base_root) + except ValueError: + full_path = None + else: + full_path = self._resolve_path_under_root(req_path, base_root) + runtime_media = media_capabilities()["media"] file_types = { - 'image': ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'ico', 'webp'], - 'audio': ['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac', 'wma', 'm4b', 'm4p', 'm4r'], - 'video': ['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'mpeg', 'mpg', 'm4v', 'webm'], - 'text': ['txt', 'md', 'csv', 'json', 'xml', 'yaml', 'yml', 'ini', 'toml', 'cfg', 'conf', 'log', 'html', 'css', 'js', 'ts', 'py', 'rb', 'php', 'sql', 'sh', 'bash'], - 'archive': ['zip', 'rar', 'tar', 'gz', 'bz2', '7z'], - '3d': ['glb', 'gltf', 'stl', 'obj', 'fbx', 'dae', 'ply', '3ds', 'max', 'blend'], + "image": [extension.lstrip(".") for extension in runtime_media["image"]["importExtensions"]], + "audio": [extension.lstrip(".") for extension in runtime_media["audio"]["importExtensions"]], + "video": [extension.lstrip(".") for extension in runtime_media["video"]["importExtensions"]], + "text": [ + "txt", + "md", + "csv", + "json", + "xml", + "yaml", + "yml", + "ini", + "toml", + "cfg", + "conf", + "log", + "html", + "css", + "js", + "ts", + "py", + "rb", + "php", + "sql", + "sh", + "bash", + ], + "archive": ["zip", "rar", "tar", "gz", "bz2", "7z"], + "3d": ["glb", "gltf", "stl", "obj", "fbx", "dae", "ply", "3ds", "max", "blend"], } - if not str(full_path).startswith(self.work_dir): - return web.json_response({"error": f"Cannot access paths outside of {self.work_dir}."}, status=403) + if full_path is None: + return web.json_response({"error": f"Cannot access paths outside of {base_root}."}, status=403) contents = { - 'files': [], - 'path': '', - 'abs_path': '', + "files": [], + "path": "", + "abs_path": "", } try: if full_path.exists(): - contents['path'] = str(full_path.relative_to(self.work_dir)) - contents['abs_path'] = str(full_path) + contents["path"] = ( + data_path_identifier(full_path, self.data_dir) + if data_namespace + else self._public_path_identifier(full_path) + ) + # Kept for the existing response schema, but deliberately no + # longer contains an absolute host path. + contents["abs_path"] = contents["path"] for item in full_path.iterdir(): - suffix = item.suffix.lstrip('.').lower() + suffix = item.suffix.lstrip(".").lower() # if any of the requested types don't match the file type, skip it - if not item.is_dir() and req_type and not any(suffix in exts for ftype, exts in file_types.items() if ftype in req_type): + if ( + not item.is_dir() + and req_type + and not any(suffix in exts for ftype, exts in file_types.items() if ftype in req_type) + ): continue file = { - 'is_dir': item.is_dir(), - 'is_hidden': item.name.startswith('.'), # TODO: Windows: bool(os.stat(item).st_mode & stat.FILE_ATTRIBUTE_HIDDEN), - 'name': item.name, - 'path': str(item.relative_to(self.work_dir)), + "is_dir": item.is_dir(), + "is_hidden": is_hidden_path(item), + "name": item.name, + "path": ( + data_path_identifier(item, self.data_dir) + if data_namespace + else self._public_path_identifier(item) + ), #'abs_path': str(item), - 'modified': item.stat().st_mtime, - 'size': None, - 'ext': None, - 'type': None, + "modified": item.stat().st_mtime, + "size": None, + "ext": None, + "type": None, } if not item.is_dir(): - file['size'] = item.stat().st_size - file['ext'] = suffix - file['type'] = next((ftype for ftype, exts in file_types.items() if suffix in exts), 'other') + file["size"] = item.stat().st_size + file["ext"] = suffix + file["type"] = next((ftype for ftype, exts in file_types.items() if suffix in exts), "other") - contents['files'].append(file) + contents["files"].append(file) return web.json_response(contents) else: @@ -1482,52 +3180,79 @@ async def listdir(self, request): return web.json_response({"error": str(e)}, status=500) async def listgraphs(self, request): - path = Path(self.data_dir) / 'graphs' + path = Path(self.data_dir) / "graphs" if not path.exists(): return web.json_response({"error": True, "message": "No graph directory found."}, status=404) - graphs = list_files(str(path), recursive=True, extensions=['json']) + graphs = list_files(str(path), recursive=True, extensions=["json"]) + workflow_metadata: dict[str, dict] = {} + manifest_path = Path(self.data_dir) / "workflow-library-manifest.json" + if manifest_path.exists(): + try: + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + manifest_workflows = [ + *manifest.get("workflows", []), + *manifest.get("experimentalWorkflows", []), + ] + workflow_metadata = { + str(item.get("graphPath", "")).replace("\\", "/"): item + for item in manifest_workflows + if isinstance(item, dict) and item.get("graphPath") + } + except (OSError, ValueError, TypeError) as exc: + logger.warning(f"Could not read workflow library manifest: {exc}") # build a nested tree of directories and files tree: list[dict] = [] for g in graphs: - rel_dir = g.get('rel_directory') or '.' + rel_dir = g.get("rel_directory") or "." # normalize and split directory parts - parts = [] if rel_dir in (None, '.', '') else [p for p in rel_dir.strip('/').split('/') if p] + parts = [] if rel_dir in (None, ".", "") else [p for p in rel_dir.strip("/").split("/") if p] parent_children = tree current_path_parts: list[str] = [] # ensure directory nodes exist for each part for part in parts: current_path_parts.append(part) - node = next((n for n in parent_children if n.get('isDir') and n.get('name') == part), None) + node = next((n for n in parent_children if n.get("isDir") and n.get("name") == part), None) if not node: node = { "isDir": True, "name": part, "path": f"{str(path)}/{'/'.join(current_path_parts)}", - "children": [] + "children": [], } parent_children.append(node) - parent_children = node['children'] + parent_children = node["children"] - raw_name = g.get('name') or Path(g.get('path', '')).name + raw_name = g.get("name") or Path(g.get("path", "")).name + try: + relative_graph_path = Path(g.get("path", "")).resolve().relative_to(path.resolve()).as_posix() + except (ValueError, TypeError): + relative_graph_path = "" + metadata = workflow_metadata.get(relative_graph_path, {}) file_item = { "isDir": False, "name": Path(raw_name).stem, - "path": g.get('path') + "path": g.get("path"), + "modelType": metadata.get("modelType"), + "mode": metadata.get("mode"), + "mediaKind": metadata.get("mediaKind"), + "supportTier": metadata.get("supportTier"), + "qualificationStatus": metadata.get("qualificationStatus"), + "requiredArtifacts": metadata.get("requiredArtifacts", []), } parent_children.append(file_item) # recursively sort directories (dirs first, then files) by name def sort_children(children: list[dict]): - dirs = [c for c in children if c['isDir']] - files = [c for c in children if not c['isDir']] - dirs.sort(key=lambda x: x['name'].lower()) - files.sort(key=lambda x: x['name'].lower()) + dirs = [c for c in children if c["isDir"]] + files = [c for c in children if not c["isDir"]] + dirs.sort(key=lambda x: x["name"].lower()) + files.sort(key=lambda x: x["name"].lower()) for d in dirs: - sort_children(d['children']) + sort_children(d["children"]) # mutate list in-place to preserve references children[:] = dirs + files @@ -1535,128 +3260,378 @@ def sort_children(children: list[dict]): return web.json_response(tree) + async def workflows_list(self, _request): + from modiff.workflow_store import list_workflows + + return web.json_response({"workflows": list_workflows(self.data_dir)}) + + async def workflow_get(self, request): + from modiff.workflow_store import get_workflow + + record = get_workflow(self.data_dir, request.match_info.get("workflow_id")) + if record is None: + return web.json_response({"error": True, "message": "Workflow not found."}, status=404) + return web.json_response(record) + + async def workflow_put(self, request): + from modiff.workflow_store import save_workflow + + try: + record = save_workflow( + self.data_dir, + request.match_info.get("workflow_id"), + await request.json(), + ) + except (ValueError, json.JSONDecodeError) as exc: + return web.json_response({"error": True, "message": str(exc)}, status=400) + self.queue_message({"type": "workflow_updated", "workflow": record}) + return web.json_response(record) + + async def workflow_delete(self, request): + from modiff.workflow_store import delete_workflow + + try: + workflow_id = request.match_info.get("workflow_id") + deleted = delete_workflow(self.data_dir, workflow_id) + except ValueError as exc: + return web.json_response({"error": True, "message": str(exc)}, status=400) + if not deleted: + return web.json_response({"error": True, "message": "Workflow not found."}, status=404) + self.queue_message({"type": "workflow_deleted", "workflow_id": workflow_id}) + return web.json_response({"error": False, "workflow_id": workflow_id}) + async def fileGet(self, request): - file = request.query.get('file') + file = request.query.get("file") if not file: return web.json_response({"error": "Incorrect request, `file` is required."}, status=400) - file_path = Path(file) - if not file_path.is_absolute(): - file_path = Path(self.work_dir) / file_path + file_path = self._resolve_managed_path_identifier(file) + if file_path is None: + return web.json_response( + {"error": "Files outside the configured MoDiff roots cannot be opened."}, status=403 + ) if not file_path.exists(): return web.json_response({"error": f"The file {file} does not exist."}, status=404) + download_format = str(request.query.get("download_format") or "").strip().lower() + if download_format: + from modiff.media_io import export_media_file, probe_media_file + + try: + media_kind = str(request.query.get("media_kind") or "").rstrip("s").lower() + if not media_kind: + media_kind = str(probe_media_file(file_path).get("kind") or "") + export_path, content_type, filename = await asyncio.to_thread( + export_media_file, + file_path, + kind=media_kind, + format_id=download_format, + options=self._media_export_options(request), + cache_root=Path(self.data_dir) / ".media-exports", + ) + except (OSError, ValueError, subprocess.SubprocessError) as exc: + return web.json_response({"error": str(exc)}, status=422) + return web.FileResponse( + export_path, + headers={ + "Content-Disposition": f'attachment; filename="{filename}"', + "Content-Type": content_type, + "Cache-Control": "private, max-age=31536000, immutable", + }, + ) + + try: + download_sample_rate = parse_audio_download_sample_rate(request.query.get("download_sample_rate")) + except ValueError as exc: + return web.json_response({"error": str(exc)}, status=400) + + if download_sample_rate is not None: + if file_path.suffix.lower() != ".wav": + return web.json_response( + {"error": "Sample-rate conversion is supported only for WAV downloads."}, + status=422, + ) + try: + body = resample_wav_bytes(file_path.read_bytes(), download_sample_rate) + except (OSError, ValueError) as exc: + return web.json_response( + {"error": f"Could not prepare the requested WAV download: {exc}"}, + status=422, + ) + filename = audio_download_filename(file_path.name, download_sample_rate) + return web.Response( + body=body, + content_type="audio/wav", + headers={ + "Content-Disposition": f'attachment; filename="{filename}"', + "Cache-Control": "no-store", + "X-MoDiff-Audio-Sample-Rate": str(download_sample_rate), + }, + ) + return web.FileResponse(file_path) async def filePost(self, request): data = await request.post() - file = data.get('file') - type = data.get('type', 'images') - type = type if type in ['images', 'audio', 'videos', 'text', '3d'] else 'images' - file_path = Path(self.data_dir) / type / file.filename + file = data.get("file") + if file is None or not getattr(file, "filename", None): + return web.json_response({"error": "A file upload is required."}, status=400) + type = data.get("type", "images") + type = type if type in ["images", "audio", "videos", "text", "3d"] else "images" + safe_name = Path(str(file.filename).replace("\\", "/")).name + if not safe_name or safe_name in {".", ".."}: + return web.json_response({"error": "The uploaded filename is invalid."}, status=400) + file_path = (Path(self.data_dir) / type / safe_name).resolve() + destination_root = (Path(self.data_dir) / type).resolve() + try: + file_path.relative_to(destination_root) + except ValueError: + return web.json_response({"error": "The uploaded filename is invalid."}, status=400) if file_path.exists(): file_path = file_path.with_name(f"{file_path.stem}_{nanoid.generate(size=6)}{file_path.suffix}") try: file_path.parent.mkdir(parents=True, exist_ok=True) - with open(file_path, 'wb') as f: - f.write(file.file.read()) - - return web.json_response({"error": False, "path": str(file_path.relative_to(self.work_dir))}) + max_upload_bytes = int(self.client_max_size) + with open(file_path, "wb") as f: + total = 0 + while True: + chunk = file.file.read(1024 * 1024) + if not chunk: + break + total += len(chunk) + if total > max_upload_bytes: + limit_mib = max_upload_bytes // (1024 * 1024) + raise ValueError(f"The uploaded file exceeds the {limit_mib} MB local import limit.") + f.write(chunk) + + from modiff.media_io import probe_media_file + + expected_kind = {"images": "image", "videos": "video", "audio": "audio", "text": "text"}.get(type) + metadata = ( + await asyncio.to_thread(probe_media_file, file_path, expected_kind) + if expected_kind + else { + "filename": file_path.name, + "extension": file_path.suffix.lower(), + "sizeBytes": file_path.stat().st_size, + "kind": "3d", + } + ) + return web.json_response( + { + "error": False, + "path": data_path_identifier(file_path, self.data_dir), + "media": metadata, + } + ) except Exception as e: + file_path.unlink(missing_ok=True) logger.error(f"Error saving file: {e}") - return web.json_response({"error": str(e)}, status=500) + return web.json_response({"error": str(e)}, status=400 if isinstance(e, ValueError) else 500) + + @staticmethod + def _media_export_options(request): + options = {} + fields = { + "sample_rate": ("sampleRate", int), + "channels": ("channels", int), + "bit_depth": ("bitDepth", int), + "bitrate": ("bitrate", int), + "quality": ("quality", int), + "compression": ("compression", int), + "fps": ("fps", int), + "width": ("width", int), + "background": ("background", str), + "speed": ("speed", str), + } + for query_key, (option_key, converter) in fields.items(): + value = request.query.get(query_key) + if value in (None, ""): + continue + try: + options[option_key] = converter(value) + except (TypeError, ValueError) as exc: + raise ValueError(f"Invalid media export option: {query_key}.") from exc + return options - async def preview(self, request): - from utils.image import cover - from PIL import Image - from io import BytesIO + async def media_capabilities(self, _request): + from modiff.media_io import media_capabilities - Image.MAX_IMAGE_PIXELS = None + return web.json_response(await asyncio.to_thread(media_capabilities)) - file = request.query.get('file') - if not file: - return web.json_response({"error": "Incorrect request, `file` is required."}, status=400) + async def media_probe(self, request): + from modiff.media_io import probe_media_file - file_path = Path(file) - if not file_path.is_absolute(): - file_path = Path(self.work_dir) / file_path + file = request.query.get("file") + if not file: + return web.json_response({"error": "`file` is required."}, status=400) + file_path = self._resolve_managed_path_identifier(file) + if file_path is None: + return web.json_response( + {"error": "Files outside the configured MoDiff roots cannot be inspected."}, status=403 + ) + try: + metadata = await asyncio.to_thread( + probe_media_file, + file_path, + request.query.get("media_kind"), + ) + except FileNotFoundError as exc: + return web.json_response({"error": str(exc)}, status=404) + except (OSError, ValueError, subprocess.SubprocessError) as exc: + return web.json_response({"error": str(exc)}, status=422) + return web.json_response(metadata) + + async def media_export(self, request): + from modiff.media_io import export_media_file + + file = request.query.get("file") + format_id = request.query.get("format") + media_kind = request.query.get("media_kind") + if not file or not format_id or not media_kind: + return web.json_response({"error": "`file`, `format`, and `media_kind` are required."}, status=400) + file_path = self._resolve_managed_path_identifier(file) + if file_path is None: + return web.json_response( + {"error": "Files outside the configured MoDiff roots cannot be exported."}, status=403 + ) + try: + export_path, content_type, filename = await asyncio.to_thread( + export_media_file, + file_path, + kind=media_kind, + format_id=format_id, + options=self._media_export_options(request), + cache_root=Path(self.data_dir) / ".media-exports", + ) + except FileNotFoundError as exc: + return web.json_response({"error": str(exc)}, status=404) + except (OSError, ValueError, subprocess.SubprocessError) as exc: + return web.json_response({"error": str(exc)}, status=422) + return web.FileResponse( + export_path, + headers={ + "Content-Disposition": f'attachment; filename="{filename}"', + "Content-Type": content_type, + "Cache-Control": "private, max-age=31536000, immutable", + }, + ) - if not str(file_path).startswith(self.work_dir): - return web.json_response({"error": f"Cannot access paths outside of {self.work_dir}."}, status=403) + async def media_preview(self, request): + """Serve a cached browser-safe representation of imported media.""" - if not file_path.exists(): - return web.json_response({"error": f"The file {file} does not exist."}, status=404) + from modiff.media_io import export_media_file, media_capabilities - if not file_path.suffix.lower() in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.ico', '.webp']: - return web.json_response({"error": f"The file {file} is not an image."}, status=400) + file = request.query.get("file") + media_kind = str(request.query.get("media_kind") or "").rstrip("s").lower() + if not file or media_kind not in {"audio", "video"}: + return web.json_response({"error": "`file` and an audio/video `media_kind` are required."}, status=400) + file_path = self._resolve_managed_path_identifier(file) + if file_path is None: + return web.json_response( + {"error": "Files outside the configured MoDiff roots cannot be previewed."}, status=403 + ) - width = int(request.query.get('width', 0)) - height = int(request.query.get('height', 0)) - format = request.query.get('format', 'jpeg') - format = format.lower() - quality = int(request.query.get('quality', 95)) + available = {descriptor["value"] for descriptor in media_capabilities()["media"][media_kind]["exportFormats"]} + format_id = ( + ("mp3" if "mp3" in available else "wav") + if media_kind == "audio" + else ("mp4" if "mp4" in available else "webm") + ) + if format_id not in available: + return web.json_response({"error": f"No browser-safe {media_kind} preview is available."}, status=422) + options = ( + {"sampleRate": 48000, "bitrate": 192} if media_kind == "audio" else {"quality": 23, "speed": "veryfast"} + ) + try: + preview_path, content_type, filename = await asyncio.to_thread( + export_media_file, + file_path, + kind=media_kind, + format_id=format_id, + options=options, + cache_root=Path(self.data_dir) / ".media-previews", + ) + except FileNotFoundError as exc: + return web.json_response({"error": str(exc)}, status=404) + except (OSError, ValueError, subprocess.SubprocessError) as exc: + return web.json_response({"error": str(exc)}, status=422) + return web.FileResponse( + preview_path, + headers={ + "Content-Disposition": f'inline; filename="{filename}"', + "Content-Type": content_type, + "Cache-Control": "private, max-age=31536000, immutable", + }, + ) - image = Image.open(file_path) + async def preview(self, request): + file = request.query.get("file") + if not file: + return web.json_response({"error": "Incorrect request, `file` is required."}, status=400) - if width > 0 or height > 0: - width = min(2048, width) if width > 0 else min(2048, height) - height = min(2048, height) if height > 0 else min(2048, width) - else: - width = min(2048, image.width) - height = min(2048, image.height) + file_path = self._resolve_managed_path_identifier(file) + if file_path is None: + return web.json_response({"error": "Cannot access paths outside the configured MoDiff roots."}, status=403) - if width != image.width or height != image.height: - image = cover(image, width, height, resample='BICUBIC') + if not file_path.exists(): + return web.json_response({"error": f"The file {file} does not exist."}, status=404) - if image.mode != 'RGB' and format in ['jpeg', 'jpg', 'bmp', 'ico']: - image = image.convert('RGB') + try: + width = int(request.query.get("width", 0)) + height = int(request.query.get("height", 0)) + format_id = str(request.query.get("format", "jpeg")).lower() + quality = int(request.query.get("quality", 95)) + body, content_type = await asyncio.to_thread( + render_image_preview, + file_path, + width, + height, + format_id, + quality, + ) + except (OSError, ValueError): + return web.json_response({"error": f"The file {file} is not a supported image."}, status=400) - bytes = BytesIO() - image.save(bytes, format=format.upper(), quality=quality) - bytes = bytes.getvalue() return web.Response( - body=bytes, - content_type=f'image/{format.lower()}', + body=body, + content_type=content_type, headers={ - 'Content-Disposition': f'inline; filename="{file_path.name}"', - 'Content-Length': str(len(bytes)), - 'Cache-Control': 'no-cache', - 'Pragma': 'no-cache', - 'Expires': '0' - } + "Content-Disposition": f'inline; filename="{file_path.name}"', + "Content-Length": str(len(body)), + "Cache-Control": "no-cache", + "Pragma": "no-cache", + "Expires": "0", + }, ) async def stream(self, request): - file = request.query.get('file') + file = request.query.get("file") if not file: return web.json_response({"error": "Incorrect request, `file` is required."}, status=400) - file_path = Path(file) - if not file_path.is_absolute(): - file_path = Path(self.work_dir) / file_path - - if not str(file_path).startswith(self.work_dir): - return web.json_response({"error": f"Cannot access paths outside of {self.work_dir}."}, status=403) + file_path = self._resolve_managed_path_identifier(file) + if file_path is None: + return web.json_response({"error": "Cannot access paths outside the configured MoDiff roots."}, status=403) if not file_path.exists(): return web.json_response({"error": f"The file {file} does not exist."}, status=404) resp = web.FileResponse(file_path) - resp.headers['Content-Disposition'] = f'inline; filename="{file_path.name}"' - resp.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' - resp.headers['Pragma'] = 'no-cache' - resp.headers['Expires'] = '0' + resp.headers["Content-Disposition"] = f'inline; filename="{file_path.name}"' + resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" + resp.headers["Pragma"] = "no-cache" + resp.headers["Expires"] = "0" return resp - async def local_models(self, request): - refresh = request.query.get('refresh', False) - path_match = request.query.get('match', "") + refresh = request.query.get("refresh", False) + path_match = request.query.get("match", "") if refresh: modelstore.update_local() @@ -1666,17 +3641,17 @@ async def local_models(self, request): return web.json_response(files) def _studio_history_file(self): - return Path(self.data_dir) / 'studio' / 'outputs.json' + return Path(self.data_dir) / "studio" / "outputs.json" def _studio_outputs_dir(self): - return Path(self.data_dir) / 'studio' / 'outputs' + return Path(self.data_dir) / "studio" / "outputs" def _studio_blocks_dir(self): - return Path(self.data_dir) / 'studio' / 'blocks' + return Path(self.data_dir) / "studio" / "blocks" def _safe_block_id(self, block_id=None): raw_id = str(block_id or nanoid.generate(size=12)) - safe_id = re.sub(r'[^a-zA-Z0-9_-]+', '_', raw_id).strip('_')[:80] + safe_id = re.sub(r"[^a-zA-Z0-9_-]+", "_", raw_id).strip("_")[:80] return safe_id or nanoid.generate(size=12) def _studio_block_file(self, block_id): @@ -1684,38 +3659,52 @@ def _studio_block_file(self, block_id): def _validate_studio_block(self, payload): if not isinstance(payload, dict): - raise ValueError('User block must be a JSON object.') + raise ValueError("User block must be a JSON object.") - block_id = self._safe_block_id(payload.get('id')) - name = str(payload.get('name') or 'User Block').strip()[:120] or 'User Block' - version = payload.get('version', 1) + block_id = self._safe_block_id(payload.get("id")) + name = str(payload.get("name") or "User Block").strip()[:120] or "User Block" + version = payload.get("version", 1) if version != 1: - raise ValueError('Unsupported user block version.') + raise ValueError("Unsupported user block version.") - required_arrays = ['nodes', 'edges', 'inputs', 'outputs', 'exposedParams'] + required_arrays = ["nodes", "edges", "inputs", "outputs", "exposedParams"] for key in required_arrays: if not isinstance(payload.get(key), list): - raise ValueError(f'User block field {key} must be a list.') + raise ValueError(f"User block field {key} must be a list.") + + for node in payload["nodes"]: + if not isinstance(node, dict): + raise ValueError("User block nodes must be JSON objects.") + data = node.get("data") + if not isinstance(data, dict): + raise ValueError("User block node data must be a JSON object.") + if ( + node.get("type") == "block" + or data.get("type") == "block" + or data.get("userBlockId") + or data.get("userBlockSnapshot") + ): + raise ValueError("Nested user blocks are not supported. Flatten the selected block before saving.") block = dict(payload) - block['id'] = block_id - block['name'] = name - block['version'] = 1 - block['nodes'] = payload['nodes'] - block['edges'] = payload['edges'] - block['inputs'] = payload['inputs'] - block['outputs'] = payload['outputs'] - block['exposedParams'] = payload['exposedParams'] + block["id"] = block_id + block["name"] = name + block["version"] = 1 + block["nodes"] = payload["nodes"] + block["edges"] = payload["edges"] + block["inputs"] = payload["inputs"] + block["outputs"] = payload["outputs"] + block["exposedParams"] = payload["exposedParams"] now = int(time.time() * 1000) - block['createdAt'] = int(payload.get('createdAt') or now) - block['updatedAt'] = int(payload.get('updatedAt') or now) + block["createdAt"] = int(payload.get("createdAt") or now) + block["updatedAt"] = int(payload.get("updatedAt") or now) return block def _read_studio_block(self, block_id): block_file = self._studio_block_file(block_id) if not block_file.exists(): return None - with open(block_file, 'r', encoding='utf-8') as f: + with open(block_file, "r", encoding="utf-8") as f: payload = json.load(f) return self._validate_studio_block(payload) @@ -1723,9 +3712,10 @@ def _write_studio_block(self, block): blocks_dir = self._studio_blocks_dir() blocks_dir.mkdir(parents=True, exist_ok=True) validated = self._validate_studio_block(block) - target = self._studio_block_file(validated['id']) - temp_file = target.with_suffix('.tmp') - with open(temp_file, 'w', encoding='utf-8') as f: + validated["updatedAt"] = int(time.time() * 1000) + target = self._studio_block_file(validated["id"]) + temp_file = target.with_suffix(".tmp") + with open(temp_file, "w", encoding="utf-8") as f: json.dump(validated, f, ensure_ascii=False) temp_file.replace(target) return validated, target @@ -1736,72 +3726,197 @@ def _list_studio_blocks(self): return [] blocks = [] - for block_file in blocks_dir.glob('*.json'): + for block_file in blocks_dir.glob("*.json"): try: - with open(block_file, 'r', encoding='utf-8') as f: + with open(block_file, "r", encoding="utf-8") as f: blocks.append(self._validate_studio_block(json.load(f))) except (ValueError, json.JSONDecodeError, UnicodeDecodeError, OSError) as e: logger.error(f"Error reading Studio user block {block_file}: {e}") - return sorted(blocks, key=lambda block: block.get('updatedAt') or 0, reverse=True) + return sorted(blocks, key=lambda block: block.get("updatedAt") or 0, reverse=True) - def _read_studio_outputs(self): + def _studio_preview_slot_key(self, workflow_tab_id, node_id, field_key): + if not workflow_tab_id or not node_id or not field_key: + return None + return json.dumps( + [str(workflow_tab_id), str(node_id), str(field_key)], + ensure_ascii=False, + separators=(",", ":"), + ) + + def _studio_state_int(self, value, default=0): + try: + return int(value) + except (TypeError, ValueError, OverflowError): + return default + + def _normalize_studio_preview_slot(self, slot): + if not isinstance(slot, dict): + return None + key = self._studio_preview_slot_key( + slot.get("workflowTabId"), slot.get("nodeId"), slot.get("fieldKey") + ) + if key is None: + return None + status = str(slot.get("status") or "empty") + if status not in { + "empty", + "pending", + "ready", + "failed", + "cancelled", + "completed_without_output", + }: + status = "empty" + normalized = { + "schemaVersion": 1, + "workflowTabId": str(slot["workflowTabId"]), + "nodeId": str(slot["nodeId"]), + "fieldKey": str(slot["fieldKey"]), + "currentOutputId": str(slot["currentOutputId"]) if slot.get("currentOutputId") else None, + "pendingClientRunId": str(slot["pendingClientRunId"]) if slot.get("pendingClientRunId") else None, + "pendingTaskId": str(slot["pendingTaskId"]) if slot.get("pendingTaskId") else None, + "generation": max(self._studio_state_int(slot.get("generation"), 0), 0), + "attemptIndex": ( + self._studio_state_int(slot["attemptIndex"]) + if slot.get("attemptIndex") is not None + else None + ), + "status": status, + "updatedAt": self._studio_state_int(slot.get("updatedAt"), 0), + } + return key, normalized + + def _legacy_studio_preview_slots(self, outputs): + slots = {} + for output in self._sort_studio_outputs(outputs): + key = self._studio_preview_slot_key( + output.get("workflowTabId"), output.get("nodeId"), output.get("fieldKey") + ) + if key is None or key in slots or not output.get("id"): + continue + slots[key] = { + "schemaVersion": 1, + "workflowTabId": str(output["workflowTabId"]), + "nodeId": str(output["nodeId"]), + "fieldKey": str(output["fieldKey"]), + "currentOutputId": str(output["id"]), + "pendingClientRunId": None, + "pendingTaskId": None, + "generation": 1, + "attemptIndex": output.get("attemptIndex"), + "status": "ready", + "updatedAt": int(output.get("createdAt") or 0), + } + return slots + + def _read_studio_output_state(self): history_file = self._studio_history_file() if not history_file.exists(): - return [] + return {"revision": 0, "previewSlots": {}, "outputs": []} try: - with open(history_file, 'r', encoding='utf-8') as f: + with open(history_file, "r", encoding="utf-8") as f: payload = json.load(f) except (json.JSONDecodeError, UnicodeDecodeError, OSError) as e: logger.error(f"Error reading Studio output history: {e}") - return [] + return {"revision": 0, "previewSlots": {}, "outputs": []} if isinstance(payload, list): outputs = payload - elif isinstance(payload, dict) and isinstance(payload.get('outputs'), list): - outputs = payload['outputs'] + version = 1 + revision = 0 + raw_slots = None + elif isinstance(payload, dict) and isinstance(payload.get("outputs"), list): + outputs = payload["outputs"] + version = self._studio_state_int(payload.get("version"), 1) + revision = max(self._studio_state_int(payload.get("revision"), 0), 0) + raw_slots = payload.get("previewSlots") else: outputs = [] + version = 1 + revision = 0 + raw_slots = None + + outputs = [output for output in outputs if isinstance(output, dict)] + slots = {} + slot_values = list(raw_slots.values()) if isinstance(raw_slots, dict) else raw_slots + if isinstance(slot_values, (list, tuple)): + for slot in slot_values: + normalized = self._normalize_studio_preview_slot(slot) + if normalized is not None: + slots[normalized[0]] = normalized[1] + if version < 2: + slots = self._legacy_studio_preview_slots(outputs) + return {"revision": revision, "previewSlots": slots, "outputs": outputs} - return [output for output in outputs if isinstance(output, dict)] + def _read_studio_outputs(self): + return self._read_studio_output_state()["outputs"] - def _write_studio_outputs(self, outputs): + def _write_studio_output_state(self, outputs, preview_slots, *, revision=None): history_file = self._studio_history_file() history_file.parent.mkdir(parents=True, exist_ok=True) - bounded_outputs = outputs[:200] + current_ids = { + str(slot.get("currentOutputId")) + for slot in preview_slots.values() + if isinstance(slot, dict) and slot.get("currentOutputId") + } + bounded_outputs = list(outputs[:200]) + bounded_ids = {str(output.get("id")) for output in bounded_outputs if output.get("id")} + for output in outputs[200:]: + output_id = str(output.get("id")) if output.get("id") else None + if output_id in current_ids and output_id not in bounded_ids: + bounded_outputs.append(output) + bounded_ids.add(output_id) + next_revision = max(self._studio_state_int(revision, 0), 0) payload = { - 'version': 1, - 'updatedAt': int(time.time() * 1000), - 'outputs': bounded_outputs, + "version": 2, + "revision": next_revision, + "updatedAt": int(time.time() * 1000), + "previewSlots": preview_slots, + "outputs": bounded_outputs, } - temp_file = history_file.with_suffix('.tmp') - with open(temp_file, 'w', encoding='utf-8') as f: + temp_file = history_file.with_suffix(".tmp") + with open(temp_file, "w", encoding="utf-8") as f: json.dump(payload, f, ensure_ascii=False) temp_file.replace(history_file) return bounded_outputs + def _write_studio_outputs(self, outputs): + state = self._read_studio_output_state() + return self._write_studio_output_state( + outputs, + state["previewSlots"], + revision=state["revision"] + 1, + ) + def _studio_output_key(self, output): - return str(output.get('id') or f"{output.get('nodeId', '')}:{output.get('fieldKey', '')}:{output.get('url', '')}") + return str( + output.get("id") or f"{output.get('nodeId', '')}:{output.get('fieldKey', '')}:{output.get('url', '')}" + ) def _hash_file(self, path): digest = hashlib.sha256() - with open(path, 'rb') as f: - for chunk in iter(lambda: f.read(1024 * 1024), b''): + with open(path, "rb") as f: + for chunk in iter(lambda: f.read(1024 * 1024), b""): digest.update(chunk) return f"sha256:bytes:{digest.hexdigest()}" def _hash_collection(self, hashes): - digest = hashlib.sha256(json.dumps(hashes, sort_keys=True).encode('utf-8')) + digest = hashlib.sha256(json.dumps(hashes, sort_keys=True).encode("utf-8")) return f"sha256:collection:{digest.hexdigest()}" def _sort_studio_outputs(self, outputs): - return sorted(outputs, key=lambda output: output.get('createdAt') or 0, reverse=True) + return sorted(outputs, key=lambda output: output.get("createdAt") or 0, reverse=True) def _merge_studio_outputs(self, existing, incoming): merged = {} order = [] - for output in incoming + existing: + # Existing records establish durable paths/favorites; incoming records + # then enrich or replace the same logical output. Processing in the + # opposite order silently kept stale retry media and discarded richer + # frontend metadata for backend-captured outputs. + for output in existing + incoming: key = self._studio_output_key(output) if not key: continue @@ -1814,38 +3929,162 @@ def _merge_studio_outputs(self, existing, incoming): merged[key] = { **previous, **output, - 'favorite': output.get('favorite', previous.get('favorite', False)), - 'backendImagePath': output.get('backendImagePath') or previous.get('backendImagePath'), - 'backendMediaPath': output.get('backendMediaPath') or previous.get('backendMediaPath'), - 'backendSyncedAt': output.get('backendSyncedAt') or previous.get('backendSyncedAt'), + "favorite": bool(previous.get("favorite", False) or output.get("favorite", False)), + "backendImagePath": output.get("backendImagePath") or previous.get("backendImagePath"), + "backendMediaPath": output.get("backendMediaPath") or previous.get("backendMediaPath"), + "backendSyncedAt": output.get("backendSyncedAt") or previous.get("backendSyncedAt"), } return self._sort_studio_outputs([merged[key] for key in order]) + def _generated_preview_fields_for_graph(self, graph): + if not isinstance(graph, dict): + return [] + runtime_hints = graph.get("runtimeHints") + workflow_tab_id = runtime_hints.get("workflowTabId") if isinstance(runtime_hints, dict) else None + nodes = graph.get("nodes") + if not workflow_tab_id or not isinstance(nodes, dict): + return [] + fields = [] + for node_id, node in nodes.items(): + if not isinstance(node, dict): + continue + module = node.get("module") + action = node.get("action") + definitions = self.modules.get(module, {}).get(action, {}).get("params", {}) + if not isinstance(definitions, dict): + continue + submitted_params = node.get("params") if isinstance(node.get("params"), dict) else {} + for field_key, definition in definitions.items(): + if not isinstance(definition, dict): + continue + if definition.get("display") not in {"ui_image", "ui_video", "ui_audio", "ui_text"}: + continue + if definition.get("hidden") or (module == "modules.Audio" and action == "Load"): + continue + # Only fields present in the submitted graph can receive an + # update for this run. This avoids clearing unrelated optional + # previews declared by a module but omitted from the graph. + if field_key not in submitted_params: + continue + fields.append((str(workflow_tab_id), str(node_id), str(field_key))) + return fields + + def _mark_studio_preview_slots_pending(self, graph, task_id): + fields = self._generated_preview_fields_for_graph(graph) + if not fields: + return {"revision": 0, "previewSlots": []} + runtime_hints = graph.get("runtimeHints") if isinstance(graph, dict) else {} + client_run_id = runtime_hints.get("clientRunId") if isinstance(runtime_hints, dict) else None + attempt_index = runtime_hints.get("attemptIndex") if isinstance(runtime_hints, dict) else None + now = int(time.time() * 1000) + with self.studio_history_file_lock: + state = self._read_studio_output_state() + slots = state["previewSlots"] + changed = [] + for workflow_tab_id, node_id, field_key in fields: + key = self._studio_preview_slot_key(workflow_tab_id, node_id, field_key) + previous = slots.get(key, {}) + slot = { + "schemaVersion": 1, + "workflowTabId": workflow_tab_id, + "nodeId": node_id, + "fieldKey": field_key, + "currentOutputId": None, + "pendingClientRunId": str(client_run_id) if client_run_id else None, + "pendingTaskId": str(task_id), + "generation": max(self._studio_state_int(previous.get("generation"), 0), 0) + 1, + "attemptIndex": self._studio_state_int(attempt_index) if attempt_index is not None else None, + "status": "pending", + "updatedAt": now, + } + slots[key] = slot + changed.append(slot) + revision = state["revision"] + 1 + self._write_studio_output_state(state["outputs"], slots, revision=revision) + return {"revision": revision, "previewSlots": changed} + + def _studio_preview_slots_for_task(self, task_id): + normalized_task_id = str(task_id or "") + with self.studio_history_file_lock: + state = self._read_studio_output_state() + slots = [ + slot + for slot in state["previewSlots"].values() + if str(slot.get("pendingTaskId") or "") == normalized_task_id + or ( + slot.get("currentOutputId") + and any( + str(output.get("id") or "") == str(slot["currentOutputId"]) + and str(output.get("taskId") or "") == normalized_task_id + for output in state["outputs"] + ) + ) + ] + return {"revision": state["revision"], "previewSlots": slots} + + def _mark_studio_preview_run_terminal(self, task_id, status): + normalized_task_id = str(task_id or "") + if not normalized_task_id: + return None + terminal_status = { + "failed": "failed", + "cancelled": "cancelled", + "completed": "completed_without_output", + }.get(status) + if terminal_status is None: + return None + now = int(time.time() * 1000) + with self.studio_history_file_lock: + state = self._read_studio_output_state() + changed = [] + for key, slot in state["previewSlots"].items(): + if str(slot.get("pendingTaskId") or "") != normalized_task_id: + continue + updated = { + **slot, + "currentOutputId": None, + "pendingClientRunId": None, + "pendingTaskId": None, + "status": terminal_status, + "updatedAt": now, + } + state["previewSlots"][key] = updated + changed.append(updated) + if not changed: + return None + revision = state["revision"] + 1 + self._write_studio_output_state( + state["outputs"], state["previewSlots"], revision=revision + ) + return {"revision": revision, "previewSlots": changed} + def _save_studio_output_image(self, output): - image_data = output.pop('image_data', None) - if not image_data or output.get('backendImagePath'): + image_data = output.pop("image_data", None) + if not image_data or output.get("backendImagePath"): return output try: - header = '' + header = "" payload = image_data - if isinstance(image_data, str) and image_data.startswith('data:') and ',' in image_data: - header, payload = image_data.split(',', 1) + if isinstance(image_data, str) and image_data.startswith("data:") and "," in image_data: + header, payload = image_data.split(",", 1) if not isinstance(payload, str): return output - mime_type = header.split(';')[0].removeprefix('data:') if header else '' + mime_type = header.split(";")[0].removeprefix("data:") if header else "" extension = { - 'image/webp': '.webp', - 'image/png': '.png', - 'image/jpeg': '.jpg', - 'image/jpg': '.jpg', - 'image/gif': '.gif', - }.get(mime_type, '.webp') - - output_id = ''.join(ch for ch in str(output.get('id') or nanoid.generate(size=12)) if ch.isalnum() or ch in ('-', '_'))[:80] + "image/webp": ".webp", + "image/png": ".png", + "image/jpeg": ".jpg", + "image/jpg": ".jpg", + "image/gif": ".gif", + }.get(mime_type, ".webp") + + output_id = "".join( + ch for ch in str(output.get("id") or nanoid.generate(size=12)) if ch.isalnum() or ch in ("-", "_") + )[:80] if not output_id: output_id = nanoid.generate(size=12) @@ -1853,17 +4092,14 @@ def _save_studio_output_image(self, output): output_dir = self._studio_outputs_dir() output_dir.mkdir(parents=True, exist_ok=True) image_path = output_dir / f"{output_id}{extension}" - with open(image_path, 'wb') as f: + with open(image_path, "wb") as f: f.write(image_bytes) - try: - image_file = str(image_path.relative_to(self.work_dir)).replace('\\', '/') - except ValueError: - image_file = str(image_path) - output['backendImagePath'] = image_file - output['url'] = f"/file?file={quote(image_file)}" - output['backendSyncedAt'] = int(time.time() * 1000) - output['mediaHash'] = self._hash_file(image_path) + image_file = data_path_identifier(image_path, self.data_dir) + output["backendImagePath"] = image_file + output["url"] = f"/file?file={quote(image_file)}" + output["backendSyncedAt"] = int(time.time() * 1000) + output["mediaHash"] = self._hash_file(image_path) except Exception as e: logger.error(f"Error saving Studio output image: {e}") @@ -1871,53 +4107,58 @@ def _save_studio_output_image(self, output): def _save_studio_output_media(self, output): output = self._save_studio_output_image(output) - if output.get('backendMediaPath') or output.get('backendImagePath'): + if output.get("backendMediaPath") or output.get("backendImagePath"): + return output + if isinstance(output.get("mediaItems"), list) and output.get("mediaItems"): return output - display_type = output.get('displayType') - preview_url = output.get('url') - if display_type != 'video' and not str(preview_url or '').lower().split('?')[0].endswith(('.mp4', '.webm', '.mov', '.mkv')): + display_type = output.get("displayType") + preview_url = output.get("url") + if display_type != "video" and not str(preview_url or "").lower().split("?")[0].endswith( + (".mp4", ".webm", ".mov", ".mkv") + ): return output try: media = self._share_media_bytes_from_url(preview_url) - if not media or not media.get('bytes'): + if not media or not media.get("bytes"): return output - content_type = media.get('contentType') or 'application/octet-stream' - extension = self._content_type_extension(content_type, media.get('filename') or preview_url) - if extension.lower() not in ('.mp4', '.webm', '.mov', '.mkv'): - extension = '.mp4' + content_type = media.get("contentType") or "application/octet-stream" + extension = self._content_type_extension(content_type, media.get("filename") or preview_url) + if extension.lower() not in (".mp4", ".webm", ".mov", ".mkv"): + extension = ".mp4" - output_id = ''.join(ch for ch in str(output.get('id') or nanoid.generate(size=12)) if ch.isalnum() or ch in ('-', '_'))[:80] + output_id = "".join( + ch for ch in str(output.get("id") or nanoid.generate(size=12)) if ch.isalnum() or ch in ("-", "_") + )[:80] if not output_id: output_id = nanoid.generate(size=12) output_dir = self._studio_outputs_dir() output_dir.mkdir(parents=True, exist_ok=True) media_path = output_dir / f"{output_id}{extension}" - with open(media_path, 'wb') as f: - f.write(media['bytes']) - - try: - media_file = str(media_path.relative_to(self.work_dir)).replace('\\', '/') - except ValueError: - media_file = str(media_path) - output['backendMediaPath'] = media_file - output['url'] = f"/file?file={quote(media_file)}" - output['backendSyncedAt'] = int(time.time() * 1000) - output['mediaHash'] = self._hash_file(media_path) + with open(media_path, "wb") as f: + f.write(media["bytes"]) + + media_file = data_path_identifier(media_path, self.data_dir) + output["backendMediaPath"] = media_file + output["url"] = f"/file?file={quote(media_file)}" + output["backendSyncedAt"] = int(time.time() * 1000) + output["mediaHash"] = self._hash_file(media_path) except Exception as e: logger.error(f"Error saving Studio output media: {e}") return output def _safe_studio_output_id(self, output): - output_id = ''.join(ch for ch in str(output.get('id') or nanoid.generate(size=12)) if ch.isalnum() or ch in ('-', '_'))[:80] + output_id = "".join( + ch for ch in str(output.get("id") or nanoid.generate(size=12)) if ch.isalnum() or ch in ("-", "_") + )[:80] return output_id or nanoid.generate(size=12) def _save_studio_output_media_items(self, output): - media_items = output.get('mediaItems') + media_items = output.get("mediaItems") if not isinstance(media_items, list) or len(media_items) == 0: return output @@ -1931,246 +4172,514 @@ def _save_studio_output_media_items(self, output): continue normalized = deepcopy(item) - normalized['index'] = int(normalized.get('index', index) or index) - if normalized.get('backendPath') and normalized.get('mediaHash'): + normalized["index"] = int(normalized.get("index", index) or index) + if normalized.get("backendPath") and normalized.get("mediaHash"): saved_items.append(normalized) continue - preview_url = normalized.get('url') or normalized.get('value') + preview_url = normalized.get("url") or normalized.get("value") media = self._share_media_bytes_from_url(preview_url) - if not media or not isinstance(media.get('bytes'), (bytes, bytearray)): + if not media or not isinstance(media.get("bytes"), (bytes, bytearray)): saved_items.append(normalized) continue - media_bytes = bytes(media['bytes']) - content_type = media.get('contentType') or 'application/octet-stream' - extension = self._content_type_extension(content_type, media.get('filename') or preview_url) + media_bytes = bytes(media["bytes"]) + content_type = media.get("contentType") or "application/octet-stream" + extension = self._content_type_extension(content_type, media.get("filename") or preview_url) filename = f"{output_id}_item_{normalized['index']:02d}{extension}" media_path = output_dir / filename - with open(media_path, 'wb') as f: + with open(media_path, "wb") as f: f.write(media_bytes) - try: - media_file = str(media_path.relative_to(self.work_dir)).replace('\\', '/') - except ValueError: - media_file = str(media_path) + media_file = data_path_identifier(media_path, self.data_dir) - normalized['backendPath'] = media_file - normalized['url'] = f"/file?file={quote(media_file)}" - normalized['mediaHash'] = self._hash_file(media_path) - normalized['contentType'] = content_type - normalized['byteSize'] = len(media_bytes) + normalized["backendPath"] = media_file + normalized["url"] = f"/file?file={quote(media_file)}" + normalized["mediaHash"] = self._hash_file(media_path) + normalized["contentType"] = content_type + normalized["byteSize"] = len(media_bytes) saved_items.append(normalized) if saved_items: - output['mediaItems'] = saved_items - item_hashes = [item.get('mediaHash') for item in saved_items if item.get('mediaHash')] + output["mediaItems"] = saved_items + first_item = saved_items[0] + if first_item.get("url"): + output["url"] = first_item["url"] + if first_item.get("backendPath"): + output["backendMediaPath"] = first_item["backendPath"] + output["backendSyncedAt"] = int(time.time() * 1000) + item_hashes = [item.get("mediaHash") for item in saved_items if item.get("mediaHash")] if item_hashes: - output['mediaCollectionHash'] = self._hash_collection(item_hashes) + output["mediaCollectionHash"] = self._hash_collection(item_hashes) if len(item_hashes) > 1: - output['mediaHash'] = output['mediaCollectionHash'] + output["mediaHash"] = output["mediaCollectionHash"] + elif first_item.get("mediaHash"): + output["mediaHash"] = first_item["mediaHash"] return output def _normalize_studio_output(self, output): normalized = deepcopy(output) - if not isinstance(normalized.get('id'), str) or not normalized.get('id'): - normalized['id'] = nanoid.generate(size=12) - if not normalized.get('createdAt'): - normalized['createdAt'] = int(time.time() * 1000) - if 'favorite' not in normalized: - normalized['favorite'] = False + if not isinstance(normalized.get("id"), str) or not normalized.get("id"): + normalized["id"] = nanoid.generate(size=12) + if not normalized.get("createdAt"): + normalized["createdAt"] = int(time.time() * 1000) + if "favorite" not in normalized: + normalized["favorite"] = False normalized = self._save_studio_output_media(normalized) normalized = self._save_studio_output_media_items(normalized) - provenance = normalized.get('provenance') if isinstance(normalized.get('provenance'), dict) else {} - runtime_fingerprint = provenance.get('runtimeFingerprint') + provenance = normalized.get("provenance") if isinstance(normalized.get("provenance"), dict) else {} + runtime_fingerprint = provenance.get("runtimeFingerprint") if not runtime_fingerprint and self.current_task: - runtime_fingerprint = self.current_task.get('runtimeFingerprint') - normalized['backendProvenance'] = { - 'schemaVersion': 1, - 'source': 'backend-record', - 'capturedAt': int(time.time() * 1000), - 'backendExecutionId': normalized.get('taskId') or normalized.get('runId'), - 'clientRunId': normalized.get('clientRunId'), - 'runInputHash': normalized.get('runInputHash'), - 'workflowTabId': normalized.get('workflowTabId'), - 'attemptIndex': normalized.get('attemptIndex'), - 'nodeId': normalized.get('nodeId'), - 'fieldKey': normalized.get('fieldKey'), - 'historyPath': str(self._studio_history_file()), - 'mediaPath': normalized.get('backendMediaPath') or normalized.get('backendImagePath'), - 'mediaHash': normalized.get('mediaHash'), - 'mediaCollectionHash': normalized.get('mediaCollectionHash'), - 'mediaItems': normalized.get('mediaItems'), - 'runtimeFingerprint': runtime_fingerprint, - 'templateId': normalized.get('templateId'), - 'templateLockHash': normalized.get('templateLockHash'), - 'promptSettingsHash': normalized.get('promptSettingsHash'), + runtime_fingerprint = self.current_task.get("runtimeFingerprint") + normalized["backendProvenance"] = { + "schemaVersion": 1, + "source": "backend-record", + "capturedAt": int(time.time() * 1000), + "backendExecutionId": normalized.get("taskId") or normalized.get("runId"), + "clientRunId": normalized.get("clientRunId"), + "runInputHash": normalized.get("runInputHash"), + "workflowTabId": normalized.get("workflowTabId"), + "attemptIndex": normalized.get("attemptIndex"), + "nodeId": normalized.get("nodeId"), + "fieldKey": normalized.get("fieldKey"), + "historyPath": str(self._studio_history_file()), + "mediaPath": normalized.get("backendMediaPath") or normalized.get("backendImagePath"), + "mediaHash": normalized.get("mediaHash"), + "mediaCollectionHash": normalized.get("mediaCollectionHash"), + "mediaItems": normalized.get("mediaItems"), + "runtimeFingerprint": runtime_fingerprint, + "templateId": normalized.get("templateId"), + "templateLockHash": normalized.get("templateLockHash"), + "promptSettingsHash": normalized.get("promptSettingsHash"), } return normalized + def _generated_output_id(self, task_id, attempt_index, node_id, field_key): + identity = json.dumps( + [str(task_id), int(attempt_index or 0), str(node_id), str(field_key)], + ensure_ascii=False, + separators=(",", ":"), + ) + digest = hashlib.sha256(identity.encode("utf-8")).hexdigest()[:24] + return f"run-output-{digest}" + + def _persist_generated_output_update(self, message, *, display): + """Capture a generated preview before its cache entry can be replaced. + + Frontend history enrichment is useful but must not be the only durable + record: the browser can reload or disconnect between update_value and + its POST to /studio_outputs. + """ + if not isinstance(message, dict) or display not in {"ui_image", "ui_video", "ui_audio", "ui_text"}: + return None, False + + task_id = message.get("task_id") + node_id = message.get("node") + field_key = message.get("key") + if not task_id or not node_id or not field_key: + return None, False + + display_type = { + "ui_image": "image", + "ui_video": "video", + "ui_audio": "audio", + "ui_text": "text", + }[display] + value = message.get("value") + values = value if isinstance(value, list) else [value] + artifacts = message.get("artifacts") if isinstance(message.get("artifacts"), list) else [] + media_items = [] + + if display_type == "text": + text_value = value if isinstance(value, str) else json.dumps(value, ensure_ascii=False, default=str) + media_items.append( + { + "index": 0, + "value": value, + "url": f"data:text/plain;charset=utf-8,{quote(text_value, safe='')}", + "displayType": "text", + "taskId": task_id, + "clientRunId": message.get("client_run_id"), + "runInputHash": message.get("run_input_hash"), + "attemptIndex": message.get("attempt_index"), + } + ) + else: + for index, item in enumerate(values): + artifact = artifacts[index] if index < len(artifacts) and isinstance(artifacts[index], dict) else {} + url = artifact.get("url") if isinstance(artifact.get("url"), str) else item + if not isinstance(url, str) or not url: + continue + media_items.append( + { + "index": index, + "value": item, + "url": url, + "displayType": display_type, + "contentType": artifact.get("mimeType"), + "width": artifact.get("width"), + "height": artifact.get("height"), + "durationSeconds": artifact.get("durationSeconds"), + "taskId": task_id, + "clientRunId": message.get("client_run_id"), + "runInputHash": message.get("run_input_hash"), + "attemptIndex": message.get("attempt_index"), + } + ) + + if not media_items: + return None, False + + graph = self.task_graphs.get(str(task_id)) + graph_runtime_hints = graph.get("runtimeHints") if isinstance(graph, dict) else None + runtime_hints = ( + graph_runtime_hints + if isinstance(graph_runtime_hints, dict) + else (self.current_task.get("runtimeHints") if self.current_task else {}) + ) + workflow_snapshot = runtime_hints.get("workflowSnapshot") if isinstance(runtime_hints, dict) else None + workflow_snapshot = workflow_snapshot if isinstance(workflow_snapshot, dict) else {} + form_snapshot = workflow_snapshot.get("studioForm") + form_snapshot = form_snapshot if isinstance(form_snapshot, dict) else {} + graph_snapshot = { + key: deepcopy(workflow_snapshot[key]) for key in ("nodes", "edges", "viewport") if key in workflow_snapshot + } + output_id = self._generated_output_id(task_id, message.get("attempt_index"), node_id, field_key) + output = { + "id": output_id, + "taskId": task_id, + "clientRunId": message.get("client_run_id"), + "runInputHash": message.get("run_input_hash"), + "workflowTabId": message.get("workflow_tab_id"), + "attemptIndex": message.get("attempt_index"), + "nodeId": node_id, + "fieldKey": field_key, + "value": value, + "url": media_items[0]["url"], + "createdAt": int(time.time() * 1000), + "favorite": False, + "displayType": "image_collection" if display_type == "image" and len(media_items) > 1 else display_type, + "mediaItems": media_items, + "sid": self.current_task.get("sid") if self.current_task else None, + "mode": form_snapshot.get("mode"), + "modelType": form_snapshot.get("modelType") + or (runtime_hints.get("modelType") if isinstance(runtime_hints, dict) else None), + "modelLabel": runtime_hints.get("modelName") if isinstance(runtime_hints, dict) else None, + "repo": runtime_hints.get("resolvedArtifact") or runtime_hints.get("modelRepo") + if isinstance(runtime_hints, dict) + else None, + "prompt": form_snapshot.get("prompt"), + "negativePrompt": form_snapshot.get("negativePrompt"), + "seed": form_snapshot.get("seed"), + "width": form_snapshot.get("width"), + "height": form_snapshot.get("height"), + "steps": form_snapshot.get("steps"), + "guidanceScale": form_snapshot.get("guidanceScale"), + "referenceImages": form_snapshot.get("referenceImages"), + "formSnapshot": deepcopy(form_snapshot) if form_snapshot else None, + "graphSnapshot": graph_snapshot or None, + "graphBindingSnapshot": deepcopy(workflow_snapshot.get("studioGraphBinding")), + "templateId": workflow_snapshot.get("activeTemplateId"), + "sourceOutputId": workflow_snapshot.get("sourceOutputId"), + "provenance": { + "schemaVersion": 1, + "source": "backend-record", + "capturedAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "backendExecutionId": task_id, + "clientRunId": message.get("client_run_id"), + "runInputHash": message.get("run_input_hash"), + "workflowTabId": message.get("workflow_tab_id"), + "attemptIndex": message.get("attempt_index"), + "nodeId": node_id, + "runtimeFingerprint": message.get("runtimeFingerprint"), + "mediaItems": media_items, + }, + } + output = {key: item for key, item in output.items() if item is not None} + + try: + with self.studio_history_file_lock: + normalized = self._normalize_studio_output(output) + state = self._read_studio_output_state() + outputs = self._merge_studio_outputs(state["outputs"], [normalized]) + slot_key = self._studio_preview_slot_key( + output.get("workflowTabId"), node_id, field_key + ) + promoted_slot = None + if slot_key is not None: + previous = state["previewSlots"].get(slot_key) + pending_matches = bool( + previous + and str(previous.get("pendingTaskId") or "") == str(task_id) + and ( + not previous.get("pendingClientRunId") + or str(previous.get("pendingClientRunId")) + == str(message.get("client_run_id") or "") + ) + ) + # A newer accepted run owns the pending slot and must not + # be displaced by a late output from the run ahead of it. + # Otherwise every update from the currently executing run + # may refresh its own durable current output (including an + # automatic retry with a new attempt index). + may_promote = previous is None or pending_matches or not previous.get("pendingTaskId") + if may_promote: + promoted_slot = { + "schemaVersion": 1, + "workflowTabId": str(output["workflowTabId"]), + "nodeId": str(node_id), + "fieldKey": str(field_key), + "currentOutputId": output_id, + "pendingClientRunId": None, + "pendingTaskId": None, + "generation": max( + self._studio_state_int((previous or {}).get("generation"), 0), 0 + ) + or 1, + "attemptIndex": message.get("attempt_index"), + "status": "ready", + "updatedAt": int(time.time() * 1000), + } + state["previewSlots"][slot_key] = promoted_slot + revision = state["revision"] + 1 + self._write_studio_output_state( + outputs, state["previewSlots"], revision=revision + ) + if promoted_slot is not None: + message["preview_slot"] = promoted_slot + message["preview_state_revision"] = revision + return output_id, True + except Exception as error: + logger.error(f"Error preserving generated Studio output {output_id}: {error}") + return output_id, False + async def studio_outputs_get(self, request): - limit = min(max(int(request.query.get('limit', 80)), 1), 200) - outputs = self._read_studio_outputs() - return web.json_response({ - 'error': False, - 'count': len(outputs), - 'outputs': outputs[:limit], - 'path': str(self._studio_history_file()), - }) + limit = min(max(int(request.query.get("limit", 80)), 1), 200) + with self.studio_history_file_lock: + state = self._read_studio_output_state() + outputs = state["outputs"] + response_outputs = list(outputs[:limit]) + response_ids = {str(output.get("id")) for output in response_outputs if output.get("id")} + current_ids = { + str(slot.get("currentOutputId")) + for slot in state["previewSlots"].values() + if slot.get("currentOutputId") + } + for output in outputs[limit:]: + output_id = str(output.get("id")) if output.get("id") else None + if output_id in current_ids and output_id not in response_ids: + response_outputs.append(output) + response_ids.add(output_id) + return web.json_response( + { + "error": False, + "count": len(outputs), + "outputs": response_outputs, + "previewSlots": list(state["previewSlots"].values()), + "revision": state["revision"], + "path": str(self._studio_history_file()), + } + ) async def studio_outputs_post(self, request): try: payload = await request.json() except json.JSONDecodeError: - return web.json_response({'error': True, 'message': 'Invalid JSON body.'}, status=400) + return web.json_response({"error": True, "message": "Invalid JSON body."}, status=400) - raw_outputs = payload.get('outputs') if isinstance(payload, dict) and isinstance(payload.get('outputs'), list) else None + raw_outputs = ( + payload.get("outputs") if isinstance(payload, dict) and isinstance(payload.get("outputs"), list) else None + ) if raw_outputs is None: raw_outputs = [payload] if isinstance(payload, dict) else [] - incoming = [self._normalize_studio_output(output) for output in raw_outputs if isinstance(output, dict)] - if not incoming: - return web.json_response({'error': True, 'message': 'No Studio outputs supplied.'}, status=400) - async with self.studio_history_lock: - existing = self._read_studio_outputs() - outputs = self._write_studio_outputs(self._merge_studio_outputs(existing, incoming)) - - return web.json_response({ - 'error': False, - 'count': len(outputs), - 'outputs': outputs, - }) + with self.studio_history_file_lock: + incoming = [ + self._normalize_studio_output(output) for output in raw_outputs if isinstance(output, dict) + ] + if not incoming: + return web.json_response({"error": True, "message": "No Studio outputs supplied."}, status=400) + existing = self._read_studio_outputs() + outputs = self._write_studio_outputs(self._merge_studio_outputs(existing, incoming)) + state = self._read_studio_output_state() + + return web.json_response( + { + "error": False, + "count": len(outputs), + "outputs": outputs, + "previewSlots": list(state["previewSlots"].values()), + "revision": state["revision"], + } + ) async def studio_outputs_patch(self, request): - output_id = request.match_info.get('output_id') + output_id = request.match_info.get("output_id") if not output_id: - return web.json_response({'error': True, 'message': 'Missing Studio output id.'}, status=400) + return web.json_response({"error": True, "message": "Missing Studio output id."}, status=400) try: payload = await request.json() except json.JSONDecodeError: - return web.json_response({'error': True, 'message': 'Invalid JSON body.'}, status=400) + return web.json_response({"error": True, "message": "Invalid JSON body."}, status=400) async with self.studio_history_lock: - outputs = self._read_studio_outputs() - updated = False - for index, output in enumerate(outputs): - if str(output.get('id')) != output_id: - continue - outputs[index] = { - **output, - **payload, - 'id': output_id, - 'updatedAt': int(time.time() * 1000), - } - updated = True - break + with self.studio_history_file_lock: + outputs = self._read_studio_outputs() + updated = False + for index, output in enumerate(outputs): + if str(output.get("id")) != output_id: + continue + outputs[index] = { + **output, + **payload, + "id": output_id, + "updatedAt": int(time.time() * 1000), + } + updated = True + break - if not updated: - return web.json_response({'error': True, 'message': f'Studio output {output_id} was not found.'}, status=404) + if not updated: + return web.json_response( + {"error": True, "message": f"Studio output {output_id} was not found."}, status=404 + ) - outputs = self._write_studio_outputs(self._sort_studio_outputs(outputs)) + outputs = self._write_studio_outputs(self._sort_studio_outputs(outputs)) + state = self._read_studio_output_state() - return web.json_response({ - 'error': False, - 'count': len(outputs), - 'outputs': outputs, - }) + return web.json_response( + { + "error": False, + "count": len(outputs), + "outputs": outputs, + "previewSlots": list(state["previewSlots"].values()), + "revision": state["revision"], + } + ) async def studio_outputs_delete(self, request): - output_id = request.match_info.get('output_id') + output_id = request.match_info.get("output_id") if not output_id: - return web.json_response({'error': True, 'message': 'Missing Studio output id.'}, status=400) + return web.json_response({"error": True, "message": "Missing Studio output id."}, status=400) async with self.studio_history_lock: - outputs = self._read_studio_outputs() - next_outputs = [output for output in outputs if str(output.get('id')) != output_id] - if len(next_outputs) == len(outputs): - return web.json_response({'error': True, 'message': f'Studio output {output_id} was not found.'}, status=404) - outputs = self._write_studio_outputs(next_outputs) - - return web.json_response({ - 'error': False, - 'count': len(outputs), - 'outputs': outputs, - }) + with self.studio_history_file_lock: + state = self._read_studio_output_state() + outputs = state["outputs"] + next_outputs = [output for output in outputs if str(output.get("id")) != output_id] + if len(next_outputs) == len(outputs): + return web.json_response( + {"error": True, "message": f"Studio output {output_id} was not found."}, status=404 + ) + now = int(time.time() * 1000) + for key, slot in state["previewSlots"].items(): + if str(slot.get("currentOutputId") or "") != output_id: + continue + state["previewSlots"][key] = { + **slot, + "currentOutputId": None, + "status": "empty", + "updatedAt": now, + } + revision = state["revision"] + 1 + outputs = self._write_studio_output_state( + next_outputs, state["previewSlots"], revision=revision + ) + + return web.json_response( + { + "error": False, + "count": len(outputs), + "outputs": outputs, + "previewSlots": list(state["previewSlots"].values()), + "revision": revision, + } + ) async def studio_blocks_get(self, request): - limit = min(max(int(request.query.get('limit', 200)), 1), 500) + limit = min(max(int(request.query.get("limit", 200)), 1), 500) blocks = self._list_studio_blocks() - return web.json_response({ - 'error': False, - 'count': len(blocks), - 'blocks': blocks[:limit], - 'path': str(self._studio_blocks_dir()), - }) + return web.json_response( + { + "error": False, + "count": len(blocks), + "blocks": blocks[:limit], + "path": str(self._studio_blocks_dir()), + } + ) async def studio_blocks_post(self, request): try: payload = await request.json() block, block_file = self._write_studio_block(payload) except json.JSONDecodeError: - return web.json_response({'error': True, 'message': 'Invalid JSON body.'}, status=400) + return web.json_response({"error": True, "message": "Invalid JSON body."}, status=400) except ValueError as e: - return web.json_response({'error': True, 'message': str(e)}, status=400) + return web.json_response({"error": True, "message": str(e)}, status=400) except OSError as e: logger.error(f"Error saving Studio user block: {e}") - return web.json_response({'error': True, 'message': 'Could not save user block.'}, status=500) + return web.json_response({"error": True, "message": "Could not save user block."}, status=500) - return web.json_response({ - 'error': False, - 'block': block, - 'path': str(block_file), - }) + return web.json_response( + { + "error": False, + "block": block, + "path": str(block_file), + } + ) async def studio_block_get(self, request): - block_id = request.match_info.get('block_id') + block_id = request.match_info.get("block_id") if not block_id: - return web.json_response({'error': True, 'message': 'Missing user block id.'}, status=400) + return web.json_response({"error": True, "message": "Missing user block id."}, status=400) try: block = self._read_studio_block(block_id) except (ValueError, json.JSONDecodeError, UnicodeDecodeError, OSError) as e: logger.error(f"Error reading Studio user block {block_id}: {e}") - return web.json_response({'error': True, 'message': 'Could not read user block.'}, status=500) + return web.json_response({"error": True, "message": "Could not read user block."}, status=500) if block is None: - return web.json_response({'error': True, 'message': f'User block {block_id} was not found.'}, status=404) - return web.json_response({ - 'error': False, - 'block': block, - }) + return web.json_response({"error": True, "message": f"User block {block_id} was not found."}, status=404) + return web.json_response( + { + "error": False, + "block": block, + } + ) async def studio_block_delete(self, request): - block_id = request.match_info.get('block_id') + block_id = request.match_info.get("block_id") if not block_id: - return web.json_response({'error': True, 'message': 'Missing user block id.'}, status=400) + return web.json_response({"error": True, "message": "Missing user block id."}, status=400) block_file = self._studio_block_file(block_id) if not block_file.exists(): - return web.json_response({'error': True, 'message': f'User block {block_id} was not found.'}, status=404) + return web.json_response({"error": True, "message": f"User block {block_id} was not found."}, status=404) try: block_file.unlink() except OSError as e: logger.error(f"Error deleting Studio user block {block_id}: {e}") - return web.json_response({'error': True, 'message': 'Could not delete user block.'}, status=500) - return web.json_response({ - 'error': False, - 'id': self._safe_block_id(block_id), - }) + return web.json_response({"error": True, "message": "Could not delete user block."}, status=500) + return web.json_response( + { + "error": False, + "id": self._safe_block_id(block_id), + } + ) def _workflow_shares_dir(self): - return Path(self.data_dir) / 'studio' / 'shares' + return Path(self.data_dir) / "studio" / "shares" def _safe_share_id(self, share_id=None): raw_id = str(share_id or nanoid.generate(size=12)) - safe_id = ''.join(ch for ch in raw_id if ch.isalnum() or ch in ('-', '_'))[:80] + safe_id = "".join(ch for ch in raw_id if ch.isalnum() or ch in ("-", "_"))[:80] return safe_id or nanoid.generate(size=12) def _workflow_share_file(self, share_id): return self._workflow_shares_dir() / f"{self._safe_share_id(share_id)}.json" def _workflow_share_media_dir(self, share_id): - return self._workflow_shares_dir() / self._safe_share_id(share_id) / 'media' + return self._workflow_shares_dir() / self._safe_share_id(share_id) / "media" def _path_within(self, path, root): try: @@ -2179,57 +4688,51 @@ def _path_within(self, path, root): except ValueError: return False - def _safe_media_filename(self, filename, fallback='preview.bin'): + def _safe_media_filename(self, filename, fallback="preview.bin"): raw_name = Path(str(filename or fallback)).name - safe_name = ''.join(ch if ch.isalnum() or ch in ('-', '_', '.') else '-' for ch in raw_name)[:120].strip('.-') + safe_name = "".join(ch if ch.isalnum() or ch in ("-", "_", ".") else "-" for ch in raw_name)[:120].strip(".-") return safe_name or fallback def _share_media_hash(self, data): return f"sha256:bytes:{hashlib.sha256(data).hexdigest()}" - def _content_type_extension(self, content_type, fallback_url=''): - normalized = str(content_type or '').split(';')[0].strip().lower() + def _content_type_extension(self, content_type, fallback_url=""): + normalized = str(content_type or "").split(";")[0].strip().lower() explicit = { - 'image/webp': '.webp', - 'image/png': '.png', - 'image/jpeg': '.jpg', - 'image/jpg': '.jpg', - 'image/gif': '.gif', - 'video/mp4': '.mp4', - 'video/webm': '.webm', - 'audio/wav': '.wav', - 'audio/mpeg': '.mp3', - 'audio/flac': '.flac', - 'application/json': '.json', - 'text/plain': '.txt', + "image/webp": ".webp", + "image/png": ".png", + "image/jpeg": ".jpg", + "image/jpg": ".jpg", + "image/gif": ".gif", + "video/mp4": ".mp4", + "video/webm": ".webm", + "audio/wav": ".wav", + "audio/mpeg": ".mp3", + "audio/flac": ".flac", + "application/json": ".json", + "text/plain": ".txt", }.get(normalized) if explicit: return explicit - parsed_suffix = Path(urlparse(str(fallback_url or '')).path).suffix.lower() + parsed_suffix = Path(urlparse(str(fallback_url or "")).path).suffix.lower() if parsed_suffix: return parsed_suffix - return (mimetypes.guess_extension(normalized) or '.bin') if normalized else '.bin' + return (mimetypes.guess_extension(normalized) or ".bin") if normalized else ".bin" def _resolve_file_route_path(self, file): if not file: return None - file_path = Path(unquote(str(file))) - if not file_path.is_absolute(): - file_path = Path(self.work_dir) / file_path - - if not file_path.exists(): + file_path = self._resolve_managed_path_identifier(unquote(str(file))) + if file_path is None or not file_path.exists(): return None - - if self._path_within(file_path, self.work_dir) or self._path_within(file_path, self.data_dir): - return file_path - return None + return file_path def _cache_media_bytes_from_url(self, preview_url): parsed = urlparse(str(preview_url)) - parts = [unquote(part) for part in parsed.path.split('/') if part] - if len(parts) < 3 or parts[0] != 'cache': + parts = [unquote(part) for part in parsed.path.split("/") if part] + if len(parts) < 3 or parts[0] != "cache": return None node, field = parts[1], parts[2] @@ -2258,35 +4761,37 @@ def _cache_media_bytes_from_url(self, preview_url): if data is None: return None - params = self.modules.get(cached_node.module_name, {}).get(cached_node.class_name, {}).get('params', {}) - data_type = params.get(field, {}).get('type') + params = self.modules.get(cached_node.module_name, {}).get(cached_node.class_name, {}).get("params", {}) + data_type = params.get(field, {}).get("type") type_values = data_type if isinstance(data_type, list) else [data_type] query = parse_qs(parsed.query) - filename = query.get('filename', [field])[0] + filename = query.get("filename", [field])[0] - if 'image' in type_values: - image_format = query.get('format', ['WEBP'])[0].upper() - quality = query.get('quality', [100])[0] + if "image" in type_values: + image_format = query.get("format", ["WEBP"])[0].upper() + quality = query.get("quality", [100])[0] return { - 'bytes': to_bytes(data_type, data, {'format': image_format, 'quality': quality}), - 'contentType': f"image/{image_format.lower()}", - 'filename': f"{filename}.{image_format.lower()}", + "bytes": to_bytes(data_type, data, {"format": image_format, "quality": quality}), + "contentType": f"image/{image_format.lower()}", + "filename": f"{filename}.{image_format.lower()}", } - if data_type == 'text' or any(isinstance(item, str) and item.startswith('str') for item in type_values): + if data_type == "text" or any(isinstance(item, str) and item.startswith("str") for item in type_values): return { - 'bytes': str(data).encode('utf-8'), - 'contentType': 'text/plain', - 'filename': f"{filename}.txt", + "bytes": str(data).encode("utf-8"), + "contentType": "text/plain", + "filename": f"{filename}.txt", } data_path = Path(str(data)) - if data_path.exists() and (self._path_within(data_path, self.work_dir) or self._path_within(data_path, self.data_dir)): - content_type = mimetypes.guess_type(str(data_path))[0] or 'application/octet-stream' + if data_path.exists() and ( + self._path_within(data_path, self.work_dir) or self._path_within(data_path, self.data_dir) + ): + content_type = mimetypes.guess_type(str(data_path))[0] or "application/octet-stream" return { - 'bytes': data_path.read_bytes(), - 'contentType': content_type, - 'filename': data_path.name, + "bytes": data_path.read_bytes(), + "contentType": content_type, + "filename": data_path.name, } return None @@ -2294,33 +4799,39 @@ def _share_media_bytes_from_url(self, preview_url): if not isinstance(preview_url, str) or not preview_url: return None - if preview_url.startswith('data:') and ',' in preview_url: - header, payload = preview_url.split(',', 1) - content_type = header.split(';')[0].removeprefix('data:') or 'application/octet-stream' - data = base64.b64decode(payload) if ';base64' in header else unquote_to_bytes(payload) + if preview_url.startswith("data:") and "," in preview_url: + header, payload = preview_url.split(",", 1) + content_type = header.split(";")[0].removeprefix("data:") or "application/octet-stream" + if len(payload) > (MAX_WORKFLOW_SHARE_MEDIA_BYTES * 4 // 3) + 4: + raise ValueError("Embedded workflow share media exceeds the 256 MB limit.") + data = base64.b64decode(payload) if ";base64" in header else unquote_to_bytes(payload) + if len(data) > MAX_WORKFLOW_SHARE_MEDIA_BYTES: + raise ValueError("Embedded workflow share media exceeds the 256 MB limit.") return { - 'bytes': data, - 'contentType': content_type, - 'filename': f"preview{self._content_type_extension(content_type, preview_url)}", + "bytes": data, + "contentType": content_type, + "filename": f"preview{self._content_type_extension(content_type, preview_url)}", } parsed = urlparse(preview_url) - if parsed.path.startswith('/workflows/share/'): + if parsed.path.startswith("/workflows/share/"): return None - if parsed.scheme in ('http', 'https') and not parsed.path.startswith('/cache/') and parsed.path != '/file': + if parsed.scheme in ("http", "https") and not parsed.path.startswith("/cache/") and parsed.path != "/file": return None - if parsed.path.startswith('/cache/'): + if parsed.path.startswith("/cache/"): return self._cache_media_bytes_from_url(preview_url) - if parsed.path == '/file': - file_path = self._resolve_file_route_path(parse_qs(parsed.query).get('file', [''])[0]) + if parsed.path == "/file": + file_path = self._resolve_file_route_path(parse_qs(parsed.query).get("file", [""])[0]) if not file_path: return None + if file_path.stat().st_size > MAX_WORKFLOW_SHARE_MEDIA_BYTES: + raise ValueError("Workflow share preview media exceeds the 256 MB limit.") return { - 'bytes': file_path.read_bytes(), - 'contentType': mimetypes.guess_type(str(file_path))[0] or 'application/octet-stream', - 'filename': file_path.name, + "bytes": file_path.read_bytes(), + "contentType": mimetypes.guess_type(str(file_path))[0] or "application/octet-stream", + "filename": file_path.name, } return None @@ -2330,11 +4841,15 @@ def _persist_share_preview_media(self, share_id, package): media = self._share_media_bytes_from_url(preview) if not media: return package, None + if len(media.get("bytes") or b"") > MAX_WORKFLOW_SHARE_MEDIA_BYTES: + raise ValueError("Workflow share preview media exceeds the 256 MB limit.") try: safe_share_id = self._safe_share_id(share_id) - content_type = media.get('contentType') or 'application/octet-stream' - filename = self._safe_media_filename(media.get('filename'), f"preview{self._content_type_extension(content_type, preview)}") + content_type = media.get("contentType") or "application/octet-stream" + filename = self._safe_media_filename( + media.get("filename"), f"preview{self._content_type_extension(content_type, preview)}" + ) if not Path(filename).suffix: filename = f"{filename}{self._content_type_extension(content_type, preview)}" @@ -2342,110 +4857,135 @@ def _persist_share_preview_media(self, share_id, package): target_dir.mkdir(parents=True, exist_ok=True) target_path = (target_dir / filename).resolve() if not self._path_within(target_path, target_dir): - raise ValueError('Resolved share media path escaped the share media directory.') + raise ValueError("Resolved share media path escaped the share media directory.") - media_bytes = media.get('bytes') + media_bytes = media.get("bytes") if not isinstance(media_bytes, (bytes, bytearray)): return package, None - with open(target_path, 'wb') as f: + with open(target_path, "wb") as f: f.write(media_bytes) byte_hash = self._share_media_hash(bytes(media_bytes)) media_url = f"/workflows/share/{quote(safe_share_id)}/media/{quote(filename)}" next_package = deepcopy(package) - manifest = next_package.setdefault('manifest', {}) if isinstance(next_package, dict) else {} - manifest_media = manifest.get('media') if isinstance(manifest.get('media'), dict) else {} - manifest_media.update({ - 'url': media_url, - 'persistedUrl': media_url, - 'byteHash': byte_hash, - 'contentType': content_type, - 'backendShareMediaPath': str(target_path), - }) - manifest['media'] = manifest_media - - metadata = next_package.setdefault('metadata', {}) if isinstance(next_package, dict) else {} - metadata['preview'] = media_url - - latest_output = next_package.get('latestOutput') if isinstance(next_package.get('latestOutput'), dict) else None + manifest = next_package.setdefault("manifest", {}) if isinstance(next_package, dict) else {} + manifest_media = manifest.get("media") if isinstance(manifest.get("media"), dict) else {} + manifest_media.update( + { + "url": media_url, + "persistedUrl": media_url, + "byteHash": byte_hash, + "contentType": content_type, + } + ) + manifest_media.pop("backendShareMediaPath", None) + manifest["media"] = manifest_media + + metadata = next_package.setdefault("metadata", {}) if isinstance(next_package, dict) else {} + metadata["preview"] = media_url + + latest_output = ( + next_package.get("latestOutput") if isinstance(next_package.get("latestOutput"), dict) else None + ) if latest_output is not None: - latest_output['url'] = media_url - latest_output['backendShareMediaPath'] = str(target_path) - latest_output['backendShareMediaHash'] = byte_hash + latest_output["url"] = media_url + latest_output["backendShareMediaHash"] = byte_hash + latest_output.pop("backendShareMediaPath", None) persisted = { - 'url': media_url, - 'path': str(target_path), - 'byteHash': byte_hash, - 'contentType': content_type, + "url": media_url, + "filename": filename, + "byteHash": byte_hash, + "contentType": content_type, } return next_package, persisted + except ValueError: + raise except Exception as e: logger.error(f"Error persisting workflow share media {share_id}: {e}") return package, None + def _public_workflow_share(self, share): + """Strip backend filesystem details from old and new public shares.""" + + public_share = deepcopy(share) if isinstance(share, dict) else {} + persisted_media = public_share.get("persistedMedia") + if isinstance(persisted_media, dict): + persisted_media.pop("path", None) + package = public_share.get("package") + if isinstance(package, dict): + manifest = package.get("manifest") + media = manifest.get("media") if isinstance(manifest, dict) else None + if isinstance(media, dict): + media.pop("backendShareMediaPath", None) + latest_output = package.get("latestOutput") + if isinstance(latest_output, dict): + latest_output.pop("backendShareMediaPath", None) + return public_share + def _share_summary(self, share): - package = share.get('package', {}) if isinstance(share, dict) else {} - metadata = package.get('metadata', {}) if isinstance(package, dict) else {} - studio = metadata.get('studio', {}) if isinstance(metadata, dict) else {} + share = self._public_workflow_share(share) + package = share.get("package", {}) if isinstance(share, dict) else {} + metadata = package.get("metadata", {}) if isinstance(package, dict) else {} + studio = metadata.get("studio", {}) if isinstance(metadata, dict) else {} return { - 'share_id': share.get('share_id'), - 'createdAt': share.get('createdAt'), - 'updatedAt': share.get('updatedAt'), - 'url': share.get('url'), - 'modelType': studio.get('modelType'), - 'mode': studio.get('mode'), - 'prompt': studio.get('prompt'), - 'preview': self._share_preview_url(package), - 'persistedMedia': share.get('persistedMedia'), + "share_id": share.get("share_id"), + "createdAt": share.get("createdAt"), + "updatedAt": share.get("updatedAt"), + "url": share.get("url"), + "modelType": studio.get("modelType"), + "mode": studio.get("mode"), + "prompt": studio.get("prompt"), + "preview": self._share_preview_url(package), + "persistedMedia": share.get("persistedMedia"), } def _share_preview_url(self, package): if not isinstance(package, dict): return None - metadata = package.get('metadata', {}) - manifest = package.get('manifest', {}) - media = manifest.get('media', {}) if isinstance(manifest, dict) else {} - preview = metadata.get('preview') if isinstance(metadata, dict) else None + metadata = package.get("metadata", {}) + manifest = package.get("manifest", {}) + media = manifest.get("media", {}) if isinstance(manifest, dict) else {} + preview = metadata.get("preview") if isinstance(metadata, dict) else None if not preview and isinstance(media, dict): - preview = media.get('url') + preview = media.get("url") if not isinstance(preview, str): return None - if preview.startswith(('http://', 'https://', 'data:image/', '/')): + if preview.startswith(("http://", "https://", "data:image/", "/")): return preview return None def _workflow_share_preview_html(self, request, share): - share_id = self._safe_share_id(share.get('share_id')) - package = share.get('package', {}) if isinstance(share, dict) else {} - metadata = package.get('metadata', {}) if isinstance(package, dict) else {} - manifest = package.get('manifest', {}) if isinstance(package, dict) else {} - studio = metadata.get('studio', {}) if isinstance(metadata, dict) else {} - template = manifest.get('template', {}) if isinstance(manifest, dict) else {} - provenance = manifest.get('provenance', {}) if isinstance(manifest, dict) else {} + share_id = self._safe_share_id(share.get("share_id")) + package = share.get("package", {}) if isinstance(share, dict) else {} + metadata = package.get("metadata", {}) if isinstance(package, dict) else {} + manifest = package.get("manifest", {}) if isinstance(package, dict) else {} + studio = metadata.get("studio", {}) if isinstance(metadata, dict) else {} + template = manifest.get("template", {}) if isinstance(manifest, dict) else {} + provenance = manifest.get("provenance", {}) if isinstance(manifest, dict) else {} frontend_url = f"/?share={quote(share_id)}" json_url = f"/workflows/share/{quote(share_id)}?format=json" preview = self._share_preview_url(package) - title = studio.get('prompt') or template.get('templateLabel') or f"MoDiff workflow {share_id}" - prompt = studio.get('prompt') or '' - mode = studio.get('mode') or 'workflow' - model_type = studio.get('modelType') or 'unknown model' - exported_at = metadata.get('exportedAt') or manifest.get('exportedAt') or share.get('createdAt') or '' + title = studio.get("prompt") or template.get("templateLabel") or f"MoDiff workflow {share_id}" + prompt = studio.get("prompt") or "" + mode = studio.get("mode") or "workflow" + model_type = studio.get("modelType") or "unknown model" + exported_at = metadata.get("exportedAt") or manifest.get("exportedAt") or share.get("createdAt") or "" media_hash = None if isinstance(provenance, dict): - frontend = provenance.get('frontend', {}) - backend = provenance.get('backend', {}) + frontend = provenance.get("frontend", {}) + backend = provenance.get("backend", {}) if isinstance(frontend, dict): - media_hash = frontend.get('mediaHash') + media_hash = frontend.get("mediaHash") if not media_hash and isinstance(backend, dict): - media_hash = backend.get('mediaHash') + media_hash = backend.get("mediaHash") def esc(value): - return html.escape(str(value or ''), quote=True) + return html.escape(str(value or ""), quote=True) - preview_html = '' + preview_html = "" if preview: preview_html = f'Shared workflow preview' @@ -2482,7 +5022,7 @@ def esc(value):
Mode
{esc(mode)}
Model
{esc(model_type)}
Exported
{esc(exported_at)}
-
Template
{esc(template.get('templateLabel') if isinstance(template, dict) else '')}
+
Template
{esc(template.get("templateLabel") if isinstance(template, dict) else "")}
Media hash
{esc(media_hash)}
Prompt
{esc(prompt)}
@@ -2492,117 +5032,127 @@ def esc(value): """ async def workflow_shares_list(self, request): - limit = min(max(int(request.query.get('limit', 50)), 1), 200) + limit = min(max(int(request.query.get("limit", 50)), 1), 200) shares_dir = self._workflow_shares_dir() summaries = [] if shares_dir.exists(): - for share_file in shares_dir.glob('*.json'): + for share_file in shares_dir.glob("*.json"): try: - with open(share_file, 'r', encoding='utf-8') as f: + with open(share_file, "r", encoding="utf-8") as f: share = json.load(f) summaries.append(self._share_summary(share)) except (json.JSONDecodeError, UnicodeDecodeError, OSError) as e: logger.error(f"Error reading workflow share {share_file}: {e}") - summaries.sort(key=lambda item: item.get('createdAt') or '', reverse=True) - return web.json_response({ - 'error': False, - 'count': len(summaries), - 'shares': summaries[:limit], - 'path': str(shares_dir), - }) + summaries.sort(key=lambda item: item.get("createdAt") or "", reverse=True) + return web.json_response( + { + "error": False, + "count": len(summaries), + "shares": summaries[:limit], + } + ) async def workflow_share_post(self, request): try: package = await request.json() except json.JSONDecodeError: - return web.json_response({'error': True, 'message': 'Invalid JSON body.'}, status=400) + return web.json_response({"error": True, "message": "Invalid JSON body."}, status=400) if not isinstance(package, dict): - return web.json_response({'error': True, 'message': 'Workflow share package must be a JSON object.'}, status=400) + return web.json_response( + {"error": True, "message": "Workflow share package must be a JSON object."}, status=400 + ) - share_id = self._safe_share_id(package.get('share_id') or package.get('shareId')) - package, persisted_media = self._persist_share_preview_media(share_id, package) - created_at = package.get('createdAt') or int(time.time() * 1000) + share_id = self._safe_share_id(package.get("share_id") or package.get("shareId")) + try: + package, persisted_media = self._persist_share_preview_media(share_id, package) + except ValueError as exc: + return web.json_response({"error": True, "message": str(exc)}, status=413) + created_at = package.get("createdAt") or int(time.time() * 1000) url = f"/workflows/share/{share_id}" share = { - 'share_id': share_id, - 'createdAt': created_at, - 'updatedAt': int(time.time() * 1000), - 'url': url, - 'package': package, + "share_id": share_id, + "createdAt": created_at, + "updatedAt": int(time.time() * 1000), + "url": url, + "package": package, } if persisted_media: - share['persistedMedia'] = persisted_media + share["persistedMedia"] = persisted_media shares_dir = self._workflow_shares_dir() shares_dir.mkdir(parents=True, exist_ok=True) share_file = self._workflow_share_file(share_id) - temp_file = share_file.with_suffix('.tmp') + temp_file = share_file.with_suffix(".tmp") try: - with open(temp_file, 'w', encoding='utf-8') as f: + with open(temp_file, "w", encoding="utf-8") as f: json.dump(share, f, ensure_ascii=False) temp_file.replace(share_file) except OSError as e: logger.error(f"Error saving workflow share {share_id}: {e}") - return web.json_response({'error': True, 'message': str(e)}, status=500) + return web.json_response({"error": True, "message": str(e)}, status=500) - return web.json_response({ - 'error': False, - 'share_id': share_id, - 'url': url, - 'path': str(share_file), - 'persistedMedia': persisted_media, - 'share': share, - }) + return web.json_response( + { + "error": False, + "share_id": share_id, + "url": url, + "persistedMedia": persisted_media, + "share": self._public_workflow_share(share), + } + ) async def workflow_share_media_get(self, request): - share_id = self._safe_share_id(request.match_info.get('share_id')) - filename = self._safe_media_filename(request.match_info.get('filename')) + share_id = self._safe_share_id(request.match_info.get("share_id")) + filename = self._safe_media_filename(request.match_info.get("filename")) media_dir = self._workflow_share_media_dir(share_id) media_path = (media_dir / filename).resolve() if not self._path_within(media_path, media_dir): - return web.json_response({'error': True, 'message': 'Invalid workflow share media path.'}, status=403) + return web.json_response({"error": True, "message": "Invalid workflow share media path."}, status=403) if not media_path.exists() or not media_path.is_file(): - return web.json_response({'error': True, 'message': f'Workflow share media {filename} was not found.'}, status=404) + return web.json_response( + {"error": True, "message": f"Workflow share media {filename} was not found."}, status=404 + ) resp = web.FileResponse(media_path) - resp.headers['Content-Disposition'] = f'inline; filename="{filename}"' - resp.headers['Cache-Control'] = 'public, max-age=31536000, immutable' + resp.headers["Content-Disposition"] = f'inline; filename="{filename}"' + resp.headers["Cache-Control"] = "public, max-age=31536000, immutable" return resp async def workflow_share_get(self, request): - share_id = request.match_info.get('share_id') + share_id = request.match_info.get("share_id") if not share_id: - return web.json_response({'error': True, 'message': 'Missing workflow share id.'}, status=400) + return web.json_response({"error": True, "message": "Missing workflow share id."}, status=400) share_file = self._workflow_share_file(share_id) if not share_file.exists(): - return web.json_response({'error': True, 'message': f'Workflow share {share_id} was not found.'}, status=404) + return web.json_response( + {"error": True, "message": f"Workflow share {share_id} was not found."}, status=404 + ) try: - with open(share_file, 'r', encoding='utf-8') as f: + with open(share_file, "r", encoding="utf-8") as f: share = json.load(f) except (json.JSONDecodeError, UnicodeDecodeError, OSError) as e: logger.error(f"Error reading workflow share {share_id}: {e}") - return web.json_response({'error': True, 'message': str(e)}, status=500) + return web.json_response({"error": True, "message": str(e)}, status=500) - wants_html = ( - request.query.get('format') != 'json' - and 'text/html' in request.headers.get('Accept', '') - ) + wants_html = request.query.get("format") != "json" and "text/html" in request.headers.get("Accept", "") if wants_html: return web.Response( text=self._workflow_share_preview_html(request, share), - content_type='text/html', + content_type="text/html", ) - return web.json_response({ - 'error': False, - **share, - }) + return web.json_response( + { + "error": False, + **self._public_workflow_share(share), + } + ) """ ╭────────────────────────╮ @@ -2613,16 +5163,38 @@ async def workflow_share_get(self, request): async def graph(self, request): graph = await request.json() sid = graph.get("sid") - #if not sid: + # if not sid: # return web.json_response({"error": True, "message": "Missing session id"}, status=400) - task_id = await self.queue_task(self.execute_graph, (graph,), None, sid, name=f"Graph execution") - return web.json_response({ - "error": False, - "message": "Graph queued for processing", - "sid": sid, - "task_id": task_id, - }) + runtime_block = self._auto_resource_runtime_block() + if runtime_block: + issue = runtime_block["issue"] + repair_action = runtime_block["repairAction"] + return web.json_response( + { + "error": True, + "message": issue["message"], + "category": issue["category"], + "error_code": issue["code"], + "recovery_hint": repair_action["label"], + "repair_action": repair_action, + "runtime_profile": runtime_block["runtimeProfile"], + }, + status=409, + ) + + task_id = await self.queue_task(self.execute_graph, (graph,), None, sid, name="Graph execution") + preview_state = self._studio_preview_slots_for_task(task_id) + return web.json_response( + { + "error": False, + "message": "Graph queued for processing", + "sid": sid, + "task_id": task_id, + "preview_slots": preview_state["previewSlots"], + "preview_state_revision": preview_state["revision"], + } + ) def _exception_chain(self, e): chain = [] @@ -2631,7 +5203,7 @@ def _exception_chain(self, e): while current is not None and id(current) not in seen: chain.append(current) seen.add(id(current)) - current = getattr(current, '__cause__', None) or getattr(current, '__context__', None) + current = getattr(current, "__cause__", None) or getattr(current, "__context__", None) return chain def _loader_diagnostics_snapshot(self): @@ -2639,7 +5211,9 @@ def _loader_diagnostics_snapshot(self): runtime_hints = self.current_task.get("runtimeHints") if self.current_task else None runtime_hint_offload = runtime_hints.get("offloadMode") if isinstance(runtime_hints, dict) else None runtime_hint_resource_mode = runtime_hints.get("resourceMode") if isinstance(runtime_hints, dict) else None - runtime_hint_resolved_mode = runtime_hints.get("resolvedResourceMode") if isinstance(runtime_hints, dict) else None + runtime_hint_resolved_mode = ( + runtime_hints.get("resolvedResourceMode") if isinstance(runtime_hints, dict) else None + ) node_cache = getattr(self, "node_cache", {}) or {} for node_id, node in node_cache.items(): @@ -2653,31 +5227,78 @@ def _loader_diagnostics_snapshot(self): diagnostics[str(node_id)] = entry return diagnostics - def _current_run_identity_payload(self): - runtime_hints = self.current_task.get("runtimeHints") if self.current_task else None + def _run_identity_payload(self, runtime_hints): if not isinstance(runtime_hints, dict): return {} payload = {} client_run_id = runtime_hints.get("clientRunId") run_input_hash = runtime_hints.get("runInputHash") + workflow_tab_id = runtime_hints.get("workflowTabId") + workflow_canvas_epoch = runtime_hints.get("workflowCanvasEpoch") + node_id = runtime_hints.get("nodeId") if client_run_id: payload["client_run_id"] = client_run_id if run_input_hash: payload["run_input_hash"] = run_input_hash + if workflow_tab_id: + payload["workflow_tab_id"] = workflow_tab_id + if workflow_canvas_epoch is not None: + payload["workflow_canvas_epoch"] = workflow_canvas_epoch + if node_id: + payload["node_id"] = node_id + return payload + + def _run_navigation_payload(self, runtime_hints): + """Expose the captured workflow needed to navigate while execution is busy. + + This payload is intentionally attached only to queue/current snapshots, + not every progress event, so a reconnect or activity click can restore + the owning graph without repeatedly broadcasting a full document. + """ + if not isinstance(runtime_hints, dict): + return {} + payload = {} + workflow_title = runtime_hints.get("workflowTitle") + workflow_snapshot = runtime_hints.get("workflowSnapshot") + if workflow_title: + payload["workflow_title"] = workflow_title + if isinstance(workflow_snapshot, dict): + payload["workflow_snapshot"] = workflow_snapshot + return payload + + def _current_run_identity_payload(self): + runtime_hints = self.current_task.get("runtimeHints") if self.current_task else None + return self._run_identity_payload(runtime_hints) + + def _current_dynamic_message_identity_payload(self): + """Correlate node-owned UI mutations with the executing workflow.""" + + payload = self._current_run_identity_payload() + if not self.current_task: + return payload + task_id = self.current_task.get("task_id") + attempt_index = self.current_task.get("attempt_index") + sid = self.current_task.get("sid") + if task_id: + payload["task_id"] = task_id + if attempt_index is not None: + payload["attempt_index"] = attempt_index + if sid: + payload["sid"] = sid return payload def _exception_payload(self, e, task_id=None, sid=None, node_id=None, node_name=None, traceback_text=None): exception_type = type(e).__name__ message = str(e) or exception_type classification = self._classify_exception(e, message=message, exception_type=exception_type) - if classification['error_code'] == 'missing_prompt_embeddings': - message = 'Prompt embeddings are missing from Encode Prompt. Update or recreate the Studio graph after node definitions finish refreshing.' - elif classification.get('message'): - message = classification['message'] - oom = classification['category'] == 'oom' + if classification["error_code"] == "missing_prompt_embeddings": + message = "Prompt embeddings are missing from Encode Prompt. Update or recreate the Studio graph after node definitions finish refreshing." + elif classification.get("message"): + message = classification["message"] + oom = classification["category"] == "oom" memory_summary = None if oom: - memory_summary = message.split('\n')[0] + memory_summary = message.split("\n")[0] return { "task_id": task_id, @@ -2687,9 +5308,9 @@ def _exception_payload(self, e, task_id=None, sid=None, node_id=None, node_name= "message": message, "exception_type": exception_type, "traceback": traceback_text, - "category": classification['category'], - "error_code": classification['error_code'], - "recovery_hint": classification['recovery_hint'], + "category": classification["category"], + "error_code": classification["error_code"], + "recovery_hint": classification["recovery_hint"], "oom": oom, "memory_summary": memory_summary, "cuda_memory_snapshot": self._cuda_memory_snapshot(), @@ -2702,198 +5323,263 @@ def _exception_payload(self, e, task_id=None, sid=None, node_id=None, node_name= def _classify_exception(self, e, message=None, exception_type=None): exception_type = exception_type or type(e).__name__ message = message or str(e) or exception_type - explicit_error_code = getattr(e, 'modiff_error_code', None) + explicit_error_code = getattr(e, "modiff_error_code", None) if explicit_error_code: return { - 'category': getattr(e, 'modiff_category', 'runtime'), - 'error_code': explicit_error_code, - 'message': message, - 'recovery_hint': getattr(e, 'modiff_recovery_hint', None), + "category": getattr(e, "modiff_category", "runtime"), + "error_code": explicit_error_code, + "message": message, + "recovery_hint": getattr(e, "modiff_recovery_hint", None), } chain = self._exception_chain(e) - chain_text = ' | '.join(f'{type(item).__name__} {str(item) or type(item).__name__}' for item in chain) - normalized = f'{exception_type} {message} {chain_text}'.lower() + chain_text = " | ".join(f"{type(item).__name__} {str(item) or type(item).__name__}" for item in chain) + normalized = f"{exception_type} {message} {chain_text}".lower() if ( - 'outofmemory' in normalized - or 'out of memory' in normalized - or 'cuda out of memory' in normalized - or 'cublas_status_alloc_failed' in normalized - or 'cusolver_status_alloc_failed' in normalized + "outofmemory" in normalized + or "out of memory" in normalized + or "cuda out of memory" in normalized + or "cublas_status_alloc_failed" in normalized + or "cusolver_status_alloc_failed" in normalized ): return { - 'category': 'oom', - 'error_code': 'cuda_oom', - 'message': next((str(item) for item in reversed(chain) if 'out of memory' in (str(item) or '').lower() or 'outofmemory' in type(item).__name__.lower()), message), - 'recovery_hint': 'Release accelerator cache, close other accelerator-heavy apps, apply the Low VRAM preset, or switch to a smaller compatible model.', + "category": "oom", + "error_code": "cuda_oom", + "message": next( + ( + str(item) + for item in reversed(chain) + if "out of memory" in (str(item) or "").lower() or "outofmemory" in type(item).__name__.lower() + ), + message, + ), + "recovery_hint": "Release accelerator cache, close other accelerator-heavy apps, apply the Low VRAM preset, or switch to a smaller compatible model.", } if any(isinstance(item, MissingConnectedOutputError) for item in chain): return { - 'category': 'graph_incomplete', - 'error_code': 'missing_connected_output', - 'recovery_hint': 'An upstream node did not produce a connected output. Update or recreate the graph; if this followed a loader failure, inspect loader diagnostics.', + "category": "graph_incomplete", + "error_code": "missing_connected_output", + "recovery_hint": "An upstream node did not produce a connected output. Update or recreate the graph; if this followed a loader failure, inspect loader diagnostics.", } - if 'illegal memory access' in normalized or 'cudaerrorillegaladdress' in normalized: + if "illegal memory access" in normalized or "cudaerrorillegaladdress" in normalized: return { - 'category': 'cuda_context', - 'error_code': 'cuda_context_poisoned', - 'message': next((str(item) for item in reversed(chain) if 'illegal memory access' in (str(item) or '').lower()), message), - 'recovery_hint': 'CUDA reported an illegal memory access. Stop this run, restart the backend process, and retry with a safer execution plan so the current Python CUDA context is not reused.', + "category": "cuda_context", + "error_code": "cuda_context_poisoned", + "message": next( + (str(item) for item in reversed(chain) if "illegal memory access" in (str(item) or "").lower()), + message, + ), + "recovery_hint": "CUDA reported an illegal memory access. Stop this run, restart the backend process, and retry with a safer execution plan so the current Python CUDA context is not reused.", } - if 'cublas_status_not_supported' in normalized or 'cublasltmatmulalgogetheuristic' in normalized: + if "cublas_status_not_supported" in normalized or "cublasltmatmulalgogetheuristic" in normalized: return { - 'category': 'cuda_kernel', - 'error_code': 'cuda_kernel_unsupported', - 'message': next((str(item) for item in reversed(chain) if 'cublas' in (str(item) or '').lower()), message), - 'recovery_hint': 'The quantized CUDA kernel used by this model path is not supported by the current PyTorch/bitsandbytes/CUDA combination. Try a non-quantized smaller model path, update the CUDA/PyTorch/bitsandbytes stack, or use a backend path that provides compatible Qwen weights.', + "category": "cuda_kernel", + "error_code": "cuda_kernel_unsupported", + "message": next( + (str(item) for item in reversed(chain) if "cublas" in (str(item) or "").lower()), message + ), + "recovery_hint": "The quantized CUDA kernel used by this model path is not supported by the current PyTorch/bitsandbytes/CUDA combination. Try a non-quantized smaller model path, update the CUDA/PyTorch/bitsandbytes stack, or use a backend path that provides compatible Qwen weights.", } if ( - (isinstance(e, KeyError) and str(e).strip("'\"") == 'embeddings') + (isinstance(e, KeyError) and str(e).strip("'\"") == "embeddings") or "keyerror 'embeddings'" in normalized or 'keyerror "embeddings"' in normalized ): return { - 'category': 'graph_incomplete', - 'error_code': 'missing_prompt_embeddings', - 'recovery_hint': 'Prompt embeddings were not ready or connected. Update or recreate the Studio graph after node definitions finish refreshing, then retry.', + "category": "graph_incomplete", + "error_code": "missing_prompt_embeddings", + "recovery_hint": "Prompt embeddings were not ready or connected. Update or recreate the Studio graph after node definitions finish refreshing, then retry.", } - if isinstance(e, (ModuleNotFoundError, ImportError)) or 'no module named' in normalized or 'cannot import name' in normalized: + if ( + isinstance(e, (ModuleNotFoundError, ImportError)) + or "no module named" in normalized + or "cannot import name" in normalized + ): return { - 'category': 'missing_dependency', - 'error_code': 'missing_dependency', - 'recovery_hint': 'Install or repair the missing Python package, then restart the backend.', + "category": "missing_dependency", + "error_code": "missing_dependency", + "recovery_hint": "Install or repair the missing Python package, then restart the backend.", } if ( isinstance(e, FileNotFoundError) - or 'model not found' in normalized - or 'missing model' in normalized - or 'no such file or directory' in normalized - or 'localentrynotfound' in normalized - or 'entrynotfound' in normalized - or 'repo not found' in normalized - or 'repository not found' in normalized + or "model not found" in normalized + or "missing model" in normalized + or "no such file or directory" in normalized + or "localentrynotfound" in normalized + or "entrynotfound" in normalized + or "repo not found" in normalized + or "repository not found" in normalized ): return { - 'category': 'missing_model', - 'error_code': 'missing_model', - 'recovery_hint': 'Open Setup, refresh model indexes, then install or relink the missing model package.', + "category": "missing_model", + "error_code": "missing_model", + "recovery_hint": "Open Setup, refresh model indexes, then install or relink the missing model package.", } if ( isinstance(e, ConnectionError) - or 'connection refused' in normalized - or 'connection reset' in normalized - or 'backend unavailable' in normalized - or 'server disconnected' in normalized + or "connection refused" in normalized + or "connection reset" in normalized + or "backend unavailable" in normalized + or "server disconnected" in normalized ): return { - 'category': 'backend_unavailable', - 'error_code': 'backend_unavailable', - 'recovery_hint': 'Check that the backend is still running, then retry the workflow.', + "category": "backend_unavailable", + "error_code": "backend_unavailable", + "recovery_hint": "Check that the backend is still running, then retry the workflow.", + } + + if isinstance(e, PermissionError) or "permission denied" in normalized or "access is denied" in normalized: + return { + "category": "permission", + "error_code": "permission_denied", + "recovery_hint": "Check file permissions and whether another process is locking the target path.", } - if isinstance(e, PermissionError) or 'permission denied' in normalized or 'access is denied' in normalized: + if isinstance(e, asyncio.CancelledError) or "cancelled" in normalized or "interrupted" in normalized: return { - 'category': 'permission', - 'error_code': 'permission_denied', - 'recovery_hint': 'Check file permissions and whether another process is locking the target path.', + "category": "interrupted", + "error_code": "run_interrupted", + "recovery_hint": "The run was interrupted. Retry when the backend queue is idle.", } - if isinstance(e, asyncio.CancelledError) or 'cancelled' in normalized or 'interrupted' in normalized: + if any(isinstance(item, (ValueError, TypeError)) for item in chain): return { - 'category': 'interrupted', - 'error_code': 'run_interrupted', - 'recovery_hint': 'The run was interrupted. Retry when the backend queue is idle.', + "category": "input_validation", + "error_code": "invalid_node_input", + "message": next( + (str(item) for item in reversed(chain) if isinstance(item, (ValueError, TypeError)) and str(item)), + message, + ), + "recovery_hint": "Correct the referenced prompt, dimensions, mode, or node input and retry. The installed model remains runnable.", } return { - 'category': 'runtime_error', - 'error_code': 'runtime_error', - 'recovery_hint': 'Review the run details, fix the referenced node or input, and retry.', + "category": "runtime_error", + "error_code": "runtime_error", + "recovery_hint": "Review the run details, fix the referenced node or input, and retry.", } def _runtime_fingerprint(self): packages = { - 'python': sys.version.split(' ')[0], - 'platform': platform.platform(), + "python": sys.version.split(" ")[0], + "platform": platform.platform(), } - for package_name in ('diffusers', 'transformers', 'accelerate', 'bitsandbytes'): + for package_name in ("diffusers", "transformers", "accelerate", "bitsandbytes"): try: packages[package_name] = metadata.version(package_name) except Exception: packages[package_name] = None try: - hardware = get_hardware_snapshot(self.data_dir) - torch_metadata = hardware.get('torch') if isinstance(hardware.get('torch'), dict) else {} + # Runtime proof must observe settings applied immediately before + # execution. The normal hardware snapshot cache can otherwise retain + # pre-run deterministic flags and make identical duplicate runs look + # like different runtimes. + hardware = get_hardware_snapshot(self.data_dir, refresh=True) + torch_metadata = hardware.get("torch") if isinstance(hardware.get("torch"), dict) else {} legacy_status = legacy_torch_status(hardware) - if torch_metadata.get('available'): - packages['torch'] = torch_metadata.get('version') + if torch_metadata.get("available"): + packages["torch"] = torch_metadata.get("version") torch_state = { - 'cuda_available': legacy_status.get('cuda_available', False), - 'cuda_device_count': legacy_status.get('cuda_device_count', 0), - 'cuda_device_name': legacy_status.get('cuda_device_name'), - 'cudnn_version': torch_metadata.get('cudnn_version'), - 'cudnn_deterministic': torch_metadata.get('cudnn_deterministic'), - 'cudnn_benchmark': torch_metadata.get('cudnn_benchmark'), - 'deterministic_algorithms': torch_metadata.get('deterministic_algorithms'), + "cuda_available": legacy_status.get("cuda_available", False), + "cuda_device_count": legacy_status.get("cuda_device_count", 0), + "cuda_device_name": legacy_status.get("cuda_device_name"), + "xpu_available": legacy_status.get("xpu_available", False), + "xpu_device_count": legacy_status.get("xpu_device_count", 0), + "xpu_devices": legacy_status.get("xpu_devices"), + "cudnn_version": torch_metadata.get("cudnn_version"), + "cudnn_deterministic": torch_metadata.get("cudnn_deterministic"), + "cudnn_benchmark": torch_metadata.get("cudnn_benchmark"), + "deterministic_algorithms": torch_metadata.get("deterministic_algorithms"), } else: - errors = torch_metadata.get('errors') if isinstance(torch_metadata.get('errors'), dict) else {} - torch_state = {'error': errors.get('import') or 'torch is unavailable'} - - if torch_state.get('cuda_available') and torch_state.get('cuda_device_count', 0) > 0: - torch_state.update({ - 'cuda_device_total_memory_bytes': legacy_status.get('cuda_device_total_memory_bytes'), - 'cuda_memory_free_bytes': legacy_status.get('cuda_memory_free_bytes'), - 'cuda_memory_total_bytes': legacy_status.get('cuda_memory_total_bytes'), - }) + errors = torch_metadata.get("errors") if isinstance(torch_metadata.get("errors"), dict) else {} + torch_state = {"error": errors.get("import") or "torch is unavailable"} + + if torch_state.get("cuda_available") and torch_state.get("cuda_device_count", 0) > 0: + torch_state.update( + { + "cuda_device_total_memory_bytes": legacy_status.get("cuda_device_total_memory_bytes"), + "cuda_memory_free_bytes": legacy_status.get("cuda_memory_free_bytes"), + "cuda_memory_total_bytes": legacy_status.get("cuda_memory_total_bytes"), + } + ) try: - torch = import_module('torch') + torch = import_module("torch") capability = torch.cuda.get_device_capability(0) - torch_state['cuda_device_capability'] = '.'.join(str(item) for item in capability) + torch_state["cuda_device_capability"] = ".".join(str(item) for item in capability) except Exception as capability_error: - torch_state['cuda_device_capability_error'] = str(capability_error) + torch_state["cuda_device_capability_error"] = str(capability_error) except Exception as hardware_error: - hardware = {'error': str(hardware_error)} - torch_state = {'error': str(hardware_error)} - - fingerprint_payload = { - 'packages': packages, - 'torch': torch_state, - 'work_dir': str(self.work_dir), - 'data_dir': str(self.data_dir), + hardware = {"error": str(hardware_error)} + torch_state = {"error": str(hardware_error)} + + returned_payload = { + "packages": packages, + "torch": torch_state, + "work_dir": str(self.work_dir), + "data_dir": str(self.data_dir), } - fingerprint = hashlib.sha256(json.dumps(fingerprint_payload, sort_keys=True, default=str).encode('utf-8')).hexdigest() - return { - 'fingerprint': f'sha256:{fingerprint}', - **fingerprint_payload, - 'hardware': hardware, + execution_torch_identity = { + key: value for key, value in torch_state.items() if key not in {"cuda_memory_free_bytes"} + } + resource_torch_identity = { + key: value + for key, value in execution_torch_identity.items() + if key + not in { + "cudnn_deterministic", + "cudnn_benchmark", + "deterministic_algorithms", + } + } + execution_identity = { + **returned_payload, + "torch": execution_torch_identity, + } + resource_identity = { + **returned_payload, + "torch": resource_torch_identity, } + fingerprint = hashlib.sha256( + json.dumps(execution_identity, sort_keys=True, default=str).encode("utf-8") + ).hexdigest() + resource_fingerprint = hashlib.sha256( + json.dumps(resource_identity, sort_keys=True, default=str).encode("utf-8") + ).hexdigest() + result = { + "fingerprint": f"sha256:{fingerprint}", + "resourceFingerprint": f"sha256:{resource_fingerprint}", + **returned_payload, + "hardware": hardware, + } + self._last_runtime_fingerprint = deepcopy(result) + return result def _extract_graph_seed(self, graph, deterministic_options): - seed = deterministic_options.get('seed') if isinstance(deterministic_options, dict) else None + seed = deterministic_options.get("seed") if isinstance(deterministic_options, dict) else None if seed is not None: try: return int(seed) except (TypeError, ValueError): return None - for node in graph.get('nodes', {}).values(): + for node in graph.get("nodes", {}).values(): if not isinstance(node, dict): continue - params = node.get('params', {}) + params = node.get("params", {}) if not isinstance(params, dict): continue for key, param in params.items(): - if key != 'seed' or not isinstance(param, dict): + if key != "seed" or not isinstance(param, dict): continue - value = param.get('value') + value = param.get("value") try: return int(value) except (TypeError, ValueError): @@ -2903,82 +5589,92 @@ def _extract_graph_seed(self, graph, deterministic_options): def _deterministic_warnings(self, graph): warnings = [] - for node_id, node in graph.get('nodes', {}).items(): + for node_id, node in graph.get("nodes", {}).items(): if not isinstance(node, dict): continue - params = node.get('params', {}) + params = node.get("params", {}) if not isinstance(params, dict): continue for key, param in params.items(): if not isinstance(param, dict): continue - if param.get('display') == 'random': - warnings.append(f'{node_id}.{key} is still marked random in the API graph.') - if key == 'seed' and param.get('value') in (None, '', -1): - warnings.append(f'{node_id}.seed is not locked.') + if param.get("display") == "random": + warnings.append(f"{node_id}.{key} is still marked random in the API graph.") + if key == "seed" and param.get("value") in (None, "", -1): + warnings.append(f"{node_id}.seed is not locked.") return warnings def _apply_deterministic_mode(self, graph): - options = graph.get('deterministicMode') + options = graph.get("deterministicMode") if options is True: - options = {'enabled': True} - if not isinstance(options, dict) or not options.get('enabled'): + options = {"enabled": True} + if not isinstance(options, dict) or not options.get("enabled"): return None seed = self._extract_graph_seed(graph, options) applied = { - 'enabled': True, - 'seed': seed, - 'strict': bool(options.get('strict', True)), - 'warnings': self._deterministic_warnings(graph), - 'settings': { - 'python_random': False, - 'numpy_random': False, - 'torch_manual_seed': False, - 'torch_cuda_manual_seed_all': False, - 'torch_deterministic_algorithms': False, - 'cudnn_benchmark': None, - 'cudnn_deterministic': None, - 'allow_tf32': None, + "enabled": True, + "seed": seed, + "strict": bool(options.get("strict", True)), + "warnings": self._deterministic_warnings(graph), + "settings": { + "python_random": False, + "numpy_random": False, + "torch_manual_seed": False, + "torch_cuda_manual_seed_all": False, + "torch_deterministic_algorithms": False, + "cudnn_benchmark": None, + "cudnn_deterministic": None, + "allow_tf32": None, }, } if seed is None: - applied['warnings'].append('No fixed seed found for deterministic execution.') + if applied["strict"]: + raise ValueError("Strict deterministic execution requires a fixed seed.") + applied["warnings"].append("No fixed seed found for deterministic execution.") else: - os.environ['PYTHONHASHSEED'] = str(seed) + os.environ["PYTHONHASHSEED"] = str(seed) random.seed(seed) - applied['settings']['python_random'] = True + applied["settings"]["python_random"] = True try: import numpy as np - np.random.seed(seed % (2 ** 32)) - applied['settings']['numpy_random'] = True + + np.random.seed(seed % (2**32)) + applied["settings"]["numpy_random"] = True except Exception as e: - applied['warnings'].append(f'NumPy seed was not applied: {e}') + if applied["strict"]: + raise RuntimeError(f"Strict deterministic mode could not seed NumPy: {e}") from e + applied["warnings"].append(f"NumPy seed was not applied: {e}") try: import torch + torch.manual_seed(seed) - applied['settings']['torch_manual_seed'] = True + applied["settings"]["torch_manual_seed"] = True if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) - applied['settings']['torch_cuda_manual_seed_all'] = True - if hasattr(torch, 'use_deterministic_algorithms'): - torch.use_deterministic_algorithms(True, warn_only=True) - applied['settings']['torch_deterministic_algorithms'] = True - if hasattr(torch.backends, 'cudnn'): + applied["settings"]["torch_cuda_manual_seed_all"] = True + if applied["strict"]: + if not hasattr(torch, "use_deterministic_algorithms"): + raise RuntimeError("This Torch build does not expose deterministic algorithm enforcement.") + torch.use_deterministic_algorithms(True, warn_only=False) + applied["settings"]["torch_deterministic_algorithms"] = True + if applied["strict"] and hasattr(torch.backends, "cudnn"): torch.backends.cudnn.benchmark = False torch.backends.cudnn.deterministic = True - applied['settings']['cudnn_benchmark'] = False - applied['settings']['cudnn_deterministic'] = True - if hasattr(torch.backends, 'cuda'): + applied["settings"]["cudnn_benchmark"] = False + applied["settings"]["cudnn_deterministic"] = True + if applied["strict"] and hasattr(torch.backends, "cuda"): torch.backends.cuda.matmul.allow_tf32 = False - applied['settings']['allow_tf32'] = False - if hasattr(torch.backends, 'cudnn'): + applied["settings"]["allow_tf32"] = False + if applied["strict"] and hasattr(torch.backends, "cudnn"): torch.backends.cudnn.allow_tf32 = False except Exception as e: - applied['warnings'].append(f'Torch deterministic settings were not fully applied: {e}') + if applied["strict"]: + raise RuntimeError(f"Strict deterministic Torch settings could not be applied: {e}") from e + applied["warnings"].append(f"Torch deterministic settings were not fully applied: {e}") return applied @@ -2987,197 +5683,297 @@ def _coerce_runtime_hints(self, value): return None allowed = { - 'source', - 'device', - 'cudaIndex', - 'cudaMemoryFreeBytes', - 'cudaMemoryTotalBytes', - 'modelFamily', - 'modelType', - 'modelRepo', - 'modelName', - 'resolvedModelRepo', - 'resolvedArtifact', - 'executionPath', - 'pipelineClass', - 'dtype', - 'resourceMode', - 'resolvedResourceMode', - 'quantizationMode', - 'quantizedComponents', - 'autoOffload', - 'offloadMode', - 'offloadDiskPath', - 'supportedOffloadModes', - 'resourcePlan', - 'autoResourcePlan', - 'autoResourceCandidates', - 'autoResourceProofStatus', - 'autoResourceCandidateId', - 'resourceRetryModes', - 'resourceRetryPlans', - 'resourceRetryAttempt', - 'resourceRetryHistory', - 'resourceRetryLastError', - 'resourceRetryLastCode', - 'cudaBudgetPolicy', - 'enforceCudaBudget', - 'compatibilityProbe', - 'compatibilityStatus', - 'lowVramMode', - 'requestedCudaReserveBytes', - 'requestedCudaBudgetBytes', - 'clientRunId', - 'runInputHash', + "source", + "device", + "cudaIndex", + "cudaMemoryFreeBytes", + "cudaMemoryTotalBytes", + "modelFamily", + "modelType", + "modelRepo", + "modelName", + "resolvedModelRepo", + "resolvedArtifact", + "modelDependencies", + "executionPath", + "pipelineClass", + "dtype", + "resourceMode", + "resolvedResourceMode", + "quantizationMode", + "quantizedComponents", + "autoOffload", + "offloadMode", + "deviceMap", + "attentionBackend", + "regionalCompile", + "denoiserCache", + "channelsLast", + "layerwiseCasting", + "offloadDiskPath", + "supportedOffloadModes", + "resourcePlan", + "autoResourcePlan", + "autoResourceCandidates", + "autoResourceProofStatus", + "autoResourceCandidateId", + "resourceRetryModes", + "resourceRetryPlans", + "resourceRetryAttempt", + "resourceRetryHistory", + "resourceRetryLastError", + "resourceRetryLastCode", + "cudaBudgetPolicy", + "enforceCudaBudget", + "compatibilityProbe", + "compatibilityStatus", + "lowVramMode", + "requestedCudaReserveBytes", + "requestedCudaBudgetBytes", + "clientRunId", + "runInputHash", + "workflowTabId", + "workflowCanvasEpoch", + "workflowTitle", + "workflowSnapshot", + "nodeId", + "maxRuntimeSeconds", + "autoFieldOverrides", + "optimizationQualificationForm", } hints = {key: value.get(key) for key in allowed if key in value} for key in ( - 'source', - 'device', - 'modelFamily', - 'modelType', - 'modelRepo', - 'modelName', - 'resolvedModelRepo', - 'resolvedArtifact', - 'executionPath', - 'pipelineClass', - 'dtype', - 'resourceMode', - 'resolvedResourceMode', - 'quantizationMode', - 'offloadMode', - 'offloadDiskPath', - 'resourceRetryLastError', - 'resourceRetryLastCode', - 'cudaBudgetPolicy', - 'compatibilityStatus', - 'autoResourceProofStatus', - 'autoResourceCandidateId', - 'clientRunId', - 'runInputHash', + "source", + "device", + "modelFamily", + "modelType", + "modelRepo", + "modelName", + "resolvedModelRepo", + "resolvedArtifact", + "executionPath", + "pipelineClass", + "dtype", + "resourceMode", + "resolvedResourceMode", + "quantizationMode", + "offloadMode", + "offloadDiskPath", + "resourceRetryLastError", + "resourceRetryLastCode", + "cudaBudgetPolicy", + "compatibilityStatus", + "autoResourceProofStatus", + "autoResourceCandidateId", + "clientRunId", + "runInputHash", + "workflowTabId", + "nodeId", ): if key in hints and hints[key] is not None and not isinstance(hints[key], str): hints[key] = str(hints[key]) - for key in ('quantizedComponents', 'supportedOffloadModes', 'resourceRetryModes'): + workflow_canvas_epoch = hints.get("workflowCanvasEpoch") + if workflow_canvas_epoch is not None and ( + isinstance(workflow_canvas_epoch, bool) + or not isinstance(workflow_canvas_epoch, int) + or workflow_canvas_epoch < 0 + or workflow_canvas_epoch > 9_007_199_254_740_991 + ): + hints.pop("workflowCanvasEpoch", None) + + for key in ("quantizedComponents", "supportedOffloadModes", "resourceRetryModes"): if key in hints and hints[key] is not None: if isinstance(hints[key], list): hints[key] = [str(item) for item in hints[key] if item is not None] else: hints.pop(key, None) - if 'resourcePlan' in hints and hints['resourcePlan'] is not None and not isinstance(hints['resourcePlan'], dict): - hints.pop('resourcePlan', None) + if "modelDependencies" in hints and hints["modelDependencies"] is not None: + dependencies = hints["modelDependencies"] + if isinstance(dependencies, list): + hints["modelDependencies"] = [ + {key: str(item[key]) for key in ("id", "kind", "repo") if key in item and item[key] is not None} + for item in dependencies + if isinstance(item, dict) and item.get("repo") + ] + else: + hints.pop("modelDependencies", None) + + if ( + "resourcePlan" in hints + and hints["resourcePlan"] is not None + and not isinstance(hints["resourcePlan"], dict) + ): + hints.pop("resourcePlan", None) - if 'autoResourcePlan' in hints and hints['autoResourcePlan'] is not None and not isinstance(hints['autoResourcePlan'], dict): - hints.pop('autoResourcePlan', None) + if ( + "autoResourcePlan" in hints + and hints["autoResourcePlan"] is not None + and not isinstance(hints["autoResourcePlan"], dict) + ): + hints.pop("autoResourcePlan", None) - if 'autoResourceCandidates' in hints and hints['autoResourceCandidates'] is not None and not isinstance(hints['autoResourceCandidates'], list): - hints.pop('autoResourceCandidates', None) + if ( + "autoResourceCandidates" in hints + and hints["autoResourceCandidates"] is not None + and not isinstance(hints["autoResourceCandidates"], list) + ): + hints.pop("autoResourceCandidates", None) - if 'resourceRetryHistory' in hints and hints['resourceRetryHistory'] is not None and not isinstance(hints['resourceRetryHistory'], list): - hints.pop('resourceRetryHistory', None) + if ( + "resourceRetryHistory" in hints + and hints["resourceRetryHistory"] is not None + and not isinstance(hints["resourceRetryHistory"], list) + ): + hints.pop("resourceRetryHistory", None) - if 'resourceRetryPlans' in hints and hints['resourceRetryPlans'] is not None: - if isinstance(hints['resourceRetryPlans'], list): + if "resourceRetryPlans" in hints and hints["resourceRetryPlans"] is not None: + if isinstance(hints["resourceRetryPlans"], list): plans = [] - for item in hints['resourceRetryPlans']: + for item in hints["resourceRetryPlans"]: if isinstance(item, dict): plans.append(deepcopy(item)) - hints['resourceRetryPlans'] = plans + hints["resourceRetryPlans"] = plans else: - hints.pop('resourceRetryPlans', None) + hints.pop("resourceRetryPlans", None) - if 'compatibilityProbe' in hints and hints['compatibilityProbe'] is not None and not isinstance(hints['compatibilityProbe'], dict): - hints.pop('compatibilityProbe', None) + if ( + "compatibilityProbe" in hints + and hints["compatibilityProbe"] is not None + and not isinstance(hints["compatibilityProbe"], dict) + ): + hints.pop("compatibilityProbe", None) - for key in ('cudaIndex', 'cudaMemoryFreeBytes', 'cudaMemoryTotalBytes', 'requestedCudaReserveBytes', 'requestedCudaBudgetBytes', 'resourceRetryAttempt'): + for key in ( + "cudaIndex", + "cudaMemoryFreeBytes", + "cudaMemoryTotalBytes", + "requestedCudaReserveBytes", + "requestedCudaBudgetBytes", + "resourceRetryAttempt", + "maxRuntimeSeconds", + ): if key in hints and hints[key] is not None: try: hints[key] = int(hints[key]) except (TypeError, ValueError): hints.pop(key, None) - - for key in ('autoOffload', 'lowVramMode'): + if "workflowTitle" in hints: + workflow_title = hints["workflowTitle"] + if workflow_title is None: + hints.pop("workflowTitle", None) + else: + hints["workflowTitle"] = str(workflow_title).strip()[:256] + if not hints["workflowTitle"]: + hints.pop("workflowTitle", None) + if "workflowSnapshot" in hints: + workflow_snapshot = hints["workflowSnapshot"] + if isinstance(workflow_snapshot, dict): + hints["workflowSnapshot"] = deepcopy(workflow_snapshot) + else: + hints.pop("workflowSnapshot", None) + if "autoFieldOverrides" in hints: + overrides = hints["autoFieldOverrides"] + if isinstance(overrides, list): + hints["autoFieldOverrides"] = [ + { + key: deepcopy(item[key]) + for key in ("schemaVersion", "nodeId", "fieldKey", "formKey", "value", "updatedAt") + if key in item + } + for item in overrides[:256] + if isinstance(item, dict) + and isinstance(item.get("nodeId"), str) + and isinstance(item.get("fieldKey"), str) + ] + else: + hints.pop("autoFieldOverrides", None) + if "maxRuntimeSeconds" in hints: + # Quality-first local video models can legitimately need more than + # six hours at their upstream-recommended step count. Keep a hard + # safety ceiling, but do not force users to reduce sampling quality + # merely to fit the old gallery-oriented limit. + hints["maxRuntimeSeconds"] = max(60, min(43200, hints["maxRuntimeSeconds"])) + + for key in ("autoOffload", "lowVramMode"): if key in hints and hints[key] is not None: hints[key] = bool(hints[key]) - if 'enforceCudaBudget' in hints and hints['enforceCudaBudget'] is not None: - hints['enforceCudaBudget'] = bool(hints['enforceCudaBudget']) - if hints.get('cudaBudgetPolicy') not in (None, 'advisory', 'enforced'): - hints.pop('cudaBudgetPolicy', None) + if "enforceCudaBudget" in hints and hints["enforceCudaBudget"] is not None: + hints["enforceCudaBudget"] = bool(hints["enforceCudaBudget"]) + if hints.get("cudaBudgetPolicy") not in (None, "advisory", "enforced"): + hints.pop("cudaBudgetPolicy", None) return hints def _cuda_index_from_runtime_hints(self, hints): if not hints: return None - if isinstance(hints.get('cudaIndex'), int): - return hints.get('cudaIndex') + if isinstance(hints.get("cudaIndex"), int): + return hints.get("cudaIndex") - device = str(hints.get('device') or '').strip().lower() - match = re.match(r'^cuda(?::(\d+))?$', device) + device = str(hints.get("device") or "").strip().lower() + match = re.match(r"^cuda(?::(\d+))?$", device) if not match: return None return int(match.group(1) or 0) def _apply_cuda_runtime_budget(self, runtime_hints): result = { - 'applied': False, - 'reason': 'No CUDA runtime hints were provided.', + "applied": False, + "reason": "No CUDA runtime hints were provided.", } cuda_index = self._cuda_index_from_runtime_hints(runtime_hints) if cuda_index is None: if runtime_hints: - result['reason'] = 'Runtime hints did not target a CUDA device.' + result["reason"] = "Runtime hints did not target a CUDA device." return result try: - torch = import_module('torch') + torch = import_module("torch") except Exception as e: return { **result, - 'reason': f'torch import failed: {e}', - 'cuda_index': cuda_index, + "reason": f"torch import failed: {e}", + "cuda_index": cuda_index, } if not torch.cuda.is_available(): return { **result, - 'reason': 'CUDA is not available in this backend process.', - 'cuda_index': cuda_index, + "reason": "CUDA is not available in this backend process.", + "cuda_index": cuda_index, } device_count = int(torch.cuda.device_count()) if cuda_index < 0 or cuda_index >= device_count: return { **result, - 'reason': f'CUDA device {cuda_index} is not available.', - 'cuda_index': cuda_index, - 'device_count': device_count, + "reason": f"CUDA device {cuda_index} is not available.", + "cuda_index": cuda_index, + "device_count": device_count, } - enforce_budget = ( - (runtime_hints or {}).get('enforceCudaBudget') is True - or (runtime_hints or {}).get('cudaBudgetPolicy') == 'enforced' - ) + enforce_budget = (runtime_hints or {}).get("enforceCudaBudget") is True or (runtime_hints or {}).get( + "cudaBudgetPolicy" + ) == "enforced" if not enforce_budget: try: torch.cuda.set_per_process_memory_fraction(1.0, cuda_index) - reset_reason = 'CUDA budget is advisory; reset PyTorch process memory fraction to full device.' + reset_reason = "CUDA budget is advisory; reset PyTorch process memory fraction to full device." except Exception as e: - reset_reason = f'CUDA budget is advisory; could not reset PyTorch process memory fraction: {e}' + reset_reason = f"CUDA budget is advisory; could not reset PyTorch process memory fraction: {e}" return { **result, - 'reason': reset_reason, - 'cuda_index': cuda_index, - 'cuda_budget_policy': 'advisory', - 'fraction': 1.0, - 'model_repo': runtime_hints.get('modelRepo') if runtime_hints else None, - 'model_name': runtime_hints.get('modelName') if runtime_hints else None, - 'execution_path': runtime_hints.get('executionPath') if runtime_hints else None, - 'offload_mode': runtime_hints.get('offloadMode') if runtime_hints else None, + "reason": reset_reason, + "cuda_index": cuda_index, + "cuda_budget_policy": "advisory", + "fraction": 1.0, + "model_repo": runtime_hints.get("modelRepo") if runtime_hints else None, + "model_name": runtime_hints.get("modelName") if runtime_hints else None, + "execution_path": runtime_hints.get("executionPath") if runtime_hints else None, + "offload_mode": runtime_hints.get("offloadMode") if runtime_hints else None, } try: @@ -3191,31 +5987,39 @@ def _apply_cuda_runtime_budget(self, runtime_hints): except Exception as e: return { **result, - 'reason': f'CUDA memory info unavailable: {e}', - 'cuda_index': cuda_index, + "reason": f"CUDA memory info unavailable: {e}", + "cuda_index": cuda_index, } - gib = 1024 ** 3 - requested_reserve = runtime_hints.get('requestedCudaReserveBytes') if runtime_hints else None - reserve_bytes = requested_reserve if isinstance(requested_reserve, int) and requested_reserve > 0 else max(gib, int(total_bytes * 0.1)) + gib = 1024**3 + requested_reserve = runtime_hints.get("requestedCudaReserveBytes") if runtime_hints else None + reserve_bytes = ( + requested_reserve + if isinstance(requested_reserve, int) and requested_reserve > 0 + else max(gib, int(total_bytes * 0.1)) + ) reserve_bytes = min(reserve_bytes, max(total_bytes - 1, 0)) free_budget = max(0, free_bytes - reserve_bytes) total_budget = max(0, total_bytes - reserve_bytes) - requested_budget = runtime_hints.get('requestedCudaBudgetBytes') if runtime_hints else None + requested_budget = runtime_hints.get("requestedCudaBudgetBytes") if runtime_hints else None budget_candidates = [free_budget, total_budget] if isinstance(requested_budget, int) and requested_budget > 0: budget_candidates.append(requested_budget) - applied_budget = min(candidate for candidate in budget_candidates if candidate > 0) if any(candidate > 0 for candidate in budget_candidates) else 0 + applied_budget = ( + min(candidate for candidate in budget_candidates if candidate > 0) + if any(candidate > 0 for candidate in budget_candidates) + else 0 + ) if applied_budget <= 0: return { **result, - 'reason': 'No CUDA budget remained after reserve calculation.', - 'cuda_index': cuda_index, - 'free_bytes': free_bytes, - 'total_bytes': total_bytes, - 'reserve_bytes': reserve_bytes, + "reason": "No CUDA budget remained after reserve calculation.", + "cuda_index": cuda_index, + "free_bytes": free_bytes, + "total_bytes": total_bytes, + "reserve_bytes": reserve_bytes, } fraction = max(0.05, min(1.0, applied_budget / total_bytes)) @@ -3224,31 +6028,31 @@ def _apply_cuda_runtime_budget(self, runtime_hints): except Exception as e: return { **result, - 'reason': f'Could not apply CUDA memory fraction: {e}', - 'cuda_index': cuda_index, - 'free_bytes': free_bytes, - 'total_bytes': total_bytes, - 'reserve_bytes': reserve_bytes, - 'applied_budget_bytes': applied_budget, - 'fraction': fraction, + "reason": f"Could not apply CUDA memory fraction: {e}", + "cuda_index": cuda_index, + "free_bytes": free_bytes, + "total_bytes": total_bytes, + "reserve_bytes": reserve_bytes, + "applied_budget_bytes": applied_budget, + "fraction": fraction, } return { - 'applied': True, - 'reason': 'Applied CUDA memory fraction from runtime hints.', - 'cuda_index': cuda_index, - 'free_bytes': free_bytes, - 'total_bytes': total_bytes, - 'reserve_bytes': reserve_bytes, - 'applied_budget_bytes': applied_budget, - 'fraction': fraction, - 'model_repo': runtime_hints.get('modelRepo') if runtime_hints else None, - 'model_name': runtime_hints.get('modelName') if runtime_hints else None, - 'dtype': runtime_hints.get('dtype') if runtime_hints else None, - 'quantization_mode': runtime_hints.get('quantizationMode') if runtime_hints else None, - 'auto_offload': runtime_hints.get('autoOffload') if runtime_hints else None, - 'offload_mode': runtime_hints.get('offloadMode') if runtime_hints else None, - 'low_vram_mode': runtime_hints.get('lowVramMode') if runtime_hints else None, + "applied": True, + "reason": "Applied CUDA memory fraction from runtime hints.", + "cuda_index": cuda_index, + "free_bytes": free_bytes, + "total_bytes": total_bytes, + "reserve_bytes": reserve_bytes, + "applied_budget_bytes": applied_budget, + "fraction": fraction, + "model_repo": runtime_hints.get("modelRepo") if runtime_hints else None, + "model_name": runtime_hints.get("modelName") if runtime_hints else None, + "dtype": runtime_hints.get("dtype") if runtime_hints else None, + "quantization_mode": runtime_hints.get("quantizationMode") if runtime_hints else None, + "auto_offload": runtime_hints.get("autoOffload") if runtime_hints else None, + "offload_mode": runtime_hints.get("offloadMode") if runtime_hints else None, + "low_vram_mode": runtime_hints.get("lowVramMode") if runtime_hints else None, } def _resource_retry_modes(self, runtime_hints): @@ -3260,73 +6064,108 @@ def _resource_retry_modes(self, runtime_hints): OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK, ] - requested = runtime_hints.get('resourceRetryModes') + requested = runtime_hints.get("resourceRetryModes") modes = requested if isinstance(requested, list) else allowed modes = [mode for mode in modes if mode in allowed] - current_mode = runtime_hints.get('offloadMode') + current_mode = runtime_hints.get("offloadMode") if current_mode in modes: - return modes[modes.index(current_mode) + 1:] + return modes[modes.index(current_mode) + 1 :] return modes def _coerce_retry_plan_list(self, runtime_hints): if not runtime_hints: return [] - raw_plans = runtime_hints.get('resourceRetryPlans') + raw_plans = runtime_hints.get("resourceRetryPlans") if isinstance(raw_plans, list): plans = [] for index, raw_plan in enumerate(raw_plans): if not isinstance(raw_plan, dict): continue plan = deepcopy(raw_plan) - plan.setdefault('index', index) - plan.setdefault('reason', f'retry_plan_{index + 1}') + plan.setdefault("index", index) + plan.setdefault("reason", f"retry_plan_{index + 1}") plans.append(plan) return plans return [ { - 'index': index, - 'reason': f'{mode}_after_oom', - 'offloadMode': mode, - 'onCategories': ['oom'], + "index": index, + "reason": f"{mode}_after_oom", + "offloadMode": mode, + "onCategories": ["oom"], } for index, mode in enumerate(self._resource_retry_modes(runtime_hints)) ] def _set_param_value_if_present(self, node, key, value): - params = node.get('params') if isinstance(node, dict) else None + params = node.get("params") if isinstance(node, dict) else None if not isinstance(params, dict) or key not in params or not isinstance(params[key], dict): return False - params[key]['value'] = value + current = params[key].get("value", params[key].get("default")) + try: + if bool(current == value): + return False + except (TypeError, ValueError): + # Retry-controlled parameters are expected to be JSON-like. An + # exotic value must remain replaceable without pulling tensor + # comparison into the server planner. + pass + params[key]["value"] = deepcopy(value) return True def _set_model_repo_if_present(self, node, key, repo): - params = node.get('params') if isinstance(node, dict) else None + params = node.get("params") if isinstance(node, dict) else None if not repo or not isinstance(params, dict) or key not in params or not isinstance(params[key], dict): return False - current = params[key].get('value') + current = params[key].get("value") + current_repo = current.get("value") if isinstance(current, dict) else current + if current_repo == repo: + return False if isinstance(current, dict): - params[key]['value'] = {**current, 'value': repo} + params[key]["value"] = {**current, "value": repo} else: - params[key]['value'] = {'source': 'hub', 'value': repo} + params[key]["value"] = {"source": "hub", "value": repo} return True + def _resource_plan_loader_module(self, plan): + """Resolve the direct-loader family owned by a structured Auto plan. + + A Studio graph may intentionally contain more than one independent + Diffusers pipeline (for example ACE-Step audio plus LTX video). Model + and pipeline-class overrides belong only to the plan's family; applying + them to every loader corrupts the other branch before execution. + """ + pipeline_class = str(plan.get("pipelineClass") or "").strip().lower() if isinstance(plan, dict) else "" + if not pipeline_class: + return None + if "acestep" in pipeline_class or "audio" in pipeline_class: + return "modules.DiffusersAudio" + if any(token in pipeline_class for token in ("wan", "ltx", "video", "framepack", "hunyuan", "mochi")): + return "modules.DiffusersVideo" + return "modules.DiffusersImage" + + def _resource_plan_targets_node_family(self, node, plan): + target_module = self._resource_plan_loader_module(plan) + if target_module is None: + return True + return isinstance(node, dict) and node.get("module") == target_module + def _retry_plan_matches(self, plan, classification): if not isinstance(plan, dict) or not isinstance(classification, dict): return False - error_code = classification.get('error_code') - category = classification.get('category') - on_error_codes = plan.get('onErrorCodes') - on_categories = plan.get('onCategories') + error_code = classification.get("error_code") + category = classification.get("category") + on_error_codes = plan.get("onErrorCodes") + on_categories = plan.get("onCategories") if isinstance(on_error_codes, list) and error_code in on_error_codes: return True if isinstance(on_categories, list) and category in on_categories: return True if on_error_codes is None and on_categories is None: - return category == 'oom' + return category == "oom" return False def _next_retry_plan_index(self, plans, current_index, classification): @@ -3335,29 +6174,42 @@ def _next_retry_plan_index(self, plans, current_index, classification): return index return None + def _next_applicable_retry_plan_index(self, graph, plans, current_index, classification): + """Return a retry that can change at least one unpinned runtime field.""" + skipped = [] + index = self._next_retry_plan_index(plans, current_index, classification) + while index is not None: + graph_copy = deepcopy(graph) + if self._apply_resource_retry_plan_to_graph(graph_copy, plans[index]): + return index, skipped + skipped.append(index) + index = self._next_retry_plan_index(plans, index, classification) + return None, skipped + def _sanitize_retry_plan_for_hints(self, plan): if not isinstance(plan, dict): return None allowed = { - 'index', - 'reason', - 'executionPath', - 'modelRepo', - 'resolvedArtifact', - 'quantizationMode', - 'quantizedComponents', - 'bnb4ComputeDtype', - 'dtype', - 'pipelineClass', - 'offloadMode', - 'generation', - 'onCategories', - 'onErrorCodes', + "index", + "reason", + "executionPath", + "modelRepo", + "resolvedArtifact", + "quantizationMode", + "quantizedComponents", + "bnb4ComputeDtype", + "dtype", + "pipelineClass", + "offloadMode", + "deviceMap", + "generation", + "onCategories", + "onErrorCodes", } return {key: deepcopy(plan.get(key)) for key in allowed if key in plan} def _apply_resource_retry_to_graph(self, graph, offload_mode): - nodes = graph.get('nodes', {}) + nodes = graph.get("nodes", {}) if not isinstance(nodes, dict): return [] @@ -3365,108 +6217,132 @@ def _apply_resource_retry_to_graph(self, graph, offload_mode): for node_id, node in nodes.items(): if not isinstance(node, dict): continue - action = node.get('action') - module = node.get('module') + action = node.get("action") + module = node.get("module") compatible_loader = ( - (module == 'modules.ModularDiffusers' and action in ('ModelsLoader', 'DynamicPipelineLoader')) - or (module == 'modules.QwenImage' and action in ('LoadInpaintPipeline', 'LoadPipeline')) - or (module == 'modules.WanVACE' and action == 'LoadPipeline') - or (module in ('modules.DiffusersImage', 'modules.DiffusersAudio') and action == 'LoadPipeline') + module == "modules.ModularDiffusers" and action in ("ModelsLoader", "DynamicPipelineLoader") + ) or ( + module in ("modules.DiffusersImage", "modules.DiffusersAudio", "modules.DiffusersVideo") + and action == "LoadPipeline" ) if not compatible_loader: continue - changed = self._set_param_value_if_present(node, 'offload_mode', offload_mode) - changed = self._set_param_value_if_present(node, 'auto_offload', offload_mode != OFFLOAD_MODE_NONE) or changed + changed = self._set_param_value_if_present(node, "offload_mode", offload_mode) + changed = ( + self._set_param_value_if_present(node, "auto_offload", offload_mode != OFFLOAD_MODE_NONE) or changed + ) if changed: updated.append(str(node_id)) return updated def _apply_resource_retry_plan_to_graph(self, graph, plan): - nodes = graph.get('nodes', {}) + nodes = graph.get("nodes", {}) if not isinstance(nodes, dict) or not isinstance(plan, dict): return [] - offload_mode = plan.get('offloadMode') - model_repo = plan.get('modelRepo') or plan.get('resolvedArtifact') - quantization_mode = plan.get('quantizationMode') - quantized_components = plan.get('quantizedComponents') - compute_dtype = plan.get('bnb4ComputeDtype') - dtype = plan.get('dtype') - generation = plan.get('generation') if isinstance(plan.get('generation'), dict) else {} + runtime_hints = graph.get("runtimeHints") + raw_overrides = runtime_hints.get("autoFieldOverrides") if isinstance(runtime_hints, dict) else None + pinned_fields = { + (str(item.get("nodeId")), str(item.get("fieldKey"))) + for item in (raw_overrides if isinstance(raw_overrides, list) else []) + if isinstance(item, dict) and isinstance(item.get("nodeId"), str) and isinstance(item.get("fieldKey"), str) + } - updated = [] - for node_id, node in nodes.items(): + def set_param(node_id, node, param_key, value): + if (str(node_id), param_key) in pinned_fields: + return False + return self._set_param_value_if_present(node, param_key, value) + + def set_model_repo(node_id, node, param_key, value): + if (str(node_id), param_key) in pinned_fields: + return False + return self._set_model_repo_if_present(node, param_key, value) + + offload_mode = plan.get("offloadMode") + device_map = plan.get("deviceMap") + model_repo = plan.get("modelRepo") or plan.get("resolvedArtifact") + quantization_mode = plan.get("quantizationMode") + quantized_components = plan.get("quantizedComponents") + compute_dtype = plan.get("bnb4ComputeDtype") + dtype = plan.get("dtype") + target_recipe_ids = set() + for node in nodes.values(): if not isinstance(node, dict): continue - action = node.get('action') - module = node.get('module') + module = node.get("module") + action = node.get("action") compatible_loader = ( - (module == 'modules.ModularDiffusers' and action in ('ModelsLoader', 'DynamicPipelineLoader')) - or (module == 'modules.QwenImage' and action in ('LoadInpaintPipeline', 'LoadPipeline')) - or (module == 'modules.WanVACE' and action == 'LoadPipeline') - or (module in ('modules.DiffusersImage', 'modules.DiffusersAudio') and action == 'LoadPipeline') + module == "modules.ModularDiffusers" and action in ("ModelsLoader", "DynamicPipelineLoader") + ) or ( + module in ("modules.DiffusersImage", "modules.DiffusersAudio", "modules.DiffusersVideo") + and action == "LoadPipeline" ) - if not compatible_loader: + if not compatible_loader or not self._resource_plan_targets_node_family(node, plan): continue + recipe_param = (node.get("params") or {}).get("execution_recipe") + recipe_source_id = recipe_param.get("sourceId") if isinstance(recipe_param, dict) else None + if isinstance(recipe_source_id, str) and recipe_source_id: + target_recipe_ids.add(recipe_source_id) - changed = False - if isinstance(offload_mode, str): - changed = self._set_param_value_if_present(node, 'offload_mode', offload_mode) or changed - changed = self._set_param_value_if_present(node, 'auto_offload', offload_mode != OFFLOAD_MODE_NONE) or changed - if isinstance(model_repo, str) and model_repo: - changed = self._set_model_repo_if_present(node, 'model_id', model_repo) or changed - changed = self._set_model_repo_if_present(node, 'repo_id', model_repo) or changed - if isinstance(plan.get('pipelineClass'), str): - changed = self._set_param_value_if_present(node, 'pipeline_class', plan.get('pipelineClass')) or changed - if isinstance(dtype, str): - changed = self._set_param_value_if_present(node, 'dtype', dtype) or changed - if module == 'modules.QwenImage' and action == 'LoadPipeline': - if isinstance(quantization_mode, str): - changed = self._set_param_value_if_present(node, 'quantization_mode', quantization_mode) or changed - if isinstance(quantized_components, list): - changed = self._set_param_value_if_present(node, 'quantized_components', [str(item) for item in quantized_components]) or changed - if isinstance(compute_dtype, str): - changed = self._set_param_value_if_present(node, 'bnb_4bit_compute_dtype', compute_dtype) or changed - if model_repo == QWEN_IMAGE_2512_PREQUANTIZED_REPO: - changed = self._set_param_value_if_present(node, 'quantization_mode', 'none') or changed - changed = self._set_param_value_if_present(node, 'quantized_components', []) or changed - if changed: - updated.append(str(node_id)) - - if module == 'modules.QwenImage' and action in ('Generate', 'InpaintGenerate'): - generation_changed = False - for plan_key, param_keys in ( - ('width', ('width',)), - ('height', ('height',)), - ('steps', ('num_inference_steps', 'steps')), - ('guidanceScale', ('true_cfg_scale', 'guidance_scale', 'guidance')), - ('negativePrompt', ('negative_prompt',)), - ('maxSequenceLength', ('max_sequence_length',)), - ): - if plan_key not in generation: - continue - for param_key in param_keys: - generation_changed = self._set_param_value_if_present(node, param_key, generation[plan_key]) or generation_changed - if generation_changed: + updated = [] + for node_id, node in nodes.items(): + if not isinstance(node, dict): + continue + action = node.get("action") + module = node.get("module") + compatible_loader = ( + module == "modules.ModularDiffusers" and action in ("ModelsLoader", "DynamicPipelineLoader") + ) or ( + module in ("modules.DiffusersImage", "modules.DiffusersAudio", "modules.DiffusersVideo") + and action == "LoadPipeline" + ) + if ( + str(node_id) in target_recipe_ids + and module == "modules.DiffusersRuntime" + and action == "DiffusersExecutionRecipe" + ): + recipe_changed = False + if isinstance(offload_mode, str): + recipe_changed = set_param(node_id, node, "offload_mode", offload_mode) or recipe_changed + if isinstance(device_map, str): + recipe_changed = set_param(node_id, node, "device_map", device_map) or recipe_changed + if recipe_changed: updated.append(str(node_id)) - if module in ('modules.DiffusersImage', 'modules.DiffusersAudio') and action in ('Generate', 'Edit', 'Inpaint', 'ControlGenerate'): - generation_changed = False - for plan_key, param_keys in ( - ('width', ('width',)), - ('height', ('height',)), - ('steps', ('num_inference_steps', 'steps')), - ('guidanceScale', ('guidance_scale', 'true_cfg_scale', 'guidance')), - ('negativePrompt', ('negative_prompt',)), - ('maxSequenceLength', ('max_sequence_length',)), - ('audioDuration', ('audio_duration',)), - ('shift', ('shift',)), - ): - if plan_key not in generation: - continue - for param_key in param_keys: - generation_changed = self._set_param_value_if_present(node, param_key, generation[plan_key]) or generation_changed - if generation_changed: + + if compatible_loader and self._resource_plan_targets_node_family(node, plan): + changed = False + if isinstance(offload_mode, str): + changed = set_param(node_id, node, "offload_mode", offload_mode) or changed + changed = set_param(node_id, node, "auto_offload", offload_mode != OFFLOAD_MODE_NONE) or changed + if isinstance(device_map, str): + changed = set_param(node_id, node, "device_map", device_map) or changed + if isinstance(model_repo, str) and model_repo: + changed = set_model_repo(node_id, node, "model_id", model_repo) or changed + changed = set_model_repo(node_id, node, "repo_id", model_repo) or changed + if isinstance(plan.get("pipelineClass"), str): + changed = set_param(node_id, node, "pipeline_class", plan.get("pipelineClass")) or changed + if isinstance(dtype, str): + changed = set_param(node_id, node, "dtype", dtype) or changed + if module == "modules.DiffusersImage" and action == "LoadPipeline": + if isinstance(quantization_mode, str): + changed = set_param(node_id, node, "quantization_mode", quantization_mode) or changed + if isinstance(quantized_components, list): + changed = ( + set_param( + node_id, + node, + "quantized_components", + [str(item) for item in quantized_components], + ) + or changed + ) + if isinstance(compute_dtype, str): + changed = set_param(node_id, node, "bnb_4bit_compute_dtype", compute_dtype) or changed + if model_repo == QWEN_IMAGE_2512_PREQUANTIZED_REPO: + changed = set_param(node_id, node, "quantization_mode", "none") or changed + changed = set_param(node_id, node, "quantized_components", []) or changed + if changed: updated.append(str(node_id)) return updated @@ -3474,59 +6350,142 @@ def _apply_resource_retry_plan_to_graph(self, graph, plan): def _auto_resource_candidate_is_proven(self, candidate): if not isinstance(candidate, dict): return False - proof = candidate.get('proof') - status = proof.get('status') if isinstance(proof, dict) else None + proof = candidate.get("proof") + status = proof.get("status") if isinstance(proof, dict) else None return status in PROVEN_PROOF_STATUSES def _auto_resource_requires_proven_candidate(self, runtime_hints): - if not isinstance(runtime_hints, dict) or runtime_hints.get('resourceMode') != 'auto': - return False - return True + # Proof is qualification evidence, not a deterministic execution + # prerequisite. Missing artifacts, inputs, devices, and incompatible + # types are rejected by graph/runtime validation; an otherwise + # complete but not-yet-qualified Auto recipe remains runnable with a + # warning. + return False def _assert_auto_resource_candidate_ready(self, runtime_hints): if not self._auto_resource_requires_proven_candidate(runtime_hints): return - auto_plan = runtime_hints.get('autoResourcePlan') if isinstance(runtime_hints, dict) else None + auto_plan = runtime_hints.get("autoResourcePlan") if isinstance(runtime_hints, dict) else None if self._auto_resource_candidate_is_proven(auto_plan): return - candidates = runtime_hints.get('autoResourceCandidates') if isinstance(runtime_hints, dict) else None - proven_candidates = [ - candidate for candidate in candidates - if self._auto_resource_candidate_is_proven(candidate) - ] if isinstance(candidates, list) else [] + candidates = runtime_hints.get("autoResourceCandidates") if isinstance(runtime_hints, dict) else None + proven_candidates = ( + [candidate for candidate in candidates if self._auto_resource_candidate_is_proven(candidate)] + if isinstance(candidates, list) + else [] + ) if proven_candidates: - runtime_hints['autoResourcePlan'] = proven_candidates[0] - runtime_hints['autoResourceCandidateId'] = proven_candidates[0].get('id') - proof = proven_candidates[0].get('proof') if isinstance(proven_candidates[0].get('proof'), dict) else {} - runtime_hints['autoResourceProofStatus'] = proof.get('status') + runtime_hints["autoResourcePlan"] = proven_candidates[0] + runtime_hints["autoResourceCandidateId"] = proven_candidates[0].get("id") + proof = proven_candidates[0].get("proof") if isinstance(proven_candidates[0].get("proof"), dict) else {} + runtime_hints["autoResourceProofStatus"] = proof.get("status") return - status = runtime_hints.get('compatibilityStatus') or runtime_hints.get('autoResourceProofStatus') or 'unproven' - model_name = runtime_hints.get('modelName') or runtime_hints.get('modelType') or 'this workflow' + status = runtime_hints.get("compatibilityStatus") or runtime_hints.get("autoResourceProofStatus") or "unproven" + model_name = runtime_hints.get("modelName") or runtime_hints.get("modelType") or "this workflow" error = RuntimeError( f"Auto resource plan is not ready for {model_name}. Refresh the Auto plan or choose Expert settings before executing this workflow." ) - setattr(error, 'modiff_error_code', 'auto_resource_unproven') - setattr(error, 'modiff_category', 'auto_resource') - setattr(error, 'modiff_recovery_hint', ( - "Auto has not found a runnable artifact in the local model/cache and hardware metadata. " - "Install the suggested compatible artifact or switch to Expert if you want to choose the configuration yourself." - )) - setattr(error, 'modiff_auto_resource_status', status) + setattr(error, "modiff_error_code", "auto_resource_unproven") + setattr(error, "modiff_category", "auto_resource") + setattr( + error, + "modiff_recovery_hint", + ( + "Auto has not found a runnable artifact in the local model/cache and hardware metadata. " + "Install the suggested compatible artifact or switch to Expert if you want to choose the configuration yourself." + ), + ) + setattr(error, "modiff_auto_resource_status", status) raise error - def _record_auto_resource_success(self, runtime_hints, runtime_fingerprint): + def _record_auto_resource_success(self, runtime_hints, runtime_fingerprint, measurement=None): try: record_auto_resource_success( self.data_dir, - runtime_fingerprint=runtime_fingerprint if isinstance(runtime_fingerprint, dict) else self._runtime_fingerprint(), + runtime_fingerprint=runtime_fingerprint + if isinstance(runtime_fingerprint, dict) + else self._runtime_fingerprint(), runtime_hints=runtime_hints, + measurement=measurement, ) except Exception as exc: logger.debug(f"Could not record Auto resource success: {exc}") + def _record_optimization_observations( + self, + runtime_hints, + runtime_fingerprint, + measurement=None, + graph=None, + ): + if not isinstance(runtime_hints, dict) or not isinstance(measurement, dict): + return [] + if not isinstance(graph, dict): + task_id = str((getattr(self, "current_task", None) or {}).get("task_id") or "") + task_graphs = getattr(self, "task_graphs", {}) + graph = task_graphs.get(task_id) if isinstance(task_graphs, dict) else None + if not isinstance(graph, dict): + return [] + selections = optimization_selections_from_graph(graph) + form = runtime_hints.get("optimizationQualificationForm") + workload_key = optimization_workload_key_for_form(form if isinstance(form, dict) else {}) + runtime_identity = ( + runtime_fingerprint.get("resourceFingerprint") + if isinstance(runtime_fingerprint, dict) + else runtime_fingerprint + ) + model_type = str(runtime_hints.get("modelType") or "") + mode = str((form or {}).get("mode") or "") if isinstance(form, dict) else "" + artifact = str( + runtime_hints.get("resolvedArtifact") + or runtime_hints.get("resolvedModelRepo") + or runtime_hints.get("modelRepo") + or "" + ) + receipts = [] + if not selections: + try: + return [ + record_optimization_workload_baseline( + runtime_fingerprint=runtime_identity, + model_type=model_type, + mode=mode, + artifact=artifact, + workload_key=workload_key, + measurement=measurement, + ) + ] + except Exception as exc: + logger.debug("Could not record optimization workload baseline: %s", exc) + return [] + for selection in selections: + capability_id = str(selection.get("capabilityId") or "") + try: + receipts.append( + record_optimization_workload_observation( + capability_id=capability_id, + runtime_fingerprint=runtime_identity, + model_type=model_type, + mode=mode, + artifact=artifact, + workload_key=workload_key, + selection={key: value for key, value in selection.items() if key != "capabilityId"}, + measurement=measurement, + ) + ) + except Exception as exc: + logger.debug("Could not record optimization workload observation: %s", exc) + return receipts + def _record_auto_resource_failure(self, error, classification=None): + # Auto history is a machine/artifact admission signal. User-authored + # prompt, dimension, graph, or media-input failures must never poison a + # model candidate and prevent the corrected graph from running. + resource_categories = {"oom", "cuda_context", "cuda_kernel", "missing_dependency", "missing_model"} + if not isinstance(classification, dict) or classification.get("category") not in resource_categories: + return try: runtime_hints = self.current_task.get("runtimeHints") if self.current_task else None record_auto_resource_failure( @@ -3539,151 +6498,871 @@ def _record_auto_resource_failure(self, error, classification=None): except Exception as exc: logger.debug(f"Could not record Auto resource failure: {exc}") + def _reset_runtime_measurement(self): + """Reset accelerator peak counters immediately before one graph attempt.""" + try: + torch = import_module("torch") + if bool(torch.cuda.is_available()): + for index in range(int(torch.cuda.device_count())): + try: + torch.cuda.reset_peak_memory_stats(index) + except TypeError: + with torch.cuda.device(index): + torch.cuda.reset_peak_memory_stats() + elif bool(getattr(getattr(torch, "xpu", None), "is_available", lambda: False)()): + xpu = torch.xpu + for index in range(int(xpu.device_count())): + reset = getattr(xpu, "reset_peak_memory_stats", None) + if callable(reset): + reset(index) + except Exception as exc: + logger.debug(f"Could not reset runtime memory counters: {exc}") + + def _runtime_measurement(self, *, elapsed_seconds): + measurement = {"elapsedSeconds": max(0.0, float(elapsed_seconds))} + try: + torch = import_module("torch") + if bool(torch.cuda.is_available()): + index = 0 + hip_version = getattr(getattr(torch, "version", None), "hip", None) + measurement.update( + { + "backend": "rocm" if hip_version else "cuda", + "device": f"cuda:{index}", + "allocatedBytes": int(torch.cuda.memory_allocated(index)), + "reservedBytes": int(torch.cuda.memory_reserved(index)), + "peakAllocatedBytes": int(torch.cuda.max_memory_allocated(index)), + "peakReservedBytes": int(torch.cuda.max_memory_reserved(index)), + } + ) + elif bool(getattr(getattr(torch, "xpu", None), "is_available", lambda: False)()): + index = 0 + xpu = torch.xpu + measurement.update({"backend": "xpu", "device": f"xpu:{index}"}) + for key, method_name in ( + ("allocatedBytes", "memory_allocated"), + ("reservedBytes", "memory_reserved"), + ("peakAllocatedBytes", "max_memory_allocated"), + ("peakReservedBytes", "max_memory_reserved"), + ): + method = getattr(xpu, method_name, None) + if callable(method): + measurement[key] = int(method(index)) + elif bool(getattr(getattr(torch, "backends", None), "mps", None)) and torch.backends.mps.is_available(): + mps = getattr(torch, "mps", None) + measurement.update({"backend": "mps", "device": "mps:0"}) + current = getattr(mps, "current_allocated_memory", None) + driver = getattr(mps, "driver_allocated_memory", None) + if callable(current): + measurement["allocatedBytes"] = int(current()) + if callable(driver): + measurement["driverAllocatedBytes"] = int(driver()) + else: + measurement.update({"backend": "cpu", "device": "cpu:0"}) + except Exception as exc: + measurement["acceleratorMeasurementError"] = str(exc) + try: + import psutil + + measurement["processRssBytes"] = int(psutil.Process().memory_info().rss) + except Exception: + pass + return measurement + def _release_runtime_caches_for_retry(self): errors = [] released = { - 'nodes': len(self.node_cache), - 'models': 0, - 'diffusers_components': 0, - 'offload_files': 0, + "nodes": len(self.node_cache), + "models": 0, + "diffusers_components": 0, + "offload_files": 0, } try: - self.node_cache.clear() - except Exception as e: - errors.append(f'node cache: {e}') - - try: - released['models'] = memory_manager.clear() + released["models"] = memory_manager.clear() except Exception as e: - errors.append(f'memory manager: {e}') + errors.append(f"memory manager: {e}") try: memory_manager.cache.clear() except Exception as clear_error: - errors.append(f'memory manager fallback: {clear_error}') + errors.append(f"memory manager fallback: {clear_error}") - released['diffusers_components'], diffusers_errors = self._release_modular_diffusers_components() + # Detach MemoryManager ownership first. Node destructors otherwise call + # remove(), which may try to materialize an offloaded pipeline on CPU + # while its accelerator allocation is still live. + try: + self.node_cache.clear() + except Exception as e: + errors.append(f"node cache: {e}") + + released["diffusers_components"], diffusers_errors = self._release_modular_diffusers_components() errors.extend(diffusers_errors) - released['offload_files'], offload_errors = self._release_diffusers_offload_cache() + released["offload_files"], offload_errors = self._release_diffusers_offload_cache() errors.extend(offload_errors) try: gc.collect() except Exception as e: - errors.append(f'gc.collect: {e}') + errors.append(f"gc.collect: {e}") errors.extend(self._best_effort_device_cache_clear()) + allocator_trimmed, allocator_errors = self._best_effort_allocator_trim() + errors.extend(allocator_errors) return { - 'released': released, - 'errors': errors, + "released": released, + "allocatorTrimmed": allocator_trimmed, + "errors": errors, } - def execute_graph(self, graph): - sid = graph['sid'] - nodes = graph['nodes'] - paths = graph['paths'] + @staticmethod + def _auto_candidate_minimums(runtime_hints): + if not isinstance(runtime_hints, dict): + return {} + candidate = runtime_hints.get("autoResourcePlan") + if not isinstance(candidate, dict): + return {} + requirements = candidate.get("requirements") + if not isinstance(requirements, dict): + return {} + minimum = requirements.get("minimum") + return minimum if isinstance(minimum, dict) else {} - graph_execution_time = time.time() - base_runtime_hints = self._coerce_runtime_hints(graph.get('runtimeHints')) - retry_plans = self._coerce_retry_plan_list(base_runtime_hints) - retry_history = [] - attempt_index = 0 - retry_plan_index = -1 + @staticmethod + def _auto_candidate_cache_signature(runtime_hints): + if not isinstance(runtime_hints, dict): + return None + candidate = runtime_hints.get("autoResourcePlan") + if not isinstance(candidate, dict): + return None + payload = { + "modelType": candidate.get("modelType") or runtime_hints.get("modelType"), + "artifact": ( + candidate.get("resolvedArtifact") + or candidate.get("artifact") + or candidate.get("modelRepo") + or runtime_hints.get("resolvedArtifact") + or runtime_hints.get("modelRepo") + ), + "executionPath": candidate.get("executionPath") or runtime_hints.get("executionPath"), + "pipelineClass": candidate.get("pipelineClass") or runtime_hints.get("pipelineClass"), + # The same model/artifact can be represented by an assembled + # Diffusers pipeline node or by Diffusers component-loader nodes. + # Those resident objects are not interchangeable. + "loaderContract": runtime_hints.get("loaderContract"), + "dtype": candidate.get("dtype") or runtime_hints.get("dtype"), + "quantizationMode": (candidate.get("quantizationMode") or runtime_hints.get("quantizationMode")), + "quantizedComponents": sorted( + str(item) + for item in (candidate.get("quantizedComponents") or runtime_hints.get("quantizedComponents") or []) + ), + "offloadMode": candidate.get("offloadMode") or runtime_hints.get("offloadMode"), + "deviceMap": candidate.get("deviceMap") or runtime_hints.get("deviceMap"), + "attentionBackend": candidate.get("attentionBackend") or runtime_hints.get("attentionBackend"), + "regionalCompile": candidate.get("regionalCompile") or runtime_hints.get("regionalCompile"), + "denoiserCache": candidate.get("denoiserCache") or runtime_hints.get("denoiserCache"), + "channelsLast": candidate.get("channelsLast") or runtime_hints.get("channelsLast"), + "layerwiseCasting": candidate.get("layerwiseCasting") or runtime_hints.get("layerwiseCasting"), + } + return hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")).hexdigest() - while True: - if self.current_task: - self.current_task["attempt_index"] = attempt_index - self.current_task["progress"] = 0 - runtime_hints = deepcopy(base_runtime_hints) if base_runtime_hints else None - active_retry_plan = retry_plans[retry_plan_index] if retry_plan_index >= 0 and retry_plan_index < len(retry_plans) else None - if self.current_task and runtime_hints is not None: - self.current_task['runtimeHints'] = runtime_hints - if runtime_hints and active_retry_plan is None and attempt_index == 0: - self._assert_auto_resource_candidate_ready(runtime_hints) - auto_plan = runtime_hints.get('autoResourcePlan') - if isinstance(auto_plan, dict) and self._auto_resource_candidate_is_proven(auto_plan): - updated_nodes = self._apply_resource_retry_plan_to_graph(graph, auto_plan) - if updated_nodes: - self.queue_message({ - "type": "auto_resource_plan_applied", - "sid": sid, - "task_id": self.current_task.get("task_id") if self.current_task else None, - "attempt": attempt_index, - "attempt_index": attempt_index, - "candidateId": auto_plan.get('id'), - "updatedNodes": updated_nodes, - "message": "Applied the proven Auto resource plan before execution.", - }, sid) - if runtime_hints and active_retry_plan: - updated_nodes = self._apply_resource_retry_plan_to_graph(graph, active_retry_plan) - retry_mode = active_retry_plan.get('offloadMode') - if isinstance(retry_mode, str): - runtime_hints['offloadMode'] = retry_mode - runtime_hints['autoOffload'] = retry_mode != OFFLOAD_MODE_NONE - runtime_hints['offloadDiskPath'] = 'data/offload/diffusers' if retry_mode == OFFLOAD_MODE_GROUP_DISK else None - if isinstance(active_retry_plan.get('modelRepo'), str): - runtime_hints['modelRepo'] = active_retry_plan['modelRepo'] - runtime_hints['resolvedModelRepo'] = active_retry_plan['modelRepo'] - if isinstance(active_retry_plan.get('resolvedArtifact'), str): - runtime_hints['resolvedArtifact'] = active_retry_plan['resolvedArtifact'] - elif isinstance(active_retry_plan.get('modelRepo'), str): - runtime_hints['resolvedArtifact'] = active_retry_plan['modelRepo'] - if isinstance(active_retry_plan.get('executionPath'), str): - runtime_hints['executionPath'] = active_retry_plan['executionPath'] - if isinstance(active_retry_plan.get('quantizationMode'), str): - runtime_hints['quantizationMode'] = active_retry_plan['quantizationMode'] - if isinstance(active_retry_plan.get('quantizedComponents'), list): - runtime_hints['quantizedComponents'] = [str(item) for item in active_retry_plan['quantizedComponents']] - runtime_hints['resourceRetryAttempt'] = attempt_index - runtime_hints['resourceRetryHistory'] = retry_history - plan = runtime_hints.get('resourcePlan') - if isinstance(plan, dict): - plan['activeRetryPlan'] = self._sanitize_retry_plan_for_hints(active_retry_plan) - if isinstance(retry_mode, str): - plan['offloadMode'] = retry_mode - plan['autoOffload'] = retry_mode != OFFLOAD_MODE_NONE - self.queue_message({ - "type": "resource_retry", - "sid": sid, - "task_id": self.current_task.get("task_id") if self.current_task else None, - "attempt": attempt_index, - "attempt_index": attempt_index, - "offloadMode": retry_mode, - "retryPlan": self._sanitize_retry_plan_for_hints(active_retry_plan), - "updatedNodes": updated_nodes, - "message": f"Retrying with {str(retry_mode or active_retry_plan.get('reason') or 'safer plan').replace('_', '-')} after {retry_history[-1]['errorCode'] if retry_history else 'resource pressure'}.", - "history": retry_history, - }, sid) + @staticmethod + def _graph_loader_contract(nodes): + if not isinstance(nodes, dict): + return [] + loader_actions = {"LoadPipeline", "ModelsLoader", "AutoModelLoader"} + contract = { + f"{node.get('module', '')}.{node.get('action', '')}" + for node in nodes.values() + if isinstance(node, dict) and node.get("action") in loader_actions + } + return sorted(item for item in contract if item != ".") - runtime_budget = self._apply_cuda_runtime_budget(runtime_hints) - deterministic = self._apply_deterministic_mode(graph) if attempt_index == 0 else None - runtime_fingerprint = self._runtime_fingerprint() - if self.current_task: - self.current_task['runtimeFingerprint'] = runtime_fingerprint.get('fingerprint') - if deterministic is not None: - self.current_task['deterministicMode'] = deterministic - self.current_task['runtimeHints'] = runtime_hints - self.current_task['runtimeBudget'] = runtime_budget + def _prepare_auto_runtime_for_graph(self, runtime_hints): + """Release stale app-owned caches before an Auto run when warranted. - if deterministic: - self.queue_message({ - "type": "deterministic_execution", - "sid": sid, - "task_id": self.current_task.get("task_id") if self.current_task else None, - "deterministicMode": deterministic, - "runtimeFingerprint": runtime_fingerprint, - }, sid) + Same-family cache is intentionally retained while memory has headroom; + it is useful, not stale. A model-family switch or live RAM/VRAM + pressure releases all app-owned graph/model caches before loading the + selected candidate. + """ + if not isinstance(runtime_hints, dict) or runtime_hints.get("resourceMode") != "auto": + return None - node_weights = { - id: node_execution_weight(nodes[id].get('module', ''), nodes[id].get('action', '')) - for path in paths - for id in path - if id in nodes - } - total_task_weight = sum(node_weights.values()) or 1 - task_progress = 0.0 + candidate = runtime_hints.get("autoResourcePlan") + previous_family = self._last_auto_model_family + previous_signature = self._last_auto_resource_signature + incoming_family = str( + (candidate.get("modelType") if isinstance(candidate, dict) else None) + or runtime_hints.get("modelType") + or "" + ).strip() + incoming_signature = self._auto_candidate_cache_signature(runtime_hints) + has_runtime_cache = bool(self.node_cache or memory_manager.cache) + resident_recipe_reusable = bool( + has_runtime_cache + and previous_family + and incoming_family + and previous_family == incoming_family + and previous_signature + and incoming_signature + and previous_signature == incoming_signature + ) + reasons = [] + if has_runtime_cache and incoming_family and not previous_family: + reasons.append("cached model family is unknown") + if has_runtime_cache and previous_family and incoming_family and previous_family != incoming_family: + reasons.append(f"model family changed from {previous_family} to {incoming_family}") + if ( + has_runtime_cache + and previous_family + and previous_family == incoming_family + and previous_signature + and incoming_signature + and previous_signature != incoming_signature + ): + reasons.append(f"Auto resource recipe changed within {incoming_family}") - try: + minimums = self._auto_candidate_minimums(runtime_hints) + try: + hardware = get_hardware_snapshot(self.data_dir, refresh=True) + except Exception: + logger.debug("Could not sample resources before Auto execution", exc_info=True) + hardware = {} + + system = hardware.get("system") if isinstance(hardware, dict) else {} + available_ram = system.get("ram_available") if isinstance(system, dict) else None + total_ram = system.get("ram_total") if isinstance(system, dict) else None + required_ram = minimums.get("systemRamBytes") + ram_floor = max( + 4 * 1024**3, + int(total_ram * 0.1) if isinstance(total_ram, int) else 0, + ) + if has_runtime_cache and isinstance(available_ram, int): + if available_ram < ram_floor: + reasons.append("available system memory is below the safety floor") + elif not resident_recipe_reusable and isinstance(required_ram, int) and available_ram < required_ram: + reasons.append("available system memory is below the selected candidate minimum") + + cuda_devices = [ + device + for device in (hardware.get("devices") if isinstance(hardware, dict) else []) or [] + if isinstance(device, dict) and device.get("type") == "cuda" + ] + accelerator = cuda_devices[0] if cuda_devices else None + available_vram = ( + accelerator.get("torch_vram_free") or accelerator.get("vram_free") + if isinstance(accelerator, dict) + else None + ) + total_vram = ( + accelerator.get("torch_vram_total") or accelerator.get("vram_total") + if isinstance(accelerator, dict) + else None + ) + required_vram = minimums.get("vramBytes") + vram_floor = max( + 2 * 1024**3, + int(total_vram * 0.1) if isinstance(total_vram, int) else 0, + ) + if has_runtime_cache and isinstance(available_vram, int): + if available_vram < vram_floor: + reasons.append("available accelerator memory is below the safety floor") + elif not resident_recipe_reusable and isinstance(required_vram, int) and available_vram < required_vram: + reasons.append("available accelerator memory is below the selected candidate minimum") + + cleanup = self._release_runtime_caches_for_retry() if reasons else None + if incoming_family: + self._last_auto_model_family = incoming_family + if incoming_signature: + self._last_auto_resource_signature = incoming_signature + result = { + "performed": cleanup is not None, + "reasons": reasons, + "incomingModelFamily": incoming_family or None, + "previousModelFamily": previous_family, + "residentRecipeReusable": resident_recipe_reusable, + "resourceRecipeChanged": bool( + previous_signature and incoming_signature and previous_signature != incoming_signature + ), + "availableRamBytes": available_ram, + "availableVramBytes": available_vram, + "cleanup": cleanup, + } + if cleanup is not None: + self.queue_message( + { + "type": "auto_resource_cleanup", + "task_id": self.current_task.get("task_id") if self.current_task else None, + **self._current_run_identity_payload(), + **result, + } + ) + return result + + def _prepare_graph_loops(self, graph): + raw_loops = graph.get("loops") + if raw_loops in (None, []): + return {"loops": [], "by_node": {}} + if not isinstance(raw_loops, list): + raise ValueError("Graph loops must be a list.") + + nodes = graph.get("nodes") if isinstance(graph.get("nodes"), dict) else {} + paths = graph.get("paths") if isinstance(graph.get("paths"), list) else [] + global_order = [] + for path in paths: + if not isinstance(path, list): + continue + for node_id in path: + if node_id in nodes and node_id not in global_order: + global_order.append(node_id) + + prepared = [] + for position, item in enumerate(raw_loops): + if not isinstance(item, dict): + raise ValueError(f"Loop {position + 1} must be an object.") + loop_id = str(item.get("id") or "").strip() + if not loop_id: + raise ValueError(f"Loop {position + 1} needs an id.") + body_ids = [str(value) for value in item.get("bodyNodeIds") or []] + body_ids = list(dict.fromkeys(body_ids)) + if not body_ids: + raise ValueError(f"Loop {loop_id} needs at least one body node.") + missing = [node_id for node_id in body_ids if node_id not in nodes] + if missing: + raise ValueError(f"Loop {loop_id} references missing body nodes: {', '.join(missing)}.") + max_iterations = max(1, min(10000, int(item.get("maxIterations") or 100))) + iterations = int(item.get("iterations") or 1) + if iterations < 1 or iterations > max_iterations: + raise ValueError(f"Loop {loop_id} iterations must be between 1 and its maximum of {max_iterations}.") + ordered_body = [node_id for node_id in global_order if node_id in body_ids] + if set(ordered_body) != set(body_ids): + unresolved = sorted(set(body_ids) - set(ordered_body)) + raise ValueError(f"Loop {loop_id} body is not present in executable paths: {', '.join(unresolved)}.") + + input_id = str(item.get("inputNodeId") or "").strip() or None + index_id = str(item.get("indexNodeId") or "").strip() or None + item_id = str(item.get("itemNodeId") or "").strip() or None + result_id = str(item.get("resultNodeId") or "").strip() or None + for label, boundary_id in ( + ("input", input_id), + ("index", index_id), + ("items", item_id), + ("result", result_id), + ): + if boundary_id and boundary_id not in body_ids: + raise ValueError(f"Loop {loop_id} {label} node must be inside its visual container.") + if not result_id: + raise ValueError(f"Loop {loop_id} needs one Loop Result node.") + iteration_mode = str(item.get("iterationMode") or "count") + if iteration_mode not in {"count", "collection"}: + raise ValueError(f"Loop {loop_id} has unsupported iteration mode {iteration_mode!r}.") + if iteration_mode == "collection" and not item_id: + raise ValueError(f"Loop {loop_id} needs a Loop Items node in collection mode.") + ordered_body = [node_id for node_id in ordered_body if node_id != result_id] + [result_id] + + body_set = set(body_ids) + for target_id, target in nodes.items(): + if target_id in body_set and target_id != result_id: + for param in (target.get("params") or {}).values(): + if isinstance(param, dict) and param.get("sourceId") == result_id: + raise ValueError( + f"Loop {loop_id} result cannot feed another node inside the same iteration; " + "use Loop Input for carried state." + ) + if target_id in body_set: + continue + for param in (target.get("params") or {}).values(): + if not isinstance(param, dict): + continue + source_id = param.get("sourceId") + if source_id in body_set and source_id != result_id: + raise ValueError( + f"Loop {loop_id} can only expose values through its Loop Result node; " + f"{target_id} reads directly from {source_id}." + ) + + prepared_loop = { + "id": loop_id, + "body": ordered_body, + "iterations": iterations, + "max_iterations": max_iterations, + "input_id": input_id, + "index_id": index_id, + "item_id": item_id, + "result_id": result_id, + "iteration_mode": iteration_mode, + "carry": bool(item.get("carry", True)), + "collect": bool(item.get("collect", True)), + "max_retries": max(0, min(10, int(item.get("maxRetries") or 0))), + } + prepared.append(prepared_loop) + body_sets = {loop["id"]: set(loop["body"]) for loop in prepared} + for index, loop in enumerate(prepared): + for other in prepared[index + 1 :]: + left = body_sets[loop["id"]] + right = body_sets[other["id"]] + overlap = left & right + if overlap and not (left < right or right < left): + raise ValueError( + f"Loop {other['id']} overlaps another loop at: {', '.join(sorted(overlap))}. " + "Nested loop bodies must be strictly contained rather than partially overlapping." + ) + + loops_by_id = {loop["id"]: loop for loop in prepared} + for loop in prepared: + supersets = [other for other in prepared if body_sets[loop["id"]] < body_sets[other["id"]]] + parent = min(supersets, key=lambda item: len(body_sets[item["id"]])) if supersets else None + loop["parent_id"] = parent["id"] if parent else None + loop["child_by_node"] = {} + for child in prepared: + if not child["parent_id"]: + continue + parent = loops_by_id[child["parent_id"]] + for node_id in child["body"]: + parent["child_by_node"][node_id] = child + + root_by_node = {} + for loop in prepared: + root = loop + while root["parent_id"]: + root = loops_by_id[root["parent_id"]] + for node_id in loop["body"]: + root_by_node[node_id] = root + return {"loops": prepared, "by_node": root_by_node, "loops_by_id": loops_by_id} + + def _loop_checkpoint(self, loop, *, iterations, scope=""): + """Return a compatible in-process checkpoint for graph-level retries.""" + task_id = str((self.current_task or {}).get("task_id") or "session") + checkpoints = self.__dict__.setdefault("_loop_checkpoints", {}) + if len(checkpoints) > 20: + oldest_task = next(iter(checkpoints)) + if oldest_task != task_id: + checkpoints.pop(oldest_task, None) + task_checkpoints = checkpoints.setdefault(task_id, {}) + checkpoint_key = f"{scope}/{loop['id']}" if scope else loop["id"] + checkpoint = task_checkpoints.get(checkpoint_key) + signature = (loop["iteration_mode"], int(iterations), bool(loop["carry"]), bool(loop["collect"])) + if not isinstance(checkpoint, dict) or checkpoint.get("signature") != signature: + checkpoint = { + "signature": signature, + "next_index": 0, + "collection": [], + "carry_value": None, + "stopped": False, + } + task_checkpoints[checkpoint_key] = checkpoint + return checkpoint + + def _restore_loop_result(self, loop, nodes, sid, checkpoint): + """Recreate the lightweight result boundary after a cache-clearing graph retry.""" + result_id = loop["result_id"] + if result_id not in self.node_cache: + result_node = nodes[result_id] + work_module = import_module(f"{result_node['module']}.main") + self.node_cache[result_id] = getattr(work_module, result_node["action"])(result_id) + result_node = self.node_cache[result_id] + result_node._sid = sid + result_node.output = { + "collection": list(checkpoint["collection"]) if loop["collect"] else [], + "value": checkpoint["carry_value"], + "stopped": bool(checkpoint["stopped"]), + } + return result_node + + def _execute_graph_loop(self, loop, nodes, sid, checkpoint_scope=""): + iterations = loop["iterations"] + if loop["iteration_mode"] == "collection": + self.execute_node( + loop["item_id"], + nodes[loop["item_id"]], + sid, + param_overrides={"item_index": 0}, + ) + iterations = int(self.node_cache[loop["item_id"]].output.get("count") or 0) + if iterations < 1: + raise ValueError(f"Loop {loop['id']} cannot iterate an empty collection.") + if iterations > loop["max_iterations"]: + raise ValueError( + f"Loop {loop['id']} collection has {iterations} items, above its maximum of {loop['max_iterations']}." + ) + checkpoint = self._loop_checkpoint(loop, iterations=iterations, scope=checkpoint_scope) + collected = list(checkpoint["collection"]) + carry_value = checkpoint["carry_value"] + stopped = bool(checkpoint["stopped"]) + completed_iterations = min(int(checkpoint["next_index"]), iterations) + if completed_iterations: + self.queue_message( + { + "type": "progress", + "node": loop["id"], + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, + **self._current_run_identity_payload(), + "status": "running", + "phase": "loop", + "message": f"Resuming after {completed_iterations} completed iteration(s)", + "progress": int(completed_iterations / iterations * 100), + "current_step": completed_iterations, + "total_steps": iterations, + } + ) + for index in range(completed_iterations, iterations): + if self.interrupt_flag: + raise InterruptedError(f"Loop {loop['id']} was interrupted before iteration {index + 1}.") + runtime_limit = ((self.current_task or {}).get("runtimeHints") or {}).get("maxRuntimeSeconds") + started_at = (self.current_task or {}).get("started_at") + if runtime_limit and started_at and time.time() - float(started_at) >= float(runtime_limit): + raise TimeoutError( + f"Loop {loop['id']} reached the configured {int(runtime_limit)} second runtime limit." + ) + self.queue_message( + { + "type": "progress", + "node": loop["id"], + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, + **self._current_run_identity_payload(), + "status": "running", + "phase": "loop", + "message": f"Iteration {index + 1}/{iterations}", + "progress": int(index / iterations * 100), + "current_step": index + 1, + "total_steps": iterations, + } + ) + retry = 0 + while True: + try: + executed_children = set() + for node_id in loop["body"]: + child_loop = loop.get("child_by_node", {}).get(node_id) + if child_loop is not None: + if child_loop["id"] not in executed_children: + child_scope = f"{checkpoint_scope}/{loop['id']}:{index}".strip("/") + self._execute_graph_loop(child_loop, nodes, sid, checkpoint_scope=child_scope) + executed_children.add(child_loop["id"]) + continue + overrides = None + if node_id == loop["index_id"]: + overrides = {"index_value": index, "iteration_count": iterations} + elif node_id == loop["item_id"]: + overrides = {"item_index": index} + elif node_id == loop["input_id"] and index > 0 and loop["carry"]: + overrides = {"initial": carry_value} + self.execute_node(node_id, nodes[node_id], sid, param_overrides=overrides) + break + except InterruptedError: + raise + except Exception: + if retry >= loop["max_retries"]: + raise + retry += 1 + for node_id in loop["body"]: + self.node_cache.pop(node_id, None) + self.queue_message( + { + "type": "progress", + "node": loop["id"], + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, + **self._current_run_identity_payload(), + "status": "running", + "phase": "loop", + "message": f"Retrying iteration {index + 1}/{iterations} ({retry}/{loop['max_retries']})", + "progress": int(index / iterations * 100), + "current_step": index + 1, + "total_steps": iterations, + } + ) + + result = self.node_cache[loop["result_id"]].output + carry_value = result.get("value") + collected.append(carry_value) + stopped = bool(result.get("stopped")) + completed_iterations = index + 1 + checkpoint.update( + { + "next_index": completed_iterations, + "collection": list(collected), + "carry_value": carry_value, + "stopped": stopped, + } + ) + if stopped: + break + + result_node = self._restore_loop_result(loop, nodes, sid, checkpoint) + result_node.output["collection"] = collected if loop["collect"] else [] + result_node.output["value"] = carry_value + result_node.output["stopped"] = stopped + self.queue_message( + { + "type": "progress", + "node": loop["id"], + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, + **self._current_run_identity_payload(), + "status": "succeeded", + "phase": "loop", + "message": f"Completed {completed_iterations} iteration(s)", + "progress": 100, + "current_step": completed_iterations, + "total_steps": iterations, + } + ) + return {"iterations": completed_iterations, "stopped": stopped, "collection": collected} + + def _capture_execution_process_state(self): + """Capture process-wide RNG and Torch backend settings changed by a run.""" + + state = { + "python_random": random.getstate(), + "python_hash_seed_present": "PYTHONHASHSEED" in os.environ, + "python_hash_seed": os.environ.get("PYTHONHASHSEED"), + } + try: + np = import_module("numpy") + state["numpy_module"] = np + state["numpy_random"] = np.random.get_state() + except Exception: + pass + try: + torch = import_module("torch") + state["torch_module"] = torch + get_rng_state = getattr(torch, "get_rng_state", None) + if callable(get_rng_state): + state["torch_rng"] = get_rng_state() + deterministic_probe = getattr(torch, "are_deterministic_algorithms_enabled", None) + if callable(deterministic_probe): + state["torch_deterministic_algorithms"] = bool(deterministic_probe()) + warn_only_probe = getattr(torch, "is_deterministic_algorithms_warn_only_enabled", None) + if callable(warn_only_probe): + state["torch_deterministic_warn_only"] = bool(warn_only_probe()) + + cuda = getattr(torch, "cuda", None) + is_initialized = getattr(cuda, "is_initialized", None) + get_cuda_rng = getattr(cuda, "get_rng_state_all", None) + if callable(is_initialized) and is_initialized() and callable(get_cuda_rng): + state["torch_cuda_rng"] = get_cuda_rng() + + cudnn = getattr(getattr(torch, "backends", None), "cudnn", None) + if cudnn is not None: + for name in ("benchmark", "deterministic", "allow_tf32"): + if hasattr(cudnn, name): + state[f"cudnn_{name}"] = getattr(cudnn, name) + matmul = getattr(getattr(getattr(torch, "backends", None), "cuda", None), "matmul", None) + if matmul is not None and hasattr(matmul, "allow_tf32"): + state["cuda_matmul_allow_tf32"] = matmul.allow_tf32 + except Exception: + pass + return state + + def _restore_execution_process_state(self, state, graph): + try: + random.setstate(state["python_random"]) + if state.get("python_hash_seed_present"): + os.environ["PYTHONHASHSEED"] = state.get("python_hash_seed") or "" + else: + os.environ.pop("PYTHONHASHSEED", None) + + np = state.get("numpy_module") + if np is not None and "numpy_random" in state: + np.random.set_state(state["numpy_random"]) + + torch = state.get("torch_module") + if torch is None: + return + set_rng_state = getattr(torch, "set_rng_state", None) + if callable(set_rng_state) and "torch_rng" in state: + set_rng_state(state["torch_rng"]) + set_cuda_rng = getattr(getattr(torch, "cuda", None), "set_rng_state_all", None) + if callable(set_cuda_rng) and "torch_cuda_rng" in state: + set_cuda_rng(state["torch_cuda_rng"]) + + deterministic = state.get("torch_deterministic_algorithms") + deterministic_setter = getattr(torch, "use_deterministic_algorithms", None) + if deterministic is not None and callable(deterministic_setter): + deterministic_setter( + deterministic, + warn_only=bool(state.get("torch_deterministic_warn_only", False)), + ) + cudnn = getattr(getattr(torch, "backends", None), "cudnn", None) + if cudnn is not None: + for name in ("benchmark", "deterministic", "allow_tf32"): + key = f"cudnn_{name}" + if key in state: + setattr(cudnn, name, state[key]) + matmul = getattr(getattr(getattr(torch, "backends", None), "cuda", None), "matmul", None) + if matmul is not None and "cuda_matmul_allow_tf32" in state: + matmul.allow_tf32 = state["cuda_matmul_allow_tf32"] + + runtime_hints = graph.get("runtimeHints") if isinstance(graph, dict) else None + cuda_index = self._cuda_index_from_runtime_hints(runtime_hints) + cuda = getattr(torch, "cuda", None) + if cuda_index is not None and callable(getattr(cuda, "set_per_process_memory_fraction", None)): + cuda.set_per_process_memory_fraction(1.0, cuda_index) + except Exception as exc: + logger.warning("Could not fully restore process-wide execution settings: %s", exc) + + def execute_graph(self, graph): + process_state = self._capture_execution_process_state() + try: + return self._execute_graph(graph) + finally: + self._restore_execution_process_state(process_state, graph) + + def _execute_graph(self, graph): + sid = graph["sid"] + nodes = graph["nodes"] + paths = graph["paths"] + self._active_graph_node_ids = set(nodes) + graph_loops = self._prepare_graph_loops(graph) + + graph_execution_time = time.time() + base_runtime_hints = self._coerce_runtime_hints(graph.get("runtimeHints")) + if isinstance(base_runtime_hints, dict): + base_runtime_hints["loaderContract"] = self._graph_loader_contract(nodes) + auto_runtime_preparation = self._prepare_auto_runtime_for_graph(base_runtime_hints) + if self.current_task and auto_runtime_preparation is not None: + self.current_task["autoRuntimePreparation"] = auto_runtime_preparation + retry_plans = self._coerce_retry_plan_list(base_runtime_hints) + retry_history = [] + deterministic_receipt = None + attempt_index = 0 + retry_plan_index = -1 + + while True: + if self.current_task: + self.current_task["attempt_index"] = attempt_index + self.current_task["progress"] = 0 + runtime_hints = deepcopy(base_runtime_hints) if base_runtime_hints else None + active_retry_plan = ( + retry_plans[retry_plan_index] + if retry_plan_index >= 0 and retry_plan_index < len(retry_plans) + else None + ) + if self.current_task and runtime_hints is not None: + self.current_task["runtimeHints"] = runtime_hints + if runtime_hints and active_retry_plan is None and attempt_index == 0: + self._assert_auto_resource_candidate_ready(runtime_hints) + auto_plan = runtime_hints.get("autoResourcePlan") + if isinstance(auto_plan, dict) and self._auto_resource_candidate_is_proven(auto_plan): + updated_nodes = self._apply_resource_retry_plan_to_graph(graph, auto_plan) + if updated_nodes: + self.queue_message( + { + "type": "auto_resource_plan_applied", + "sid": sid, + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt": attempt_index, + "attempt_index": attempt_index, + "candidateId": auto_plan.get("id"), + "updatedNodes": updated_nodes, + "message": "Applied the proven Auto resource plan before execution.", + } + ) + if runtime_hints and active_retry_plan: + updated_nodes = self._apply_resource_retry_plan_to_graph(graph, active_retry_plan) + retry_mode = active_retry_plan.get("offloadMode") + if isinstance(retry_mode, str): + runtime_hints["offloadMode"] = retry_mode + runtime_hints["autoOffload"] = retry_mode != OFFLOAD_MODE_NONE + runtime_hints["offloadDiskPath"] = ( + "data/offload/diffusers" if retry_mode == OFFLOAD_MODE_GROUP_DISK else None + ) + if isinstance(active_retry_plan.get("modelRepo"), str): + runtime_hints["modelRepo"] = active_retry_plan["modelRepo"] + runtime_hints["resolvedModelRepo"] = active_retry_plan["modelRepo"] + if isinstance(active_retry_plan.get("resolvedArtifact"), str): + runtime_hints["resolvedArtifact"] = active_retry_plan["resolvedArtifact"] + elif isinstance(active_retry_plan.get("modelRepo"), str): + runtime_hints["resolvedArtifact"] = active_retry_plan["modelRepo"] + if isinstance(active_retry_plan.get("executionPath"), str): + runtime_hints["executionPath"] = active_retry_plan["executionPath"] + if isinstance(active_retry_plan.get("quantizationMode"), str): + runtime_hints["quantizationMode"] = active_retry_plan["quantizationMode"] + if isinstance(active_retry_plan.get("quantizedComponents"), list): + runtime_hints["quantizedComponents"] = [ + str(item) for item in active_retry_plan["quantizedComponents"] + ] + runtime_hints["resourceRetryAttempt"] = attempt_index + runtime_hints["resourceRetryHistory"] = retry_history + plan = runtime_hints.get("resourcePlan") + if isinstance(plan, dict): + plan["activeRetryPlan"] = self._sanitize_retry_plan_for_hints(active_retry_plan) + if isinstance(retry_mode, str): + plan["offloadMode"] = retry_mode + plan["autoOffload"] = retry_mode != OFFLOAD_MODE_NONE + retry_message = ( + f"Retrying with {str(retry_mode or active_retry_plan.get('reason') or 'safer plan').replace('_', '-')} " + f"after {retry_history[-1]['errorCode'] if retry_history else 'resource pressure'}." + ) + retry_progress = self.record_node_progress( + { + "type": "progress", + "node": self.current_task.get("current_node") if self.current_task else None, + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": attempt_index, + **self._current_run_identity_payload(), + "status": "running", + "phase": "retry", + "message": retry_message, + "progress": -1, + } + ) + self.queue_message(retry_progress) + self.queue_message( + { + "type": "resource_retry", + "sid": sid, + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt": attempt_index, + "attempt_index": attempt_index, + "offloadMode": retry_mode, + "retryPlan": self._sanitize_retry_plan_for_hints(active_retry_plan), + "updatedNodes": updated_nodes, + "message": retry_message, + "history": retry_history, + } + ) + + runtime_budget = self._apply_cuda_runtime_budget(runtime_hints) + deterministic = self._apply_deterministic_mode(graph) + if deterministic is not None: + deterministic_receipt = deterministic + runtime_fingerprint = self._runtime_fingerprint() + if self.current_task: + self.current_task["runtimeFingerprint"] = runtime_fingerprint.get("fingerprint") + if deterministic is not None: + self.current_task["deterministicMode"] = deterministic + self.current_task["runtimeHints"] = runtime_hints + self.current_task["runtimeBudget"] = runtime_budget + + if deterministic: + self.queue_message( + { + "type": "deterministic_execution", + "sid": sid, + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": attempt_index, + "deterministicMode": deterministic, + "runtimeFingerprint": runtime_fingerprint, + } + ) + + node_weights = { + id: node_execution_weight(nodes[id].get("module", ""), nodes[id].get("action", "")) + for path in paths + for id in path + if id in nodes + } + total_task_weight = sum(node_weights.values()) or 1 + task_progress = 0.0 + attempt_started_at = time.monotonic() + self._reset_runtime_measurement() + executed_loops = set() + + try: for path in paths: for id in path: if self.interrupt_flag: @@ -3693,112 +7372,266 @@ def execute_graph(self, graph): if self.current_task: node = nodes.get(id, {}) - module = node.get('module', '') - action = node.get('action', '') - self.current_task.update({ - "updated_at": time.time(), - "current_node": id, - "current_node_name": f"{module}.{action}", - "node_progress": -1, - "phase": node_execution_phase(module, action), - "message": node_execution_message(module, action, node_execution_phase(module, action)), - "current_step": None, - "total_steps": None, - "completed_progress": task_progress, - "current_node_weight": node_weights.get(id, 1.0) / total_task_weight * 100, - }) - self.execute_node(id, nodes[id], sid) + module = node.get("module", "") + action = node.get("action", "") + node_started_at = time.time() + next_phase = node_execution_phase(module, action) + prior_phase = self.current_task.get("phase") + prior_phase_started_at = self.current_task.get("_phase_started_at") + phase_timings = self.current_task.setdefault("phase_timings", {}) + if ( + prior_phase + and prior_phase != next_phase + and isinstance(phase_timings, dict) + and isinstance(prior_phase_started_at, (int, float)) + ): + phase_timings[prior_phase] = float(phase_timings.get(prior_phase, 0.0)) + max( + 0.0, node_started_at - float(prior_phase_started_at) + ) + self.current_task.update( + { + "updated_at": node_started_at, + "last_heartbeat_at": node_started_at, + "current_node": id, + "current_node_name": f"{module}.{action}", + "node_progress": -1, + "phase": next_phase, + "_phase_started_at": node_started_at, + "message": node_execution_message(module, action, next_phase), + "current_step": None, + "total_steps": None, + "component": None, + "shard_current": None, + "shard_total": None, + "elapsed_seconds": None, + "average_step_seconds": None, + "eta_seconds": None, + "completed_progress": task_progress, + "current_node_weight": node_weights.get(id, 1.0) / total_task_weight * 100, + } + ) + self._persist_supervisor_queue_state(force=True) + graph_loop = graph_loops["by_node"].get(id) + if graph_loop is not None: + loop_id = graph_loop["id"] + if loop_id in executed_loops: + continue + self._execute_graph_loop(graph_loop, nodes, sid) + executed_loops.add(loop_id) + else: + self.execute_node(id, nodes[id], sid) # broadcast the task progress if self.current_task: task_progress += node_weights.get(id, 1.0) / total_task_weight * 100 - self.current_task['progress'] = int(task_progress) - self.current_task['completed_progress'] = task_progress - self.current_task['node_progress'] = 100 - self.current_task['updated_at'] = time.time() - self.queue_message({ - "type": "task_progress", - "task_id": self.current_task["task_id"], - "attempt_index": self.current_task.get("attempt_index"), - **self._current_run_identity_payload(), - "progress": self.current_task['progress'], - }) + self.current_task["progress"] = int(task_progress) + self.current_task["completed_progress"] = task_progress + self.current_task["node_progress"] = 100 + self.current_task["updated_at"] = time.time() + self.queue_message( + { + "type": "task_progress", + "task_id": self.current_task["task_id"], + "attempt_index": self.current_task.get("attempt_index"), + **self._current_run_identity_payload(), + "progress": self.current_task["progress"], + } + ) except Exception as e: classification = self._classify_exception(e) next_retry_plan_index = None - if classification['error_code'] != 'cuda_context_poisoned': - next_retry_plan_index = self._next_retry_plan_index(retry_plans, retry_plan_index, classification) + pinned_retry_plan_indexes = [] + if classification["error_code"] != "cuda_context_poisoned": + next_retry_plan_index, pinned_retry_plan_indexes = self._next_applicable_retry_plan_index( + graph, + retry_plans, + retry_plan_index, + classification, + ) can_retry = next_retry_plan_index is not None if not can_retry: + if pinned_retry_plan_indexes: + setattr(e, "modiff_error_code", "auto_retry_requires_override_approval") + setattr(e, "modiff_category", "auto_resource") + setattr( + e, + "modiff_recovery_hint", + "A safer Auto retry would change one or more pinned workflow fields. " + "Reset the conflicting field to Auto or change it explicitly, then retry.", + ) + self.queue_message( + { + "type": "auto_retry_requires_approval", + "sid": sid, + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": attempt_index, + **self._current_run_identity_payload(), + "node": getattr(e, "modiff_node_id", None), + "message": getattr(e, "modiff_recovery_hint"), + "retryPlans": [ + self._sanitize_retry_plan_for_hints(retry_plans[index]) + for index in pinned_retry_plan_indexes + ], + } + ) raise - retry_history.append({ - 'attempt': attempt_index, - 'offloadMode': runtime_hints.get('offloadMode') if runtime_hints else None, - 'retryPlan': self._sanitize_retry_plan_for_hints(active_retry_plan), - 'category': classification.get('category'), - 'errorCode': classification.get('error_code'), - 'error': str(e) or type(e).__name__, - 'node': getattr(e, 'modiff_node_id', None), - 'nodeName': getattr(e, 'modiff_node_name', None), - 'loaderDiagnostics': self._loader_diagnostics_snapshot(), - 'nextRetryPlan': self._sanitize_retry_plan_for_hints(retry_plans[next_retry_plan_index]), - }) + retry_history.append( + { + "attempt": attempt_index, + "offloadMode": runtime_hints.get("offloadMode") if runtime_hints else None, + "retryPlan": self._sanitize_retry_plan_for_hints(active_retry_plan), + "category": classification.get("category"), + "errorCode": classification.get("error_code"), + "error": str(e) or type(e).__name__, + "node": getattr(e, "modiff_node_id", None), + "nodeName": getattr(e, "modiff_node_name", None), + "loaderDiagnostics": self._loader_diagnostics_snapshot(), + "nextRetryPlan": self._sanitize_retry_plan_for_hints(retry_plans[next_retry_plan_index]), + } + ) if runtime_hints is not None: - runtime_hints['resourceRetryLastError'] = str(e) or type(e).__name__ - runtime_hints['resourceRetryLastCode'] = classification.get('error_code') - runtime_hints['resourceRetryHistory'] = retry_history + runtime_hints["resourceRetryLastError"] = str(e) or type(e).__name__ + runtime_hints["resourceRetryLastCode"] = classification.get("error_code") + runtime_hints["resourceRetryHistory"] = retry_history if self.current_task: - self.current_task['runtimeHints'] = runtime_hints + self.current_task["runtimeHints"] = runtime_hints + cleanup_progress = self.record_node_progress( + { + "type": "progress", + "node": getattr(e, "modiff_node_id", None) + or (self.current_task.get("current_node") if self.current_task else None), + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": attempt_index, + **self._current_run_identity_payload(), + "status": "running", + "phase": "cleanup", + "message": "Releasing failed-attempt model and accelerator caches before retry", + "progress": -1, + } + ) + self.queue_message(cleanup_progress) cleanup = self._release_runtime_caches_for_retry() - self.queue_message({ - "type": "resource_retry_cleanup", - "sid": sid, - "task_id": self.current_task.get("task_id") if self.current_task else None, - "attempt": attempt_index, - "attempt_index": attempt_index, - **cleanup, - }, sid) + self.queue_message( + { + "type": "resource_retry_cleanup", + "sid": sid, + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt": attempt_index, + "attempt_index": attempt_index, + **cleanup, + } + ) attempt_index += 1 retry_plan_index = next_retry_plan_index continue # the graph has completed - self._record_auto_resource_success(runtime_hints, runtime_fingerprint) - self.queue_message({ - "type": "graph_completed", - "sid": sid, - "task_id": self.current_task.get("task_id") if self.current_task else None, - **self._current_run_identity_payload(), - "executionTime": time.time() - graph_execution_time, - "runtimeFingerprint": runtime_fingerprint, - "deterministicMode": deterministic, - "runtimeHints": runtime_hints, - "runtimeBudget": runtime_budget, - "resourceRetryHistory": retry_history, - }, sid) + runtime_measurement = self._runtime_measurement( + elapsed_seconds=time.monotonic() - attempt_started_at, + ) + self._record_auto_resource_success(runtime_hints, runtime_fingerprint, runtime_measurement) + optimization_receipts = self._record_optimization_observations( + runtime_hints, + runtime_fingerprint, + runtime_measurement, + graph=graph, + ) + if self.current_task: + self.current_task.update( + { + "runtimeFingerprint": runtime_fingerprint, + "resourceCandidateId": ( + runtime_hints.get("autoResourceCandidateId") if isinstance(runtime_hints, dict) else None + ), + "runtimeMeasurement": runtime_measurement, + "optimizationReceiptIds": [item.get("id") for item in optimization_receipts], + "updated_at": time.time(), + } + ) + task_id = str((self.current_task or {}).get("task_id") or "session") + self.__dict__.setdefault("_loop_checkpoints", {}).pop(task_id, None) + self.queue_message( + { + "type": "graph_completed", + "sid": sid, + "task_id": self.current_task.get("task_id") if self.current_task else None, + **self._current_run_identity_payload(), + "executionTime": time.time() - graph_execution_time, + "runtimeFingerprint": runtime_fingerprint, + "deterministicMode": deterministic_receipt, + "runtimeHints": runtime_hints, + "runtimeBudget": runtime_budget, + "runtimeMeasurement": runtime_measurement, + "resourceRetryHistory": retry_history, + } + ) return - async def stop_execution(self, _): + async def stop_execution(self, request): # check if there is a current task or any queued task if not self.current_task and not self.queued_tasks: - return web.json_response({ - "error": True, - "message": "Nothing to do. No task is currently running or queued.", - }) + return web.json_response( + { + "error": True, + "message": "Nothing to do. No task is currently running or queued.", + } + ) if self.interrupt_flag: - return web.json_response({ - "error": True, - "message": "Execution is already set for interruption.", - }) + return web.json_response( + { + "error": True, + "message": "Execution is already set for interruption.", + } + ) + + cancelled_queued = [] + for task_id, task in list(self.queued_tasks.items()): + self.queued_tasks.pop(task_id, None) + self.task_graphs.pop(task_id, None) + cancelled_queued.append(task_id) + self.queue_message( + { + "type": "task_cancelled", + "task_id": task_id, + **self._run_identity_payload(task.get("runtimeHints")), + "status": "cancelled", + "message": "Cancelled before execution.", + } + ) + + if not self.current_task: + self.interrupt_flag = False + self._persist_supervisor_queue_state(force=True) + return web.json_response( + { + "error": False, + "message": f"Cancelled {len(cancelled_queued)} queued task(s).", + "cancelled_queued_task_ids": cancelled_queued, + "cleanup_pending": False, + } + ) self.interrupt_flag = True if self.current_task: self.current_task["interrupt_requested"] = True self.current_task["updated_at"] = time.time() - self.current_task["message"] = "Stopping after the current model step" + self.current_task["phase"] = "stopping" + self.current_task["message"] = "Stopping and releasing runtime resources" + self._persist_supervisor_queue_state(force=True) + self.queue_message( + { + "type": "task_progress", + "task_id": self.current_task.get("task_id"), + **self._current_run_identity_payload(), + "status": "running", + "phase": "stopping", + "progress": self.current_task.get("progress", 0), + "message": self.current_task["message"], + } + ) # set the interrupt flag for all the nodes in the cache for node in self.node_cache: @@ -3807,10 +7640,67 @@ async def stop_execution(self, _): if active_pipeline is not None and hasattr(active_pipeline, "_interrupt"): active_pipeline._interrupt = True - return web.json_response({ - "error": False, - "message": "Execution set for interruption.", - }) + hard_restart_after_ms = self._schedule_forced_restart_if_still_running( + self.current_task.get("task_id") if self.current_task else None + ) + return web.json_response( + { + "error": False, + "message": ( + "Execution is stopping. Runtime resources will be released before the queue can advance." + if hard_restart_after_ms is None + else "Execution is stopping. If the active model call does not return promptly, " + "MoDiff will restart its backend worker to release RAM and VRAM." + ), + "task_id": self.current_task.get("task_id") if self.current_task else None, + "cancelled_queued_task_ids": cancelled_queued, + "cleanup_pending": True, + "hard_restart_scheduled": hard_restart_after_ms is not None, + "hard_restart_after_ms": hard_restart_after_ms, + } + ) + + def _schedule_forced_restart_if_still_running(self, task_id): + """Escalate cooperative cancellation by replacing the supervised worker.""" + if not task_id or os.environ.get("MODIFF_WORKER_SUPERVISED") != "1": + return None + try: + grace_seconds = max(0.25, float(os.environ.get("MODIFF_HARD_CANCEL_GRACE_SECONDS", "2"))) + except (TypeError, ValueError): + grace_seconds = 2.0 + prior = self._forced_restart_timer + if prior is not None: + prior.cancel() + timer = threading.Timer(grace_seconds, self._force_restart_if_task_is_active, args=(task_id,)) + timer.daemon = True + self._forced_restart_timer = timer + timer.start() + return int(grace_seconds * 1000) + + def _force_restart_if_task_is_active(self, task_id): + current = self.current_task if isinstance(self.current_task, dict) else {} + if current.get("task_id") != task_id or not current.get("interrupt_requested"): + return + logger.error( + "Task %s did not honor cancellation; replacing the supervised backend worker to release runtime memory.", + task_id, + ) + self.queue_message( + { + "type": "task_cancelled", + "task_id": task_id, + **self._current_run_identity_payload(), + "status": "cancelled", + "message": "The backend worker is restarting to finish cancellation and release RAM/VRAM.", + "backend_restart": True, + } + ) + # Give the event-loop queue one brief opportunity to flush the terminal + # notification. The supervisor immediately replaces this process; the + # operating system, rather than Python object finalizers, releases all + # remaining accelerator allocations. + time.sleep(0.05) + os._exit(SUPERVISED_RESTART_EXIT_CODE) def _connected_output_value(self, *, target_node_id, target_node_name, target_param, source_node_id, source_key): source_node = self.node_cache.get(source_node_id) @@ -3819,14 +7709,16 @@ def _connected_output_value(self, *, target_node_id, target_node_name, target_pa f"Connected input '{target_param}' on {target_node_name} expected output '{source_key}' " f"from upstream node {source_node_id}, but that node has not executed." ) - setattr(error, 'modiff_node_id', source_node_id) - setattr(error, 'modiff_node_name', 'Unknown upstream node') - setattr(error, 'modiff_target_node_id', target_node_id) - setattr(error, 'modiff_target_node_name', target_node_name) + setattr(error, "modiff_node_id", source_node_id) + setattr(error, "modiff_node_name", "Unknown upstream node") + setattr(error, "modiff_target_node_id", target_node_id) + setattr(error, "modiff_target_node_name", target_node_name) raise error - output = getattr(source_node, 'output', None) - source_name = f"{getattr(source_node, 'module_name', 'unknown')}.{getattr(source_node, 'class_name', 'unknown')}" + output = getattr(source_node, "output", None) + source_name = ( + f"{getattr(source_node, 'module_name', 'unknown')}.{getattr(source_node, 'class_name', 'unknown')}" + ) if not isinstance(output, dict) or source_key not in output or output.get(source_key) is None: available = sorted(output.keys()) if isinstance(output, dict) else [] error = MissingConnectedOutputError( @@ -3834,18 +7726,44 @@ def _connected_output_value(self, *, target_node_id, target_node_name, target_pa f"from upstream node {source_name}, but that output was not produced. " f"Available outputs: {available or 'none'}." ) - setattr(error, 'modiff_node_id', source_node_id) - setattr(error, 'modiff_node_name', source_name) - setattr(error, 'modiff_target_node_id', target_node_id) - setattr(error, 'modiff_target_node_name', target_node_name) + setattr(error, "modiff_node_id", source_node_id) + setattr(error, "modiff_node_name", source_name) + setattr(error, "modiff_target_node_id", target_node_id) + setattr(error, "modiff_target_node_name", target_node_name) raise error return output[source_key] - def execute_node(self, id, node, sid, quiet=False): - module = node['module'] - action = node['action'] - params = node['params'] + def _adopt_reusable_loader_node(self, node_id, module, action): + """Move a compatible loader cache entry to a new workflow node id. + + Workflow node ids are document-local, while an unchanged loader + contract can safely keep its resident pipeline between sequential + workflows. NodeBase revalidates every argument on the subsequent call; + if anything differs, it performs the normal unload/reload path. + """ + if action not in {"LoadPipeline", "ModelsLoader"}: + return None + for cached_id, cached_node in list(self.node_cache.items()): + if cached_id == node_id or cached_id in self._active_graph_node_ids: + continue + if getattr(cached_node, "module_name", None) != module: + continue + if getattr(cached_node, "class_name", None) != action: + continue + prepare_for_reuse = getattr(cached_node, "prepare_for_workflow_reuse", None) + if callable(prepare_for_reuse): + prepare_for_reuse() + self.node_cache.pop(cached_id, None) + cached_node.node_id = node_id + self.node_cache[node_id] = cached_node + return cached_id + return None + + def execute_node(self, id, node, sid, quiet=False, param_overrides=None): + module = node["module"] + action = node["action"] + params = node["params"] if module not in self.modules: raise ValueError(f"Invalid module: {module}") @@ -3856,31 +7774,47 @@ def execute_node(self, id, node, sid, quiet=False): # get the arguments values args = {} ui_fields = {} + upstream_changed = False for p in params: - data_source_id = params[p].get('sourceId') - data_param_key = params[p].get('sourceKey') + data_source_id = params[p].get("sourceId") + data_param_key = params[p].get("sourceKey") # the field is a UI element, used mostly to display the data in the UI - if 'display' in params[p] and params[p]['display'] in ['ui_group', 'ui_text', 'ui_image', 'ui_imagecompare', 'ui_areaselect', 'ui_audio', 'ui_video', 'ui_3d', 'ui_label', 'ui_button']: + if "display" in params[p] and params[p]["display"] in [ + "ui_group", + "ui_text", + "ui_image", + "ui_imagecompare", + "ui_areaselect", + "ui_audio", + "ui_video", + "ui_3d", + "ui_label", + "ui_button", + ]: ui_fields[p] = data_param_key if data_param_key else None # the field is an input that gets its value from an output of another node elif data_source_id and data_param_key: + source_node = self.node_cache.get(data_source_id) + upstream_changed = upstream_changed or bool(getattr(source_node, "_has_changed", False)) # spawn field handling - #if '>>>' in p or self.modules[module][action]['params'][p].get('spawn'): - if params[p].get('spawn'): - spawn_key = p.split('>>>')[0] + # if '>>>' in p or self.modules[module][action]['params'][p].get('spawn'): + if params[p].get("spawn"): + spawn_key = p.split(">>>")[0] if not spawn_key in args: args[spawn_key] = [] - args[spawn_key].append(self._connected_output_value( - target_node_id=id, - target_node_name=f"{module}.{action}", - target_param=p, - source_node_id=data_source_id, - source_key=data_param_key, - )) + args[spawn_key].append( + self._connected_output_value( + target_node_id=id, + target_node_name=f"{module}.{action}", + target_param=p, + source_node_id=data_source_id, + source_key=data_param_key, + ) + ) else: args[p] = self._connected_output_value( target_node_id=id, @@ -3891,7 +7825,12 @@ def execute_node(self, id, node, sid, quiet=False): ) # the field is a static value else: - args[p] = params[p].get('value') + args[p] = params[p].get("value") + + if param_overrides: + if not isinstance(param_overrides, dict): + raise TypeError("Node parameter overrides must be a dictionary.") + args.update(param_overrides) if not quiet: reset_memory_stats() @@ -3899,104 +7838,214 @@ def execute_node(self, id, node, sid, quiet=False): phase = node_execution_phase(module, action) # tell the client that the node is running - self.queue_message({ - "type": "progress", - "node": id, - "name": f"{module}.{action}", - "task_id": self.current_task.get("task_id") if self.current_task else None, - "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, - **self._current_run_identity_payload(), - "status": "running", - "phase": phase, - "message": node_execution_message(module, action, phase), - "current_node": id, - "progress": -1, # -1 sets the progress to indeterminate - }, sid) + starting_progress = self.record_node_progress( + { + "type": "progress", + "node": id, + "name": f"{module}.{action}", + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, + **self._current_run_identity_payload(), + "status": "running", + "phase": phase, + "message": node_execution_message(module, action, phase), + "current_node": id, + "progress": -1, # -1 sets the progress to indeterminate + } + ) + self.queue_message(starting_progress) # if the node is not in the cache, initialize it if id not in self.node_cache: - work_module = import_module(f"{module}.main") - work_action = getattr(work_module, action) - self.node_cache[id] = work_action(id) + reused_node_id = self._adopt_reusable_loader_node(id, module, action) + if reused_node_id is None: + work_module = import_module(f"{module}.main") + work_action = getattr(work_module, action) + self.node_cache[id] = work_action(id) + else: + self.queue_message( + { + "type": "runtime_loader_reused", + "task_id": self.current_task.get("task_id") if self.current_task else None, + **self._current_run_identity_payload(), + "node": id, + "previous_node": reused_node_id, + "module": module, + "action": action, + "message": "Reusing the resident loader across workflows; inputs will be revalidated.", + } + ) if not callable(self.node_cache[id]): - raise TypeError(f"The class `{module}.{action}` is not callable. Make sure the class has a `__call__` method or extends `NodeBase`.") + raise TypeError( + f"The class `{module}.{action}` is not callable. Make sure the class has a `__call__` method or extends `NodeBase`." + ) # set the session id, it can be used to send messages from the node back to the client self.node_cache[id]._sid = sid + if upstream_changed: + invalidate_cache = getattr(self.node_cache[id], "invalidate_cache", None) + if callable(invalidate_cache): + invalidate_cache() + + heartbeat_stop = threading.Event() + heartbeat_thread = None + if not quiet: + heartbeat_task_id = self.current_task.get("task_id") if self.current_task else None + + def publish_node_heartbeat(): + while not heartbeat_stop.wait(5.0): + current_task = self.current_task + if ( + not current_task + or current_task.get("task_id") != heartbeat_task_id + or current_task.get("current_node") != id + ): + return + heartbeat_at = time.time() + progress_value = current_task.get("node_progress") + if not isinstance(progress_value, (int, float)): + progress_value = -1 + try: + resource_snapshot = self._runtime_resource_snapshot(max_age_seconds=1.5) + except Exception: + resource_snapshot = None + heartbeat_payload = self.record_node_progress( + { + "type": "progress", + "node": id, + "name": f"{module}.{action}", + "task_id": heartbeat_task_id, + "attempt_index": current_task.get("attempt_index"), + **self._current_run_identity_payload(), + "status": "running", + "phase": current_task.get("phase") or node_execution_phase(module, action), + "message": current_task.get("message") + or node_execution_message(module, action, node_execution_phase(module, action)), + "component": current_task.get("component"), + "shard_current": current_task.get("shard_current"), + "shard_total": current_task.get("shard_total"), + "current_step": current_task.get("current_step"), + "total_steps": current_task.get("total_steps"), + "elapsed_seconds": max(0.0, heartbeat_at - start_time), + "average_step_seconds": current_task.get("average_step_seconds"), + "eta_seconds": current_task.get("eta_seconds"), + "last_heartbeat_at": heartbeat_at, + "resource_snapshot": resource_snapshot, + "progress": progress_value, + } + ) + self.queue_message(heartbeat_payload) + + heartbeat_thread = threading.Thread( + target=publish_node_heartbeat, + name=f"modiff-progress-{id}", + daemon=True, + ) + heartbeat_thread.start() # *** execute the node *** try: self.node_cache[id](**args) except Exception as e: + heartbeat_stop.set() + if heartbeat_thread is not None: + heartbeat_thread.join(timeout=0.2) traceback_text = traceback.format_exc() logger.error(f"Error executing node {id} ({module}.{action})") logger.error(traceback_text) - setattr(e, 'modiff_node_id', id) - setattr(e, 'modiff_node_name', f"{module}.{action}") - setattr(e, 'modiff_traceback', traceback_text) - self.queue_message({ - "type": "node_error", - **self._exception_payload( - e, - task_id=self.current_task.get("task_id") if self.current_task else None, - sid=sid, - node_id=id, - node_name=f"{module}.{action}", - traceback_text=traceback_text, - ), - **self._current_run_identity_payload(), - "status": "failed", - "current_node": id, - }, sid) - if not quiet: - self.queue_message({ - "type": "progress", - "node": id, - "task_id": self.current_task.get("task_id") if self.current_task else None, - "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, + setattr(e, "modiff_node_id", id) + setattr(e, "modiff_node_name", f"{module}.{action}") + setattr(e, "modiff_traceback", traceback_text) + self.queue_message( + { + "type": "node_error", + **self._exception_payload( + e, + task_id=self.current_task.get("task_id") if self.current_task else None, + sid=sid, + node_id=id, + node_name=f"{module}.{action}", + traceback_text=traceback_text, + ), **self._current_run_identity_payload(), "status": "failed", - "phase": node_execution_phase(module, action), - "message": f"{module}.{action} failed", - "progress": 0, - }, sid) + "current_node": id, + } + ) + if not quiet: + self.queue_message( + { + "type": "progress", + "node": id, + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, + **self._current_run_identity_payload(), + "status": "failed", + "phase": node_execution_phase(module, action), + "message": f"{module}.{action} failed", + "progress": 0, + } + ) raise e + heartbeat_stop.set() + if heartbeat_thread is not None: + heartbeat_thread.join(timeout=0.2) + if not quiet: execution_time = time.time() - start_time - self.node_cache[id]._execution_time['last'] = execution_time - self.node_cache[id]._execution_time['min'] = min(self.node_cache[id]._execution_time['min'], execution_time) if self.node_cache[id]._execution_time['min'] is not None else execution_time - self.node_cache[id]._execution_time['max'] = max(self.node_cache[id]._execution_time['max'], execution_time) if self.node_cache[id]._execution_time['max'] is not None else execution_time + self.node_cache[id]._execution_time["last"] = execution_time + self.node_cache[id]._execution_time["min"] = ( + min(self.node_cache[id]._execution_time["min"], execution_time) + if self.node_cache[id]._execution_time["min"] is not None + else execution_time + ) + self.node_cache[id]._execution_time["max"] = ( + max(self.node_cache[id]._execution_time["max"], execution_time) + if self.node_cache[id]._execution_time["max"] is not None + else execution_time + ) memory_stats = get_memory_stats() if memory_stats: - self.node_cache[id]._memory_usage['last'] = memory_stats['peak'] - self.node_cache[id]._memory_usage['min'] = min(self.node_cache[id]._memory_usage['min'], memory_stats['peak']) if self.node_cache[id]._memory_usage['min'] is not None else memory_stats['peak'] - self.node_cache[id]._memory_usage['max'] = max(self.node_cache[id]._memory_usage['max'], memory_stats['peak']) if self.node_cache[id]._memory_usage['max'] is not None else memory_stats['peak'] + self.node_cache[id]._memory_usage["last"] = memory_stats["peak"] + self.node_cache[id]._memory_usage["min"] = ( + min(self.node_cache[id]._memory_usage["min"], memory_stats["peak"]) + if self.node_cache[id]._memory_usage["min"] is not None + else memory_stats["peak"] + ) + self.node_cache[id]._memory_usage["max"] = ( + max(self.node_cache[id]._memory_usage["max"], memory_stats["peak"]) + if self.node_cache[id]._memory_usage["max"] is not None + else memory_stats["peak"] + ) # the node has completed - self.queue_message({ - "type": "executed", - "node": id, - "name": f"{module}.{action}", - "task_id": self.current_task.get("task_id") if self.current_task else None, - "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, - **self._current_run_identity_payload(), - "status": "cached" if not self.node_cache[id]._has_changed else "succeeded", - "phase": node_execution_phase(module, action), - "progress": 100, - "current_node": id, - "hasChanged": self.node_cache[id]._has_changed, - "executionTime": self.node_cache[id]._execution_time, - "memoryUsage": self.node_cache[id]._memory_usage - }, sid) + self.queue_message( + { + "type": "executed", + "node": id, + "name": f"{module}.{action}", + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, + **self._current_run_identity_payload(), + "status": "cached" if not self.node_cache[id]._has_changed else "succeeded", + "phase": node_execution_phase(module, action), + "progress": 100, + "current_node": id, + "hasChanged": self.node_cache[id]._has_changed, + "executionTime": self.node_cache[id]._execution_time, + "memoryUsage": self.node_cache[id]._memory_usage, + } + ) for ui_key, data_key in ui_fields.items(): message = None + display = self.modules[module][action]["params"][ui_key].get("display") # skip for button and group fields - if self.modules[module][action]['params'][ui_key].get('display') in ['ui_button', 'ui_group']: + if display in ["ui_button", "ui_group"]: continue else: @@ -4008,16 +8057,18 @@ def execute_node(self, id, node, sid, quiet=False): else: continue - data_type = self.modules[module][action]['params'][data_key].get('type') # data type of the source field - data_format = self.modules[module][action]['params'][ui_key].get('type', 'text') # format of the returned value: text, raw, url - fieldOptions = self.modules[module][action]['params'][ui_key].get('fieldOptions', {}) + data_type = self.modules[module][action]["params"][data_key].get("type") # data type of the source field + data_format = self.modules[module][action]["params"][ui_key].get( + "type", "text" + ) # format of the returned value: text, raw, url + fieldOptions = self.modules[module][action]["params"][ui_key].get("fieldOptions", {}) source_value = source_value if isinstance(source_value, list) else [source_value] artifacts = None - if data_format == 'url': + if data_format == "url": if is_image_data_type(data_type): - image_format = fieldOptions.get('format', 'WEBP') - image_quality = fieldOptions.get('quality', 100) + image_format = fieldOptions.get("format", "WEBP") + image_quality = fieldOptions.get("quality", 100) data_value = [] artifacts = [] task_id = self.current_task.get("task_id") if self.current_task else None @@ -4035,56 +8086,98 @@ def execute_node(self, id, node, sid, quiet=False): f"&t={time.time()}" ) data_value.append(url) - artifacts.append(attach_run_identity_to_artifact( - cache_image_artifact(id, data_key, i, url, source_value[i], image_format), - task_id=task_id, - attempt_index=attempt_index, - runtime_hints=runtime_hints, - )) + artifacts.append( + attach_run_identity_to_artifact( + cache_image_artifact(id, data_key, i, url, source_value[i], image_format), + task_id=task_id, + attempt_index=attempt_index, + runtime_hints=runtime_hints, + ) + ) + elif display in {"ui_audio", "ui_video"}: + data_value = [] + artifacts = [] + for i, item in enumerate(source_value): + if item is None: + continue + file_url = file_backed_media_preview(item) + url = file_url or f"/cache/{id}/{data_key}/{i}?t={time.time()}" + data_value.append(url) + if file_url: + filename = os.fspath(item) + mime_type, _ = mimetypes.guess_type(filename) + artifacts.append( + { + "url": url, + "nodeId": id, + "fieldKey": data_key, + "index": i, + "mimeType": mime_type, + "filename": filename, + "source": "file", + } + ) + else: + artifacts.append( + { + "url": url, + "nodeId": id, + "fieldKey": data_key, + "index": i, + "source": "cache", + } + ) else: - data_value = [f"/cache/{id}/{data_key}/{i}?t={time.time()}" - for i in range(len(source_value)) if source_value[i] is not None] - elif data_format == 'raw': + data_value = [ + f"/cache/{id}/{data_key}/{i}?t={time.time()}" + for i in range(len(source_value)) + if source_value[i] is not None + ] + elif data_format == "raw": data_value = [to_bytes(data_type, item, fieldOptions) for item in source_value if item is not None] else: - data_value = [to_base64(data_type, item, fieldOptions) for item in source_value if item is not None] + data_value = [to_base64(data_type, item, fieldOptions) for item in source_value if item is not None] message = { - 'client_id': sid, - 'type': 'update_value', - 'node': id, - 'key': ui_key, - 'data_type': data_type, - 'value': data_value, - 'task_id': self.current_task.get("task_id") if self.current_task else None, - 'attempt_index': self.current_task.get("attempt_index") if self.current_task else None, + "client_id": sid, + "type": "update_value", + "node": id, + "key": ui_key, + "data_type": data_type, + "value": data_value, + "task_id": self.current_task.get("task_id") if self.current_task else None, + "attempt_index": self.current_task.get("attempt_index") if self.current_task else None, **self._current_run_identity_payload(), - 'runtimeFingerprint': self.current_task.get("runtimeFingerprint") if self.current_task else None, + "runtimeFingerprint": self.current_task.get("runtimeFingerprint") if self.current_task else None, } if artifacts is not None: - message['artifacts'] = artifacts + message["artifacts"] = artifacts if message: - self.queue_message(message, sid) - + ui_field_hidden = bool(self.modules[module][action]["params"][ui_key].get("hidden")) + if not ui_field_hidden and not (module == "modules.Audio" and action == "Load"): + output_id, backend_persisted = self._persist_generated_output_update(message, display=display) + if output_id: + message["output_id"] = output_id + message["backend_persisted"] = backend_persisted + self.queue_message(message) def trigger_node(self, source_id, output, sid): if not self.current_task: return - graph = self.current_task['args'][0] - nodes = graph['nodes'] + graph = self.current_task["args"][0] + nodes = graph["nodes"] for id in nodes: - params = nodes[id]['params'] + params = nodes[id]["params"] for p in params: - data_source_id = params[p].get('sourceId') - data_param_key = params[p].get('sourceKey') + data_source_id = params[p].get("sourceId") + data_param_key = params[p].get("sourceKey") if data_source_id == source_id and data_param_key == output: self.execute_node(id, nodes[id], sid, quiet=True) - """ ╭────────────────╮ Hugging Face @@ -4092,187 +8185,533 @@ def trigger_node(self, source_id, output, sid): """ async def hf_cache(self, request): - refresh = request.query.get('refresh', False) - id = request.match_info.get('id', None) - class_name = request.query.get('className', None) - compact = request.query.get('compact', False) - return_type = "compact" if compact else "full" - + refresh = request.query.get("refresh", False) + id = request.match_info.get("id", None) + class_name = request.query.get("className", None) + compact = request.query.get("compact", False) if refresh: modelstore.update_hf() - models = modelstore.get_hf_models(id, class_name, return_type) + models = modelstore.get_hf_models(id, class_name, "full") + annotated = [] + for model in models: + status = artifact_cache_status(model.get("id"), models) + entry = dict(model) + entry.update( + { + "cached": bool(status.get("installed")), + "installed": bool(status.get("complete")), + "complete": bool(status.get("complete")), + "repair_required": bool(status.get("repairRequired")), + "install_reason": status.get("reason"), + "active_files": status.get("activeFiles") or [], + "missing_files": status.get("missingFiles") or [], + "corrupt_files": status.get("corruptFiles") or [], + } + ) + if compact: + entry = { + key: entry[key] + for key in ( + "id", + "class_names", + "cached", + "installed", + "complete", + "repair_required", + "install_reason", + "active_files", + "missing_files", + "corrupt_files", + ) + } + annotated.append(entry) - return web.json_response(models) + return web.json_response(annotated) def _package_status(self, module_name, distribution_name=None): package = { - 'available': False, - 'module': module_name, - 'distribution': distribution_name or module_name, + "available": False, + "module": module_name, + "distribution": distribution_name or module_name, } try: - package['version'] = metadata.version(distribution_name or module_name) + package["version"] = metadata.version(distribution_name or module_name) except Exception: pass try: module = import_module(module_name) - package['available'] = True - package['version'] = getattr(module, '__version__', package.get('version')) + package["available"] = True + package["version"] = getattr(module, "__version__", package.get("version")) except Exception as e: - package['error'] = str(e) + package["error"] = str(e) return package + def _runtime_fingerprint_for_control_request(self): + """Avoid entering accelerator APIs while a model call owns the runtime.""" + if self.current_task and isinstance(self._last_runtime_fingerprint, dict): + return deepcopy(self._last_runtime_fingerprint) + return self._runtime_fingerprint() + async def system_stats(self, _request): + if self.current_task and isinstance(self._last_runtime_fingerprint, dict): + cached_hardware = self._last_runtime_fingerprint.get("hardware") + if isinstance(cached_hardware, dict): + return web.json_response(deepcopy(cached_hardware)) return web.json_response(get_hardware_snapshot(self.data_dir)) async def runtime_status(self, request): - hardware = get_hardware_snapshot(self.data_dir) + runtime_fingerprint = self._runtime_fingerprint_for_control_request() + hardware = runtime_fingerprint.get("hardware") + if not isinstance(hardware, dict): + hardware = get_hardware_snapshot(self.data_dir, refresh=True) + profile = runtime_profile(hardware, venv=Path(sys.prefix)) packages = { - 'aiohttp': self._package_status('aiohttp'), - 'aiohttp_cors': self._package_status('aiohttp_cors', 'aiohttp-cors'), - 'torch': self._package_status('torch'), - 'diffusers': self._package_status('diffusers'), - 'transformers': self._package_status('transformers'), - 'huggingface_hub': self._package_status('huggingface_hub', 'huggingface-hub'), - 'accelerate': self._package_status('accelerate'), - 'safetensors': self._package_status('safetensors'), + "aiohttp": self._package_status("aiohttp"), + "aiohttp_cors": self._package_status("aiohttp_cors", "aiohttp-cors"), + "torch": self._package_status("torch"), + "diffusers": self._package_status("diffusers"), + "transformers": self._package_status("transformers"), + "huggingface_hub": self._package_status("huggingface_hub", "huggingface-hub"), + "accelerate": self._package_status("accelerate"), + "safetensors": self._package_status("safetensors"), } - packages['torch'].update(legacy_torch_status(hardware)) - required = ['aiohttp', 'aiohttp_cors', 'torch', 'diffusers', 'huggingface_hub'] - missing_required = [name for name in required if not packages.get(name, {}).get('available')] + packages["torch"].update(legacy_torch_status(hardware)) + required = ["aiohttp", "aiohttp_cors", "torch", "diffusers", "huggingface_hub"] + missing_required = [name for name in required if not packages.get(name, {}).get("available")] current_task = None if self.current_task: current_task = { - 'task_id': self.current_task.get('task_id'), - 'name': self.current_task.get('name'), - 'sid': self.current_task.get('sid'), - 'started_at': self.current_task.get('started_at'), - 'progress': self.current_task.get('progress'), + "task_id": self.current_task.get("task_id"), + "name": self.current_task.get("name"), + "sid": self.current_task.get("sid"), + "started_at": self.current_task.get("started_at"), + "progress": self.current_task.get("progress"), } - return web.json_response({ - 'error': False, - 'ready': len(missing_required) == 0, - 'instance': self.instance, - 'server': { - 'host': self.host, - 'port': self.port, - 'scheme': 'https' if self.ssl_context else 'http', - 'work_dir': self.work_dir, - 'data_dir': self.data_dir, - 'client_max_size': self.client_max_size, - }, - 'python': { - 'version': sys.version, - 'executable': sys.executable, - 'platform': platform.platform(), - 'cwd': os.getcwd(), - }, - 'config': { - 'hf_cache_dir': CONFIG.hf.get('cache_dir'), - 'hf_online_status': CONFIG.hf.get('online_status'), - 'hf_token_configured': bool(CONFIG.hf.get('token')), - 'pytorch_cuda_alloc_conf': os.environ.get('PYTORCH_CUDA_ALLOC_CONF'), - 'paths': CONFIG.paths, - }, - 'packages': packages, - 'hardware': hardware, - 'missing_required_packages': missing_required, - 'modules': { - 'registered_count': len(self.modules), - 'module_map_count': len(MODULE_MAP), - }, - 'queue': { - 'current': current_task, - 'queued_count': len(self.queued_tasks), - 'main_queue_size': self.main_queue.qsize(), - 'background_queue_size': self.background_queue.qsize(), - 'interrupt_requested': self.interrupt_flag, + ready = len(missing_required) == 0 and bool(profile.get("execution_ready")) + return web.json_response( + { + "error": False, + "ready": ready, + "runtime_fingerprint": ( + runtime_fingerprint.get("resourceFingerprint") or runtime_fingerprint.get("fingerprint") + ), + "runtime_profile": profile, + "instance": self.instance, + "server": { + "host": self.host, + "port": self.port, + "scheme": "https" if self.ssl_context else "http", + "work_dir": self.work_dir, + "data_dir": self.data_dir, + "client_max_size": self.client_max_size, + }, + "python": { + "version": sys.version, + "executable": sys.executable, + "platform": platform.platform(), + "cwd": os.getcwd(), + }, + "config": { + "hf_cache_dir": CONFIG.hf.get("cache_dir"), + "hf_online_status": CONFIG.hf.get("online_status"), + "hf_token_configured": bool(CONFIG.hf.get("token")), + "pytorch_cuda_alloc_conf": os.environ.get("PYTORCH_CUDA_ALLOC_CONF"), + "paths": CONFIG.paths, + }, + "packages": packages, + "hardware": hardware, + "missing_required_packages": missing_required, + "modules": { + "registered_count": len(self.modules), + "module_map_count": len(MODULE_MAP), + }, + "queue": { + "current": current_task, + "queued_count": len(self.queued_tasks), + "main_queue_size": self.main_queue.qsize(), + "background_queue_size": self.background_queue.qsize(), + "interrupt_requested": self.interrupt_flag, + }, + } + ) + + def _optimization_runtime_context(self): + runtime_fingerprint = self._runtime_fingerprint_for_control_request() + hardware = runtime_fingerprint.get("hardware") + if not isinstance(hardware, dict): + hardware = get_hardware_snapshot(self.data_dir, refresh=not bool(self.current_task)) + return runtime_fingerprint, hardware, runtime_profile(hardware, venv=Path(sys.prefix)) + + def _persist_optimization_job(self, job): + try: + job_dir = Path(self.data_dir) / "runtime" / "optimization-jobs" + job_dir.mkdir(parents=True, exist_ok=True) + path = job_dir / f"{job.get('id')}.json" + temporary = path.with_suffix(".json.tmp") + temporary.write_text(json.dumps(job, indent=2, default=str) + "\n", encoding="utf-8") + temporary.replace(path) + except Exception: + logger.debug("Could not persist optional-runtime installation job", exc_info=True) + + def _update_optimization_job(self, job_id, **updates): + job = self.optimization_jobs.get(job_id) + if not isinstance(job, dict): + return + job.update(updates) + job["updatedAt"] = time.time() + self._persist_optimization_job(job) + + async def runtime_optimizations(self, _request): + _fingerprint, hardware, profile = self._optimization_runtime_context() + return web.json_response(public_optimization_catalog(runtime_profile=profile, hardware=hardware)) + + async def _run_optimization_install_job(self, job_id, capability_id, profile, hardware): + loop = asyncio.get_running_loop() + + def progress(update): + loop.call_soon_threadsafe( + self._update_optimization_job, + job_id, + status="running", + progress=update, + ) + + try: + result = await asyncio.to_thread( + install_optimization_capability, + capability_id, + runtime_profile=profile, + hardware=hardware, + progress=progress, + ) + self._update_optimization_job( + job_id, + status="ready", + progress={ + "phase": "ready", + "message": "Validation passed. Activate to restart MoDiff with this optional environment.", + "updatedAt": time.time(), + }, + result=result, + ) + except Exception as exc: + logger.warning("Optional runtime package installation failed: %s", exc) + self._update_optimization_job( + job_id, + status="failed", + progress={ + "phase": "failed", + "message": str(exc), + "updatedAt": time.time(), + }, + error=str(exc), + ) + + async def runtime_optimization_install(self, request): + if self.current_task or self.queued_tasks: + return web.json_response( + { + "error": True, + "error_code": "optimization_install_busy", + "message": "Finish or stop active and queued runs before changing optional runtime packages.", + }, + status=409, + ) + try: + body = await request.json() + except Exception: + body = {} + capability_id = str(body.get("capabilityId") or "").strip() + if not capability_id: + return web.json_response({"error": True, "message": "capabilityId is required."}, status=400) + _fingerprint, hardware, profile = self._optimization_runtime_context() + job_id = f"optjob-{nanoid.generate(size=12)}" + job = { + "id": job_id, + "capabilityId": capability_id, + "status": "queued", + "progress": { + "phase": "queued", + "message": "Waiting to stage the optional package.", + "updatedAt": time.time(), }, - }) + "createdAt": time.time(), + "updatedAt": time.time(), + } + self.optimization_jobs[job_id] = job + self._persist_optimization_job(job) + asyncio.create_task(self._run_optimization_install_job(job_id, capability_id, profile, hardware)) + return web.json_response({"error": False, "job": job}, status=202) + + async def runtime_optimization_job(self, request): + job_id = str(request.match_info.get("job_id") or "") + job = self.optimization_jobs.get(job_id) + if not isinstance(job, dict): + path = Path(self.data_dir) / "runtime" / "optimization-jobs" / f"{job_id}.json" + try: + value = json.loads(path.read_text(encoding="utf-8")) + job = value if isinstance(value, dict) else None + except (OSError, TypeError, ValueError): + job = None + if not job: + return web.json_response( + {"error": True, "message": "Optimization installation job not found."}, status=404 + ) + return web.json_response({"error": False, "job": job}) + + def _schedule_optional_runtime_restart(self): + if os.environ.get("MODIFF_WORKER_SUPERVISED") != "1": + return False + + def restart_worker(): + os._exit(SUPERVISED_RESTART_EXIT_CODE) + + timer = threading.Timer(0.75, restart_worker) + timer.daemon = True + timer.start() + return True + + async def runtime_optimization_activate(self, request): + if self.current_task or self.queued_tasks: + return web.json_response( + { + "error": True, + "error_code": "optimization_activation_busy", + "message": "Finish or stop active and queued runs before activating an optional runtime.", + }, + status=409, + ) + try: + body = await request.json() + result = activate_optimization_environment(str(body.get("environmentId") or "")) + except (ValueError, RuntimeError) as exc: + return web.json_response({"error": True, "message": str(exc)}, status=400) + restarting = bool(result.get("restartRequired")) and self._schedule_optional_runtime_restart() + return web.json_response( + { + "error": False, + **result, + "restarting": restarting, + "message": ( + "The validated optional runtime is active. MoDiff is restarting." + if restarting + else "The validated optional runtime is active. Restart MoDiff to load it." + if result.get("restartRequired") + else "This optional runtime is already active." + ), + } + ) + + async def runtime_optimization_rollback(self, _request): + if self.current_task or self.queued_tasks: + return web.json_response( + { + "error": True, + "error_code": "optimization_rollback_busy", + "message": "Finish or stop active and queued runs before rolling back the optional runtime.", + }, + status=409, + ) + try: + result = rollback_optimization_environment() + except RuntimeError as exc: + return web.json_response({"error": True, "message": str(exc)}, status=400) + restarting = bool(result.get("restartRequired")) and self._schedule_optional_runtime_restart() + return web.json_response( + { + "error": False, + **result, + "restarting": restarting, + "message": ( + "The previous optional runtime is restored. MoDiff is restarting." + if restarting + else "The previous optional runtime is selected. Restart MoDiff to finish rollback." + ), + } + ) + + async def runtime_optimization_enable(self, request): + try: + body = await request.json() + capability_id = str(body.get("capabilityId") or "") + enabled = body.get("enabled") is True + _fingerprint, hardware, profile = self._optimization_runtime_context() + catalog = public_optimization_catalog(runtime_profile=profile, hardware=hardware) + capability = next( + (item for item in catalog.get("capabilities", []) if item.get("id") == capability_id), + None, + ) + if not capability: + raise ValueError("Unknown optimization capability.") + if enabled and not capability.get("compatible"): + raise ValueError(capability.get("disabledReason") or "This optimization is incompatible.") + if enabled and not capability.get("canEnable"): + raise ValueError(capability.get("disabledReason") or "This optimization is not available to enable.") + if ( + enabled + and capability.get("kind") in {"package", "profile", "external"} + and not capability.get("installed") + ): + raise ValueError("Install and validate this package before enabling it.") + state = set_optimization_capability_enabled(capability_id, enabled) + except (ValueError, RuntimeError) as exc: + return web.json_response({"error": True, "message": str(exc)}, status=400) + return web.json_response( + { + "error": False, + "state": state, + "message": ( + "Opt-in enabled. Auto will still require an exact qualified workload receipt." + if enabled + else "Opt-in disabled. Auto will not select this optimization." + ), + } + ) + + async def runtime_optimization_probe(self, request): + if self.current_task: + return web.json_response( + { + "error": True, + "message": "Compatibility probes cannot run while a graph owns the accelerator.", + }, + status=409, + ) + try: + body = await request.json() + capability_id = str(body.get("capabilityId") or "") + runtime_fingerprint, hardware, profile = self._optimization_runtime_context() + catalog = public_optimization_catalog(runtime_profile=profile, hardware=hardware) + capability = next( + (item for item in catalog.get("capabilities", []) if item.get("id") == capability_id), + None, + ) + if not capability: + raise ValueError("Unknown optimization capability.") + if not capability.get("canEnable"): + raise ValueError(capability.get("disabledReason") or "This optimization is unavailable.") + receipt = await asyncio.to_thread( + probe_optimization_capability, + capability_id, + runtime_fingerprint=runtime_fingerprint, + ) + except (ValueError, RuntimeError) as exc: + return web.json_response({"error": True, "message": str(exc)}, status=400) + return web.json_response( + { + "error": False, + "receipt": receipt, + "message": ( + "Compatibility probe passed. This does not authorize Auto until a real workload is qualified." + if receipt.get("status") == "probe_passed" + else "Compatibility probe failed. The optimization remains unavailable to Auto." + ), + } + ) + + async def runtime_optimization_receipts(self, _request): + return web.json_response(read_optimization_receipts()) + + async def runtime_optimization_qualify(self, request): + try: + body = await request.json() + receipt = qualify_optimization_receipt( + str(body.get("receiptId") or ""), + output_reviewed=body.get("outputReviewed") is True, + ) + except (ValueError, RuntimeError) as exc: + return web.json_response({"error": True, "message": str(exc)}, status=400) + return web.json_response( + { + "error": False, + "receipt": receipt, + "message": "This exact runtime, model, workload, and optimization selection is now eligible for Auto.", + } + ) def _cuda_memory_snapshot(self): snapshot = { - 'available': False, - 'device_count': 0, - 'devices': [], + "available": False, + "device_count": 0, + "devices": [], } try: - torch = import_module('torch') + torch = import_module("torch") cuda_available = bool(torch.cuda.is_available()) - snapshot['available'] = cuda_available + snapshot["available"] = cuda_available if cuda_available: device_count = int(torch.cuda.device_count()) - snapshot['device_count'] = device_count + snapshot["device_count"] = device_count devices = [] for index in range(device_count): device = { - 'index': index, - 'name': torch.cuda.get_device_name(index), + "index": index, + "name": torch.cuda.get_device_name(index), } try: properties = torch.cuda.get_device_properties(index) - device['total_memory'] = int(getattr(properties, 'total_memory', 0)) + device["total_memory"] = int(getattr(properties, "total_memory", 0)) except Exception as properties_error: - device['properties_error'] = str(properties_error) + device["properties_error"] = str(properties_error) try: try: free_bytes, total_bytes = torch.cuda.mem_get_info(index) except TypeError: with torch.cuda.device(index): free_bytes, total_bytes = torch.cuda.mem_get_info() - device['free_bytes'] = int(free_bytes) - device['total_bytes'] = int(total_bytes) + device["free_bytes"] = int(free_bytes) + device["total_bytes"] = int(total_bytes) except Exception as memory_error: - device['mem_get_info_error'] = str(memory_error) + device["mem_get_info_error"] = str(memory_error) try: - device['allocated_bytes'] = int(torch.cuda.memory_allocated(index)) - device['reserved_bytes'] = int(torch.cuda.memory_reserved(index)) - device['max_allocated_bytes'] = int(torch.cuda.max_memory_allocated(index)) - device['max_reserved_bytes'] = int(torch.cuda.max_memory_reserved(index)) + device["allocated_bytes"] = int(torch.cuda.memory_allocated(index)) + device["reserved_bytes"] = int(torch.cuda.memory_reserved(index)) + device["max_allocated_bytes"] = int(torch.cuda.max_memory_allocated(index)) + device["max_reserved_bytes"] = int(torch.cuda.max_memory_reserved(index)) except Exception as stats_error: - device['memory_stats_error'] = str(stats_error) + device["memory_stats_error"] = str(stats_error) devices.append(device) - snapshot['devices'] = devices + snapshot["devices"] = devices if devices: first_device = devices[0] - snapshot['free_bytes'] = first_device.get('free_bytes') - snapshot['total_bytes'] = first_device.get('total_bytes') - snapshot['allocated_bytes'] = first_device.get('allocated_bytes') - snapshot['reserved_bytes'] = first_device.get('reserved_bytes') - snapshot['device_name'] = first_device.get('name') + snapshot["free_bytes"] = first_device.get("free_bytes") + snapshot["total_bytes"] = first_device.get("total_bytes") + snapshot["allocated_bytes"] = first_device.get("allocated_bytes") + snapshot["reserved_bytes"] = first_device.get("reserved_bytes") + snapshot["device_name"] = first_device.get("name") except Exception as e: - snapshot['error'] = str(e) + snapshot["error"] = str(e) return snapshot def _gpu_process_snapshot(self): - nvidia_smi = shutil.which('nvidia-smi') + nvidia_smi = shutil.which("nvidia-smi") if not nvidia_smi: return { - 'available': False, - 'reason': 'nvidia-smi not found', - 'gpus': [], - 'processes': [], + "available": False, + "reason": "nvidia-smi not found", + "gpus": [], + "processes": [], } snapshot = { - 'available': True, - 'gpus': [], - 'processes': [], + "available": True, + "gpus": [], + "processes": [], } try: gpu_result = subprocess.run( [ nvidia_smi, - '--query-gpu=index,name,memory.used,memory.free,memory.total', - '--format=csv,noheader,nounits', + "--query-gpu=index,name,memory.used,memory.free,memory.total", + "--format=csv,noheader,nounits", ], capture_output=True, text=True, @@ -4283,113 +8722,482 @@ def _gpu_process_snapshot(self): if len(row) < 5: continue index, name, used_mb, free_mb, total_mb = [item.strip() for item in row[:5]] - snapshot['gpus'].append({ - 'index': int(index) if index.isdigit() else index, - 'name': name, - 'memory_used_mb': self._safe_int(used_mb), - 'memory_free_mb': self._safe_int(free_mb), - 'memory_total_mb': self._safe_int(total_mb), - }) + snapshot["gpus"].append( + { + "index": int(index) if index.isdigit() else index, + "name": name, + "memory_used_mb": self._safe_int(used_mb), + "memory_free_mb": self._safe_int(free_mb), + "memory_total_mb": self._safe_int(total_mb), + } + ) else: - snapshot['gpu_query_error'] = gpu_result.stderr.strip() or gpu_result.stdout.strip() + snapshot["gpu_query_error"] = gpu_result.stderr.strip() or gpu_result.stdout.strip() except Exception as e: - snapshot['gpu_query_error'] = str(e) + snapshot["gpu_query_error"] = str(e) try: process_result = subprocess.run( [ nvidia_smi, - '--query-compute-apps=gpu_uuid,pid,process_name,used_memory', - '--format=csv,noheader,nounits', + "--query-compute-apps=gpu_uuid,pid,process_name,used_memory", + "--format=csv,noheader,nounits", ], capture_output=True, text=True, timeout=4, ) - if process_result.returncode == 0: - for row in csv.reader(process_result.stdout.splitlines()): - if len(row) < 4: - continue - gpu_uuid, pid, process_name, used_memory_mb = [item.strip() for item in row[:4]] - snapshot['processes'].append({ - 'gpu_uuid': gpu_uuid, - 'pid': self._safe_int(pid), - 'process_name': process_name, - 'used_memory_mb': self._safe_int(used_memory_mb), - }) - else: - snapshot['process_query_error'] = process_result.stderr.strip() or process_result.stdout.strip() - except Exception as e: - snapshot['process_query_error'] = str(e) + if process_result.returncode == 0: + for row in csv.reader(process_result.stdout.splitlines()): + if len(row) < 4: + continue + gpu_uuid, pid, process_name, used_memory_mb = [item.strip() for item in row[:4]] + snapshot["processes"].append( + { + "gpu_uuid": gpu_uuid, + "pid": self._safe_int(pid), + "process_name": process_name, + "used_memory_mb": self._safe_int(used_memory_mb), + } + ) + else: + snapshot["process_query_error"] = process_result.stderr.strip() or process_result.stdout.strip() + except Exception as e: + snapshot["process_query_error"] = str(e) + + return snapshot + + def _safe_int(self, value): + try: + return int(str(value).strip()) + except Exception: + return None + + def _storage_kind_for_path(self, path, *, sys_dev_root=Path("/sys/dev/block")): + """Return a storage kind only when Linux exposes an authoritative rotational flag.""" + if platform.system() != "Linux": + return "unknown", None + candidate = Path(path).expanduser() + while not candidate.exists() and candidate != candidate.parent: + candidate = candidate.parent + try: + device_number = candidate.stat().st_dev + device_link = Path(sys_dev_root) / f"{os.major(device_number)}:{os.minor(device_number)}" + device_path = device_link.resolve(strict=True) + except (OSError, RuntimeError): + return "unknown", None + + for device_or_parent in (device_path, *device_path.parents): + rotational_path = device_or_parent / "queue" / "rotational" + try: + rotational = rotational_path.read_text(encoding="utf-8").strip() + except OSError: + continue + if rotational == "0": + return "ssd", "linux-sysfs" + if rotational == "1": + return "hdd", "linux-sysfs" + return "unknown", None + return "unknown", None + + def _runtime_storage_snapshot(self): + path = Path(self.data_dir).expanduser() + existing_path = path + while not existing_path.exists() and existing_path != existing_path.parent: + existing_path = existing_path.parent + kind, detection_source = self._storage_kind_for_path(existing_path) + active_percent, activity_source = self._runtime_disk_activity_sampler.sample(existing_path) + usage = shutil.disk_usage(existing_path) + total = int(usage.total) + free = int(usage.free) + used = int(usage.used) + return { + "path": str(existing_path.resolve()), + "totalBytes": total, + "freeBytes": free, + "usedBytes": used, + "percent": (used / total * 100.0) if total > 0 else None, + "activePercent": active_percent, + "activitySource": activity_source, + "kind": kind, + "detectionSource": detection_source, + } + + async def runtime_gpu_processes(self, request): + return web.json_response( + { + "error": False, + "cuda_memory_snapshot": self._cuda_memory_snapshot(), + "gpu_processes": self._gpu_process_snapshot(), + } + ) + + def _runtime_resource_snapshot(self, *, max_age_seconds=1.0): + with self._runtime_resource_lock: + now = time.monotonic() + if isinstance( + self._runtime_resource_cached_snapshot, dict + ) and now - self._runtime_resource_cached_at <= max(0.0, float(max_age_seconds)): + return deepcopy(self._runtime_resource_cached_snapshot) + snapshot = self._collect_runtime_resource_snapshot() + self._runtime_resource_cached_at = now + self._runtime_resource_cached_snapshot = snapshot + return deepcopy(snapshot) + + def _collect_runtime_resource_snapshot(self): + sampled_at = time.time() + system = { + "cpuPercent": None, + "ramTotalBytes": None, + "ramAvailableBytes": None, + "ramUsedBytes": None, + "ramPercent": None, + } + process = { + "cpuPercent": None, + "rssBytes": None, + } + errors = [] + storage = { + "path": None, + "totalBytes": None, + "freeBytes": None, + "usedBytes": None, + "percent": None, + "activePercent": None, + "activitySource": None, + "kind": "unknown", + "detectionSource": None, + } + + try: + storage = self._runtime_storage_snapshot() + except Exception as exc: + errors.append(f"storage telemetry: {exc}") + + try: + import psutil + + memory = psutil.virtual_memory() + system.update( + { + "cpuPercent": float(psutil.cpu_percent(interval=None)), + "ramTotalBytes": int(memory.total), + "ramAvailableBytes": int(memory.available), + "ramUsedBytes": int(memory.total - memory.available), + "ramPercent": float(memory.percent), + } + ) + current_process = self._runtime_resource_process or psutil.Process() + self._runtime_resource_process = current_process + process.update( + { + "cpuPercent": float(current_process.cpu_percent(interval=None)), + "rssBytes": int(current_process.memory_info().rss), + } + ) + except Exception as exc: + errors.append(f"process telemetry: {exc}") - return snapshot + cuda_snapshot = self._cuda_memory_snapshot() + accelerators = [] + try: + torch = import_module("torch") + hardware_snapshot = get_hardware_snapshot(self.data_dir) + topology_by_device = { + str(item.get("device")): item + for item in (hardware_snapshot.get("devices") or []) + if isinstance(item, dict) and item.get("device") + } + hip_version = getattr(getattr(torch, "version", None), "hip", None) + runtime_backend = "rocm" if hip_version else "cuda" + ram_total = system.get("ramTotalBytes") + for raw_device in cuda_snapshot.get("devices") or []: + device_name = f"cuda:{raw_device.get('index', len(accelerators))}" + topology = topology_by_device.get(device_name, {}) + total = self._safe_int(raw_device.get("total_bytes") or raw_device.get("total_memory")) + free = self._safe_int(raw_device.get("free_bytes")) + allocated = self._safe_int(raw_device.get("allocated_bytes")) + reserved = self._safe_int(raw_device.get("reserved_bytes")) + used = max(0, total - free) if total is not None and free is not None else reserved + shared = topology.get("memory_kind") == "shared" or bool( + hip_version and total is not None and ram_total is not None and total >= int(ram_total * 0.75) + ) + planning_total = self._safe_int(topology.get("planning_memory_total")) or total + planning_free = self._safe_int(topology.get("planning_memory_free")) + if planning_free is None: + planning_free = free + accelerators.append( + { + "device": device_name, + "index": raw_device.get("index", len(accelerators)), + "name": raw_device.get("name") or raw_device.get("device_name") or runtime_backend.upper(), + "backend": runtime_backend, + "vendor": topology.get("vendor") or ("amd" if hip_version else "nvidia"), + "architecture": topology.get("architecture"), + "memoryKind": "shared" if shared else "dedicated", + "utilizationPercent": None, + "utilizationSource": None, + "memoryTotalBytes": planning_total, + "memoryFreeBytes": planning_free, + "memoryUsedBytes": ( + max(0, planning_total - planning_free) + if planning_total is not None and planning_free is not None + else used + ), + "accessibleMemoryTotalBytes": total, + "accessibleMemoryFreeBytes": free, + "dedicatedMemoryTotalBytes": self._safe_int(topology.get("dedicated_memory_total")), + "dedicatedMemoryFreeBytes": self._safe_int(topology.get("dedicated_memory_free")), + "sharedMemoryTotalBytes": self._safe_int(topology.get("shared_memory_total")), + "sharedMemoryFreeBytes": self._safe_int(topology.get("shared_memory_free")), + "allocatedBytes": allocated, + "reservedBytes": reserved, + "peakAllocatedBytes": self._safe_int(raw_device.get("max_allocated_bytes")), + "peakReservedBytes": self._safe_int(raw_device.get("max_reserved_bytes")), + } + ) - def _safe_int(self, value): + xpu = getattr(torch, "xpu", None) + if not accelerators and xpu is not None and bool(getattr(xpu, "is_available", lambda: False)()): + for index in range(int(xpu.device_count())): + device_name = f"xpu:{index}" + topology = topology_by_device.get(device_name, {}) + total = free = allocated = reserved = None + try: + properties = xpu.get_device_properties(index) + total = self._safe_int(getattr(properties, "total_memory", None)) + except Exception: + pass + try: + free, runtime_total = xpu.mem_get_info(index) + free = self._safe_int(free) + total = total or self._safe_int(runtime_total) + except Exception: + pass + try: + allocated = self._safe_int(xpu.memory_allocated(index)) + reserved = self._safe_int(xpu.memory_reserved(index)) + except Exception: + pass + accelerators.append( + { + "device": device_name, + "index": index, + "name": str(getattr(xpu, "get_device_name", lambda _index: f"Intel XPU {index}")(index)), + "backend": "xpu", + "vendor": "intel", + "architecture": topology.get("architecture"), + "memoryKind": topology.get("memory_kind") or "dedicated", + "utilizationPercent": None, + "utilizationSource": None, + "memoryTotalBytes": self._safe_int(topology.get("planning_memory_total")) or total, + "memoryFreeBytes": self._safe_int(topology.get("planning_memory_free")) or free, + "accessibleMemoryTotalBytes": total, + "accessibleMemoryFreeBytes": free, + "dedicatedMemoryTotalBytes": self._safe_int(topology.get("dedicated_memory_total")), + "dedicatedMemoryFreeBytes": self._safe_int(topology.get("dedicated_memory_free")), + "sharedMemoryTotalBytes": self._safe_int(topology.get("shared_memory_total")), + "sharedMemoryFreeBytes": self._safe_int(topology.get("shared_memory_free")), + "memoryUsedBytes": max(0, total - free) + if total is not None and free is not None + else reserved, + "allocatedBytes": allocated, + "reservedBytes": reserved, + } + ) + + mps = getattr(torch, "mps", None) + if not accelerators and bool( + getattr(getattr(torch, "backends", None), "mps", None) and torch.backends.mps.is_available() + ): + allocated = None + driver_allocated = None + recommended_max = None + try: + allocated = self._safe_int(mps.current_allocated_memory()) + driver_allocated = self._safe_int(mps.driver_allocated_memory()) + recommended = getattr(mps, "recommended_max_memory", None) + recommended_max = self._safe_int(recommended()) if callable(recommended) else None + except Exception: + pass + accelerators.append( + { + "device": "mps:0", + "index": 0, + "name": "Apple Metal Performance Shaders", + "backend": "mps", + "vendor": "apple", + "architecture": platform.machine(), + "memoryKind": "shared", + "utilizationPercent": None, + "utilizationSource": None, + "memoryTotalBytes": recommended_max, + "memoryFreeBytes": ( + max(0, recommended_max - driver_allocated) + if recommended_max is not None and driver_allocated is not None + else None + ), + "memoryUsedBytes": driver_allocated, + "allocatedBytes": allocated, + "reservedBytes": driver_allocated, + } + ) + except Exception as exc: + errors.append(f"accelerator telemetry: {exc}") + + # Whole-device utilization is deliberately separate from Torch's + # allocator counters. Prefer low-overhead vendor sources and leave the + # value unavailable rather than inventing activity from allocation. try: - return int(str(value).strip()) - except Exception: - return None + if accelerators and accelerators[0].get("backend") == "cuda": + nvidia_smi = shutil.which("nvidia-smi") + if not nvidia_smi: + raise FileNotFoundError("nvidia-smi is unavailable") + result = subprocess.run( + [ + nvidia_smi, + "--query-gpu=index,utilization.gpu", + "--format=csv,noheader,nounits", + ], + capture_output=True, + text=True, + timeout=1.5, + ) + if result.returncode == 0: + for row in csv.reader(result.stdout.splitlines()): + if len(row) < 2: + continue + index = self._safe_int(row[0]) + utilization = self._safe_int(row[1]) + for accelerator in accelerators: + if accelerator.get("index") == index: + accelerator["utilizationPercent"] = utilization + accelerator["utilizationSource"] = "nvidia-smi" + elif accelerators and accelerators[0].get("backend") == "rocm": + amd_cards = [] + drm_root = Path("/sys/class/drm") + if drm_root.is_dir(): + for busy_path in sorted(drm_root.glob("card*/device/gpu_busy_percent")): + vendor_path = busy_path.parent / "vendor" + try: + if vendor_path.read_text(encoding="utf-8").strip().lower() != "0x1002": + continue + amd_cards.append(self._safe_int(busy_path.read_text(encoding="utf-8").strip())) + except Exception: + continue + for index, utilization in enumerate(amd_cards): + if index < len(accelerators): + accelerators[index]["utilizationPercent"] = utilization + accelerators[index]["utilizationSource"] = "sysfs" + except Exception as exc: + errors.append(f"device utilization: {exc}") - async def runtime_gpu_processes(self, request): - return web.json_response({ - 'error': False, - 'cuda_memory_snapshot': self._cuda_memory_snapshot(), - 'gpu_processes': self._gpu_process_snapshot(), - }) + active_device = None + if self.current_task: + runtime_hints = self.current_task.get("runtimeHints") + if isinstance(runtime_hints, dict): + active_device = runtime_hints.get("device") + if not active_device and accelerators: + active_device = accelerators[0].get("device") + + current_run = None + if self.current_task: + current_run = { + "taskId": self.current_task.get("task_id"), + "name": self.current_task.get("name"), + "startedAt": self.current_task.get("started_at"), + "progress": self.current_task.get("progress"), + } + + return { + "schemaVersion": 1, + "sampledAt": sampled_at, + "system": system, + "process": process, + "storage": storage, + "activeDevice": active_device, + "accelerators": accelerators, + "currentRun": current_run, + "errors": errors, + } + + async def runtime_resources(self, _request): + # psutil and vendor probes are blocking system calls. Keep them off the + # aiohttp loop so resource telemetry cannot delay graph or queue APIs. + return web.json_response(await asyncio.to_thread(self._runtime_resource_snapshot)) def _best_effort_device_cache_clear(self): errors = [] try: - torch = import_module('torch') + torch = import_module("torch") except Exception as e: - return [f'torch import failed: {e}'] + return [f"torch import failed: {e}"] if torch.cuda.is_available(): for label, callback in ( - ('torch.cuda.empty_cache', torch.cuda.empty_cache), - ('torch.cuda.ipc_collect', torch.cuda.ipc_collect), + ("torch.cuda.empty_cache", torch.cuda.empty_cache), + ("torch.cuda.ipc_collect", torch.cuda.ipc_collect), ): try: callback() except Exception as e: logger.debug(f"{label} failed during accelerator cleanup", exc_info=True) - errors.append(f'{label}: {e}') + errors.append(f"{label}: {e}") - mps = getattr(torch, 'mps', None) + mps = getattr(torch, "mps", None) if mps is not None: try: if mps.is_available(): mps.empty_cache() except Exception as e: logger.debug("torch.mps.empty_cache failed during accelerator cleanup", exc_info=True) - errors.append(f'torch.mps.empty_cache: {e}') + errors.append(f"torch.mps.empty_cache: {e}") return errors + def _best_effort_allocator_trim(self): + """Return freed glibc arenas to the OS after large unified-memory pipelines.""" + if platform.system() != "Linux": + return False, [] + try: + import ctypes + + libc = ctypes.CDLL(None) + malloc_trim = getattr(libc, "malloc_trim", None) + if malloc_trim is None: + return False, [] + malloc_trim.argtypes = [ctypes.c_size_t] + malloc_trim.restype = ctypes.c_int + return bool(malloc_trim(0)), [] + except Exception as e: + logger.debug("malloc_trim failed during accelerator cleanup", exc_info=True) + return False, [f"malloc_trim: {e}"] + def _release_modular_diffusers_components(self): try: - modular_diffusers = import_module('modules.ModularDiffusers') - manager = getattr(modular_diffusers, 'components', None) + modular_diffusers = import_module("modules.ModularDiffusers") + manager = getattr(modular_diffusers, "components", None) except Exception as e: - return 0, [f'Modular Diffusers components unavailable: {e}'] + return 0, [f"Modular Diffusers components unavailable: {e}"] if manager is None: return 0, [] - components_dict = getattr(manager, 'components', None) + components_dict = getattr(manager, "components", None) released_count = len(components_dict) if components_dict is not None else 0 errors = [] try: - torch = import_module('torch') + torch = import_module("torch") except Exception: torch = None - hooks = list(getattr(manager, 'model_hooks', None) or []) + hooks = list(getattr(manager, "model_hooks", None) or []) for hook in hooks: for label, callback in ( - ('offload', getattr(hook, 'offload', None)), - ('remove', getattr(hook, 'remove', None)), + ("offload", getattr(hook, "offload", None)), + ("remove", getattr(hook, "remove", None)), ): if callback is None: continue @@ -4397,56 +9205,73 @@ def _release_modular_diffusers_components(self): callback() except Exception as e: logger.debug(f"Modular Diffusers hook {label} failed during cleanup", exc_info=True) - errors.append(f'Modular Diffusers hook {label}: {e}') + errors.append(f"Modular Diffusers hook {label}: {e}") try: manager.model_hooks = None manager._auto_offload_enabled = False - if hasattr(manager, '_auto_offload_device'): + if hasattr(manager, "_auto_offload_device"): manager._auto_offload_device = None except Exception as e: - errors.append(f'Modular Diffusers offload reset: {e}') + errors.append(f"Modular Diffusers offload reset: {e}") if components_dict is not None: for component_id, component in list(components_dict.items()): try: if torch is not None and isinstance(component, torch.nn.Module): - component.to('cpu') + component.to("cpu") except Exception as e: logger.debug(f"Could not move Modular Diffusers component {component_id} to CPU", exc_info=True) - errors.append(f'Modular Diffusers component {component_id}: {e}') + errors.append(f"Modular Diffusers component {component_id}: {e}") try: components_dict.clear() except Exception as e: - errors.append(f'Modular Diffusers component clear: {e}') + errors.append(f"Modular Diffusers component clear: {e}") - for attr in ('added_time', 'collections'): + for attr in ("added_time", "collections"): try: value = getattr(manager, attr, None) if value is not None: value.clear() except Exception as e: - errors.append(f'Modular Diffusers {attr} clear: {e}') + errors.append(f"Modular Diffusers {attr} clear: {e}") return released_count, errors def _release_diffusers_offload_cache(self): - offload_path = Path('data') / 'offload' / 'diffusers' + offload_path = Path("data") / "offload" / "diffusers" if not offload_path.exists(): return 0, [] errors = [] released_count = 0 try: - released_count = sum(1 for item in offload_path.rglob('*') if item.is_file()) + released_count = sum(1 for item in offload_path.rglob("*") if item.is_file()) shutil.rmtree(offload_path) except Exception as e: logger.debug("Diffusers disk offload cache cleanup failed", exc_info=True) - errors.append(f'Diffusers disk offload cache: {e}') + errors.append(f"Diffusers disk offload cache: {e}") return released_count, errors async def runtime_gpu_cleanup(self, request): + # Clearing node_cache while a graph node is executing invalidates the + # executor's own node object. The node can finish its model call and + # then fail with a KeyError for its node id when execution metrics or + # outputs are recorded. Refuse cleanup while the worker owns a task; + # callers can retry once /queue reports no current task. + if self.current_task: + task_id = self.current_task.get("task_id") + return web.json_response( + { + "error": True, + "error_code": "runtime_cleanup_busy", + "message": "Accelerator cleanup cannot run while a task is active. Wait for the current task to finish, then retry.", + "task_id": task_id, + }, + status=409, + ) + before = self._cuda_memory_snapshot() cleanup_errors = [] released_nodes = len(self.node_cache) @@ -4454,22 +9279,29 @@ async def runtime_gpu_cleanup(self, request): released_diffusers_components = 0 released_offload_files = 0 - try: - self.node_cache.clear() - except Exception as e: - logger.debug("Failed to clear node cache during accelerator cleanup", exc_info=True) - cleanup_errors.append(f'node cache: {e}') - + # Drop memory-manager ownership before destroying cached nodes. Node + # destructors call MemoryManager.remove() for their tracked ids; if the + # manager still owns a large Accelerate-offloaded pipeline, remove() + # tries to materialize it on CPU and flushes once per id. On unified + # memory ROCm systems that turns cleanup into minutes of RAM/swap + # thrashing. With the manager detached first, those destructor calls + # are no-ops and the pipeline references are released exactly once. try: released_models = memory_manager.clear() except Exception as e: logger.debug("Failed to clear managed models during accelerator cleanup", exc_info=True) - cleanup_errors.append(f'memory manager: {e}') + cleanup_errors.append(f"memory manager: {e}") try: released_models = len(memory_manager.cache) memory_manager.cache.clear() except Exception as clear_error: - cleanup_errors.append(f'memory manager fallback: {clear_error}') + cleanup_errors.append(f"memory manager fallback: {clear_error}") + + try: + self.node_cache.clear() + except Exception as e: + logger.debug("Failed to clear node cache during accelerator cleanup", exc_info=True) + cleanup_errors.append(f"node cache: {e}") released_diffusers_components, diffusers_errors = self._release_modular_diffusers_components() cleanup_errors.extend(diffusers_errors) @@ -4480,51 +9312,265 @@ async def runtime_gpu_cleanup(self, request): try: gc.collect() except Exception as e: - cleanup_errors.append(f'gc.collect: {e}') + cleanup_errors.append(f"gc.collect: {e}") cleanup_errors.extend(self._best_effort_device_cache_clear()) + allocator_trimmed, allocator_errors = self._best_effort_allocator_trim() + cleanup_errors.extend(allocator_errors) after = self._cuda_memory_snapshot() message = ( - f'Accelerator cleanup complete. Released {released_nodes} cached node object(s), ' - f'{released_models} managed model(s), {released_diffusers_components} Modular Diffusers component(s), ' - f'and {released_offload_files} Diffusers disk offload file(s).' + f"Accelerator cleanup complete. Released {released_nodes} cached node object(s), " + f"{released_models} managed model(s), {released_diffusers_components} Modular Diffusers component(s), " + f"and {released_offload_files} Diffusers disk offload file(s)." ) if cleanup_errors: - message += ' Some cleanup calls reported errors but references were dropped where possible.' - - return web.json_response({ - 'error': False, - 'message': message, - 'released_node_count': released_nodes, - 'released_model_count': released_models, - 'released_diffusers_component_count': released_diffusers_components, - 'released_diffusers_offload_file_count': released_offload_files, - 'cleanup_errors': cleanup_errors, - 'before': before, - 'after': after, - }) + message += " Some cleanup calls reported errors but references were dropped where possible." + + return web.json_response( + { + "error": False, + "message": message, + "released_node_count": released_nodes, + "released_model_count": released_models, + "released_diffusers_component_count": released_diffusers_components, + "released_diffusers_offload_file_count": released_offload_files, + "allocator_trimmed": allocator_trimmed, + "cleanup_errors": cleanup_errors, + "before": before, + "after": after, + } + ) async def model_capabilities(self, request): - query = str(request.query.get('q', '')).lower().strip() - capabilities = list(STUDIO_MODEL_CAPABILITIES.values()) + query = str(request.query.get("q", "")).lower().strip() + profiles_by_model = {} + for profile in public_execution_profiles(): + profiles_by_model.setdefault(profile.get("model_type"), []).append(profile) + + capabilities = [] + for raw_capability in STUDIO_MODEL_CAPABILITIES.values(): + capability = dict(raw_capability) + profiles = profiles_by_model.get(capability.get("modelType"), []) + pipeline_classes = sorted( + {profile.get("pipeline_class") for profile in profiles if profile.get("pipeline_class")} + ) + runnable_modes = sorted({mode for profile in profiles for mode in profile.get("modes", [])}) + output_kind = capability.get("outputKind") or "image" + additional_requirements = capability.get("additionalRequirements") or [] + artifact_candidates = list(capability.get("artifactCandidates") or [capability.get("defaultRepo")]) + artifact_candidates.extend( + artifact + for profile in profiles + for artifact in (profile.get("default_repo"), profile.get("fallback_repo")) + if artifact + ) + artifact_candidates.extend( + requirement.get("repo") for requirement in additional_requirements if requirement.get("repo") + ) + quantized_components = sorted( + {component for profile in profiles for component in profile.get("quantizable_components", [])} + ) + capability.update( + { + "schemaVersion": 2, + "mediaKind": output_kind, + "supportTier": capability.get("supportTier") or "supported", + "pipelineClasses": pipeline_classes, + "executionProfiles": profiles, + "runnableModes": runnable_modes, + "inputContracts": capability.get("modeRequirements") or {}, + "parameterAliases": { + "modelRepository": ["model_id", "model", "repo"], + "guidanceScale": ["guidance_scale", "true_cfg_scale", "guidance"], + "steps": ["num_inference_steps", "steps"], + "sourceImage": ["image", "reference_images"], + "maskImage": ["mask_image", "mask"], + "controlImage": ["control_image", "conditioning_image"], + }, + "defaults": { + "dtype": capability.get("defaultDtype"), + "size": capability.get("defaultSize"), + "steps": capability.get("recommendedSteps"), + "guidanceScale": capability.get("recommendedGuidance"), + "offloadMode": (capability.get("offloadSupport") or {}).get("default"), + }, + "artifactCandidates": list(dict.fromkeys(filter(None, artifact_candidates))), + "revisionCandidates": capability.get("revisionCandidates") or [], + "quantizationSupport": { + "defaultMode": (capability.get("lowVram") or {}).get("quantizationMode", "none"), + "components": quantized_components, + "offloadModes": (capability.get("offloadSupport") or {}).get("modes", []), + }, + "qualificationStatus": capability.get("qualificationStatus") or "graph-qualified", + } + ) + capabilities.append(capability) if query: capabilities = [ capability for capability in capabilities - if query in capability.get('modelType', '').lower() - or query in capability.get('label', '').lower() - or query in capability.get('family', '').lower() - or query in capability.get('defaultRepo', '').lower() + if query in capability.get("modelType", "").lower() + or query in capability.get("label", "").lower() + or query in capability.get("family", "").lower() + or query in capability.get("defaultRepo", "").lower() ] - return web.json_response({ - 'error': False, - 'count': len(capabilities), - 'capabilities': capabilities, - 'diffusersExecutionProfiles': public_execution_profiles(), - 'source': 'modiff-backend', - }) + return web.json_response( + { + "error": False, + "schemaVersion": 2, + "count": len(capabilities), + "capabilities": capabilities, + "diffusersExecutionProfiles": public_execution_profiles(), + "experimentalCapabilities": public_experimental_pipelines(), + "source": "modiff-backend", + } + ) + + def _auto_resource_runtime_block(self): + cached = ( + self._last_runtime_fingerprint.get("hardware") + if self.current_task and isinstance(self._last_runtime_fingerprint, dict) + else None + ) + hardware = deepcopy(cached) if isinstance(cached, dict) else get_hardware_snapshot(self.data_dir, refresh=True) + profile = runtime_profile(hardware, venv=Path(sys.prefix)) + if profile.get("execution_ready"): + return None + profile_issues = [issue for issue in profile.get("issues", []) if issue.get("severity") == "error"] + message = ( + profile_issues[0].get("message") + if profile_issues + else "The managed runtime environment is not ready for execution." + ) + return { + "error": False, + "schemaVersion": 2, + "resourceMode": "auto", + "status": "needs_setup", + "readiness": "needs_setup", + "statusLabel": "Runtime needs repair", + "blockingReason": message, + "healthBadge": "Needs setup", + "compatibility": { + "state": "needs_setup", + "severity": "error", + "code": "runtime_profile_mismatch", + "summary": "Runtime needs repair", + "detail": message, + "action": { + "type": "repair_environment", + "label": "Open Setup", + "command": profile.get("repair_command"), + }, + "source": "backend_auto_planner", + }, + "canAutoRun": False, + "issue": { + "code": "runtime_profile_mismatch", + "category": "environment", + "message": message, + "issues": profile_issues, + }, + "repairAction": { + "type": "open_setup", + "label": "Open Setup", + "command": profile.get("repair_command"), + }, + "runtimeProfile": profile, + "selectedCandidate": None, + "candidates": [], + "checkedAt": int(time.time() * 1000), + } + + def _auto_planning_runtime_fingerprint(self): + """Report capacity available after releasing MoDiff-owned CUDA cache. + + Auto plans are requested between graph runs while the preceding + pipeline is intentionally kept resident for reuse. Sampling raw free + VRAM at that point makes the planner count MoDiff's own reusable cache + as external pressure. A high-memory native recipe can consequently + downshift to CPU offload, which changes the runtime signature and + evicts the exact cache the next graph could have reused. + + Add only this worker's PyTorch reservation back to the free-memory + sample, capped by physical capacity. Memory owned by other processes + remains unavailable, so real external pressure still selects a safer + plan. + """ + fingerprint = self._runtime_fingerprint_for_control_request() + # During an active model call the cached fingerprint was captured + # immediately before execution and already describes the capacity that + # will be available after that run. Do not call torch.cuda memory APIs + # here: some ROCm/CUDA loaders hold runtime locks while materializing + # weights, and a refresh-time planning request must remain responsive. + if self.current_task: + return fingerprint + if not (self.node_cache or memory_manager.cache): + return fingerprint + + hardware = fingerprint.get("hardware") if isinstance(fingerprint, dict) else None + devices = hardware.get("devices") if isinstance(hardware, dict) else None + if not isinstance(devices, list): + return fingerprint + + try: + torch = import_module("torch") + if not bool(torch.cuda.is_available()): + return fingerprint + except Exception: + return fingerprint + + adjusted = deepcopy(fingerprint) + adjusted_hardware = adjusted.get("hardware") + adjusted_devices = adjusted_hardware.get("devices") if isinstance(adjusted_hardware, dict) else None + if not isinstance(adjusted_devices, list): + return fingerprint + + reclaimable_by_index = {} + for index in range(int(torch.cuda.device_count())): + try: + reclaimable_by_index[index] = max(0, int(torch.cuda.memory_reserved(index))) + except Exception: + reclaimable_by_index[index] = 0 + + for device in adjusted_devices: + if not isinstance(device, dict) or device.get("type") != "cuda": + continue + try: + index = int(device.get("index") or 0) + except (TypeError, ValueError): + index = 0 + reclaimable = reclaimable_by_index.get(index, 0) + if reclaimable <= 0: + continue + totals = [ + value + for value in ( + device.get("torch_vram_total"), + device.get("vram_total"), + ) + if isinstance(value, int) and value > 0 + ] + total = min(totals) if totals else None + for key in ("torch_vram_free", "vram_free"): + free = device.get(key) + if not isinstance(free, int): + continue + device[key] = min(total, free + reclaimable) if total is not None else free + reclaimable + device["modiff_reclaimable_vram"] = reclaimable + + torch_state = adjusted_hardware.get("torch") if isinstance(adjusted_hardware, dict) else None + if isinstance(torch_state, dict): + reclaimable = reclaimable_by_index.get(0, 0) + free = torch_state.get("cuda_memory_free_bytes") + total = torch_state.get("cuda_memory_total_bytes") or torch_state.get("cuda_device_total_memory_bytes") + if reclaimable > 0 and isinstance(free, int): + torch_state["cuda_memory_free_bytes"] = ( + min(total, free + reclaimable) if isinstance(total, int) and total > 0 else free + reclaimable + ) + return adjusted async def auto_resource_plan(self, request): try: @@ -4533,21 +9579,63 @@ async def auto_resource_plan(self, request): payload = {} try: + runtime_block = self._auto_resource_runtime_block() + if runtime_block: + return web.json_response(runtime_block) plan = build_auto_resource_plan( payload if isinstance(payload, dict) else {}, - runtime_fingerprint=self._runtime_fingerprint(), + runtime_fingerprint=self._auto_planning_runtime_fingerprint(), local_models=get_local_models(), data_dir=self.data_dir, history=read_auto_resource_history(self.data_dir), ) return web.json_response(plan) except Exception as exc: - return web.json_response({ - 'error': True, - 'status': 'needs_setup', - 'statusLabel': 'Needs setup', - 'message': str(exc) or type(exc).__name__, - }, status=500) + return web.json_response( + { + "error": True, + "schemaVersion": 2, + "status": "needs_setup", + "statusLabel": "Needs setup", + "message": str(exc) or type(exc).__name__, + "compatibility": { + "state": "needs_setup", + "severity": "error", + "code": "auto_planner_error", + "summary": "Auto planning failed", + "detail": str(exc) or type(exc).__name__, + "action": {"type": "open_setup", "label": "Open Setup"}, + "source": "backend_auto_planner", + }, + }, + status=500, + ) + + async def model_artifact_catalog(self, request): + try: + live_metadata = None + if request.query.get("refresh") in {"1", "true", "yes"}: + live_metadata = await asyncio.to_thread( + refreshed_hub_metadata, + model_type=request.query.get("modelType"), + repo=request.query.get("repo"), + ) + return web.json_response( + { + "error": False, + **public_model_artifact_catalog(), + "liveMetadata": live_metadata, + } + ) + except Exception as exc: + return web.json_response( + { + "error": True, + "message": str(exc) or type(exc).__name__, + "models": [], + }, + status=500, + ) async def auto_resource_plans(self, request): try: @@ -4556,45 +9644,112 @@ async def auto_resource_plans(self, request): payload = {} try: + runtime_block = self._auto_resource_runtime_block() + if runtime_block: + forms = payload.get("forms", []) if isinstance(payload, dict) else [] + keys = payload.get("keys", []) if isinstance(payload, dict) else [] + plans = [] + for index, _form in enumerate(forms): + plan = {**runtime_block, "requestIndex": index} + if index < len(keys) and keys[index]: + plan["planKey"] = str(keys[index]) + plans.append(plan) + return web.json_response( + { + "error": False, + "schemaVersion": 2, + "resourceMode": "auto", + "count": len(plans), + "plans": plans, + "checkedAt": int(time.time() * 1000), + } + ) result = build_auto_resource_plans( payload if isinstance(payload, dict) else {}, - runtime_fingerprint=self._runtime_fingerprint(), + runtime_fingerprint=self._auto_planning_runtime_fingerprint(), local_models=get_local_models(), data_dir=self.data_dir, ) return web.json_response(result) except Exception as exc: - return web.json_response({ - 'error': True, - 'message': str(exc) or type(exc).__name__, - 'plans': [], - 'count': 0, - }, status=500) + return web.json_response( + { + "error": True, + "message": str(exc) or type(exc).__name__, + "plans": [], + "count": 0, + }, + status=500, + ) async def auto_resource_history(self, _request): - return web.json_response({ - 'error': False, - 'history': read_auto_resource_history(self.data_dir), - }) + return web.json_response( + { + "error": False, + "history": read_auto_resource_history(self.data_dir), + } + ) + + async def media_assets_list(self, _request): + from modiff.media_assets import list_media_assets + + assets = list_media_assets() + return web.json_response({"error": False, "assets": assets, "count": len(assets)}) + + async def media_assets_cleanup(self, request): + from modiff.media_assets import cleanup_media_assets + + if self.current_task is not None: + return web.json_response( + { + "error": True, + "message": "Temporary media cannot be cleaned while a generation is active.", + }, + status=409, + ) + try: + payload = await request.json() + except Exception: + payload = {} + payload = payload if isinstance(payload, dict) else {} + scope = str(payload.get("scope") or "all_unpinned") + task_id = str(payload.get("taskId") or "").strip() or None + older_seconds = None + if scope == "older_than": + older_seconds = max(0.0, float(payload.get("olderThanHours") or 24)) * 3600 + elif scope == "task": + if not task_id: + return web.json_response({"error": True, "message": "Task cleanup needs a taskId."}, status=400) + elif scope != "all_unpinned": + return web.json_response( + {"error": True, "message": f"Unsupported media cleanup scope {scope!r}."}, status=400 + ) + report = cleanup_media_assets( + task_id=task_id if scope == "task" else None, + older_than_seconds=older_seconds, + ) + return web.json_response({"error": bool(report["errors"]), **report}) async def auto_resource_history_clear(self, request): - model_type = request.query.get('modelType') or None - mode = request.query.get('mode') or None - artifact = request.query.get('artifact') or None + model_type = request.query.get("modelType") or None + mode = request.query.get("mode") or None + artifact = request.query.get("artifact") or None removed = clear_auto_resource_history( self.data_dir, model_type=model_type, mode=mode, artifact=artifact, ) - return web.json_response({ - 'error': False, - 'removed': removed, - 'history': read_auto_resource_history(self.data_dir), - }) + return web.json_response( + { + "error": False, + "removed": removed, + "history": read_auto_resource_history(self.data_dir), + } + ) def _timestamp_seconds(self, value): - if hasattr(value, 'timestamp'): + if hasattr(value, "timestamp"): return int(value.timestamp()) if isinstance(value, (int, float)): return int(value) @@ -4602,44 +9757,45 @@ def _timestamp_seconds(self, value): def _model_revision_records(self, model): revisions = [] - for index, revision in enumerate(model.get('revisions') or []): - commit_hash = revision.get('hash') if isinstance(revision, dict) else None + for index, revision in enumerate(model.get("revisions") or []): + commit_hash = revision.get("hash") if isinstance(revision, dict) else None if not commit_hash: continue - revisions.append({ - 'hash': commit_hash, - 'size': revision.get('size', 0), - 'lastModified': self._timestamp_seconds(revision.get('last_modified')), - 'order': index, - }) - revisions.sort(key=lambda item: ((item.get('lastModified') or 0), item.get('order') or 0)) + revisions.append( + { + "hash": commit_hash, + "size": revision.get("size", 0), + "lastModified": self._timestamp_seconds(revision.get("last_modified")), + "order": index, + } + ) + revisions.sort(key=lambda item: ((item.get("lastModified") or 0), item.get("order") or 0)) return revisions def _model_fingerprint(self, repo_id, revisions): payload = { - 'repoId': repo_id, - 'revisions': [revision.get('hash') for revision in revisions], + "repoId": repo_id, + "revisions": [revision.get("hash") for revision in revisions], } - digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode('utf-8')).hexdigest() + digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode("utf-8")).hexdigest() return f"sha256:{digest}" async def model_fingerprints(self, request): - query = str(request.query.get('q', '')).lower().strip() - model_type = str(request.query.get('modelType', '')).strip() - repo_query = str(request.query.get('repo') or request.query.get('repoId') or '').strip() + query = str(request.query.get("q", "")).lower().strip() + model_type = str(request.query.get("modelType", "")).strip() + repo_query = str(request.query.get("repo") or request.query.get("repoId") or "").strip() capability_by_model = { - capability.get('modelType'): capability - for capability in STUDIO_MODEL_CAPABILITIES.values() + capability.get("modelType"): capability for capability in STUDIO_MODEL_CAPABILITIES.values() } repo_to_model_types = {} for capability in STUDIO_MODEL_CAPABILITIES.values(): - repo_id = capability.get('defaultRepo') + repo_id = capability.get("defaultRepo") if repo_id: - repo_to_model_types.setdefault(repo_id, []).append(capability.get('modelType')) + repo_to_model_types.setdefault(repo_id, []).append(capability.get("modelType")) requested_repos = [] if model_type and model_type in capability_by_model: - default_repo = capability_by_model[model_type].get('defaultRepo') + default_repo = capability_by_model[model_type].get("defaultRepo") if default_repo: requested_repos.append(default_repo) if repo_query: @@ -4648,89 +9804,112 @@ async def model_fingerprints(self, request): models = [] found_repo_ids = set() - for model in get_local_models(): - repo_id = model.get('id') + local_models = get_local_models() + for model in local_models: + repo_id = model.get("id") if not repo_id: continue repo_id_lower = repo_id.lower() if requested_repo_set and repo_id_lower not in requested_repo_set: continue - if query and query not in repo_id_lower and not any(query in str(name).lower() for name in model.get('class_names', [])): + if ( + query + and query not in repo_id_lower + and not any(query in str(name).lower() for name in model.get("class_names", [])) + ): continue revisions = self._model_revision_records(model) - selected_revision = revisions[-1]['hash'] if revisions else None + selected_revision = revisions[-1]["hash"] if revisions else None + install_status = artifact_cache_status(repo_id, local_models) + complete = bool(install_status.get("complete")) found_repo_ids.add(repo_id_lower) - models.append({ - 'repoId': repo_id, - 'modelTypes': repo_to_model_types.get(repo_id, []), - 'installed': True, - 'selectedRevision': selected_revision, - 'modelRevision': f"{repo_id}@{selected_revision}" if selected_revision else None, - 'fingerprint': self._model_fingerprint(repo_id, revisions), - 'size': model.get('size', 0), - 'classNames': model.get('class_names', []), - 'cacheDirs': model.get('cache_dirs') or ([model.get('cache_dir')] if model.get('cache_dir') else []), - 'revisions': revisions, - }) + models.append( + { + "repoId": repo_id, + "modelTypes": repo_to_model_types.get(repo_id, []), + "cached": bool(install_status.get("installed")), + "installed": complete, + "complete": complete, + "repairRequired": bool(install_status.get("repairRequired")), + "installReason": install_status.get("reason"), + "activeFiles": install_status.get("activeFiles") or [], + "missingFiles": install_status.get("missingFiles") or [], + "corruptFiles": install_status.get("corruptFiles") or [], + "selectedRevision": selected_revision, + "modelRevision": f"{repo_id}@{selected_revision}" if selected_revision else None, + "fingerprint": self._model_fingerprint(repo_id, revisions), + "size": model.get("size", 0), + "classNames": model.get("class_names", []), + "cacheDirs": model.get("cache_dirs") + or ([model.get("cache_dir")] if model.get("cache_dir") else []), + "revisions": revisions, + } + ) for repo_id in requested_repos: if repo_id.lower() in found_repo_ids: continue - models.append({ - 'repoId': repo_id, - 'modelTypes': repo_to_model_types.get(repo_id, [model_type] if model_type else []), - 'installed': False, - 'selectedRevision': None, - 'modelRevision': None, - 'fingerprint': None, - 'size': 0, - 'classNames': [], - 'cacheDirs': [], - 'revisions': [], - }) + models.append( + { + "repoId": repo_id, + "modelTypes": repo_to_model_types.get(repo_id, [model_type] if model_type else []), + "installed": False, + "selectedRevision": None, + "modelRevision": None, + "fingerprint": None, + "size": 0, + "classNames": [], + "cacheDirs": [], + "revisions": [], + } + ) runtime_fingerprint = self._runtime_fingerprint() - return web.json_response({ - 'error': False, - 'count': len(models), - 'models': models, - 'runtimeFingerprint': runtime_fingerprint.get('fingerprint'), - 'source': 'modiff-backend', - }) + return web.json_response( + { + "error": False, + "count": len(models), + "models": models, + "runtimeFingerprint": runtime_fingerprint.get("fingerprint"), + "source": "modiff-backend", + } + ) async def model_cache_diagnostics(self, request): - refresh = str(request.query.get('refresh', '')).lower() in ('1', 'true', 'yes') + refresh = str(request.query.get("refresh", "")).lower() in ("1", "true", "yes") if refresh: modelstore.actualize() return web.json_response(get_cache_diagnostics()) def _custom_modules_root(self): - root = Path('custom').resolve() + root = Path("custom").resolve() root.mkdir(parents=True, exist_ok=True) return root def _disabled_custom_modules_root(self): - root = (self._custom_modules_root() / '.disabled').resolve() + root = (self._custom_modules_root() / ".disabled").resolve() root.mkdir(parents=True, exist_ok=True) return root def _safe_custom_module_name(self, value): - name = str(value or '').strip() + name = str(value or "").strip() if not name: - raise ValueError('Module name is required.') - if not re.match(r'^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$', name): - raise ValueError('Module name may only contain letters, numbers, dot, underscore, and dash, and must not start with a dot.') + raise ValueError("Module name is required.") + if not re.match(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$", name): + raise ValueError( + "Module name may only contain letters, numbers, dot, underscore, and dash, and must not start with a dot." + ) return name def _derive_custom_module_name(self, source): - source_text = str(source or '').strip().rstrip('/\\') + source_text = str(source or "").strip().rstrip("/\\") if not source_text: - raise ValueError('Module source is required.') - source_text = source_text[:-4] if source_text.endswith('.git') else source_text - name = re.split(r'[/\\:]', source_text)[-1] - name = re.sub(r'[^A-Za-z0-9_.-]+', '-', name).strip('.-') + raise ValueError("Module source is required.") + source_text = source_text[:-4] if source_text.endswith(".git") else source_text + name = re.split(r"[/\\:]", source_text)[-1] + name = re.sub(r"[^A-Za-z0-9_.-]+", "-", name).strip(".-") return self._safe_custom_module_name(name) def _custom_module_path(self, name, disabled=False): @@ -4738,17 +9917,17 @@ def _custom_module_path(self, name, disabled=False): root = self._disabled_custom_modules_root() if disabled else self._custom_modules_root() target = (root / safe_name).resolve() if target.parent != root: - raise ValueError('Resolved custom module path escaped the custom module directory.') + raise ValueError("Resolved custom module path escaped the custom module directory.") return target def _is_git_source(self, source): - source = str(source or '').strip().lower() - return source.startswith(('https://', 'http://', 'ssh://', 'git@')) or source.endswith('.git') + source = str(source or "").strip().lower() + return source.startswith(("https://", "http://", "ssh://", "git@")) or source.endswith(".git") def _run_git(self, args, cwd=None, timeout=300): - git_bin = shutil.which('git') + git_bin = shutil.which("git") if not git_bin: - raise RuntimeError('git is not available in the MoDiff backend environment.') + raise RuntimeError("git is not available in the MoDiff backend environment.") completed = subprocess.run( [git_bin, *args], @@ -4759,53 +9938,53 @@ def _run_git(self, args, cwd=None, timeout=300): shell=False, ) result = { - 'returncode': completed.returncode, - 'stdout': completed.stdout.strip(), - 'stderr': completed.stderr.strip(), + "returncode": completed.returncode, + "stdout": completed.stdout.strip(), + "stderr": completed.stderr.strip(), } if completed.returncode != 0: - message = result['stderr'] or result['stdout'] or f'git exited with {completed.returncode}' + message = result["stderr"] or result["stdout"] or f"git exited with {completed.returncode}" raise RuntimeError(message) return result def _git_value(self, module_path, args): try: - return self._run_git(args, cwd=module_path, timeout=10).get('stdout', '') + return self._run_git(args, cwd=module_path, timeout=10).get("stdout", "") except Exception: - return '' + return "" def _custom_module_git_info(self, module_path): - is_git = bool(self._git_value(module_path, ['rev-parse', '--is-inside-work-tree'])) + is_git = bool(self._git_value(module_path, ["rev-parse", "--is-inside-work-tree"])) if not is_git: return { - 'hasGit': False, - 'canUpdate': False, + "hasGit": False, + "canUpdate": False, } return { - 'hasGit': True, - 'canUpdate': True, - 'remote': self._git_value(module_path, ['config', '--get', 'remote.origin.url']), - 'branch': self._git_value(module_path, ['rev-parse', '--abbrev-ref', 'HEAD']), - 'commit': self._git_value(module_path, ['rev-parse', '--short', 'HEAD']), + "hasGit": True, + "canUpdate": True, + "remote": self._git_value(module_path, ["config", "--get", "remote.origin.url"]), + "branch": self._git_value(module_path, ["rev-parse", "--abbrev-ref", "HEAD"]), + "commit": self._git_value(module_path, ["rev-parse", "--short", "HEAD"]), } def _custom_module_info(self, name, module_path, enabled=True): - module_key = f'custom.{name}' + module_key = f"custom.{name}" node_actions = sorted((self.modules.get(module_key) or {}).keys()) if enabled else [] git_info = self._custom_module_git_info(module_path) return { - 'name': name, - 'moduleKey': module_key, - 'source': 'custom', - 'enabled': enabled, - 'status': 'enabled' if enabled else 'disabled', - 'path': str(module_path), - 'hasInit': (module_path / '__init__.py').exists(), - 'hasMain': (module_path / 'main.py').exists(), - 'nodeCount': len(node_actions), - 'nodes': node_actions, - 'canDisable': enabled, - 'canEnable': not enabled, + "name": name, + "moduleKey": module_key, + "source": "custom", + "enabled": enabled, + "status": "enabled" if enabled else "disabled", + "path": str(module_path), + "hasInit": (module_path / "__init__.py").exists(), + "hasMain": (module_path / "main.py").exists(), + "nodeCount": len(node_actions), + "nodes": node_actions, + "canDisable": enabled, + "canEnable": not enabled, **git_info, } @@ -4815,12 +9994,12 @@ def _list_custom_modules(self): modules = [] for entry in sorted(root.iterdir(), key=lambda item: item.name.lower()): - if not entry.is_dir() or entry.name.startswith('.') or entry.name == '__pycache__': + if not entry.is_dir() or entry.name.startswith(".") or entry.name == "__pycache__": continue modules.append(self._custom_module_info(entry.name, entry, enabled=True)) for entry in sorted(disabled_root.iterdir(), key=lambda item: item.name.lower()): - if not entry.is_dir() or entry.name.startswith('.') or entry.name == '__pycache__': + if not entry.is_dir() or entry.name.startswith(".") or entry.name == "__pycache__": continue modules.append(self._custom_module_info(entry.name, entry, enabled=False)) @@ -4828,17 +10007,17 @@ def _list_custom_modules(self): def _refresh_custom_module_registry(self): for key in list(MODULE_MAP.keys()): - if key.startswith('custom.'): + if key.startswith("custom."): MODULE_MAP.pop(key, None) for key in list(sys.modules.keys()): - if key == 'custom' or key.startswith('custom.'): + if key == "custom" or key.startswith("custom."): sys.modules.pop(key, None) invalidate_caches() custom_root = self._custom_modules_root() if custom_root.exists(): - parse_module_map('custom') + parse_module_map("custom") self.modules = MODULE_MAP self.instance = nanoid.generate(size=10) @@ -4847,9 +10026,9 @@ def _refresh_custom_module_registry(self): def _prune_custom_node_cache(self, module_key=None): removed = [] for node_id, cached_node in list(self.node_cache.items()): - cached_module = getattr(cached_node, 'module_name', '') + cached_module = getattr(cached_node, "module_name", "") if module_key is None: - should_remove = str(cached_module).startswith('custom.') + should_remove = str(cached_module).startswith("custom.") else: should_remove = cached_module == module_key if should_remove: @@ -4860,11 +10039,11 @@ def _prune_custom_node_cache(self, module_key=None): def _custom_modules_payload(self): modules = self._list_custom_modules() return { - 'error': False, - 'root': str(self._custom_modules_root()), - 'disabledRoot': str(self._disabled_custom_modules_root()), - 'count': len(modules), - 'modules': modules, + "error": False, + "root": str(self._custom_modules_root()), + "disabledRoot": str(self._disabled_custom_modules_root()), + "count": len(modules), + "modules": modules, } async def custom_modules_list(self, request): @@ -4873,136 +10052,164 @@ async def custom_modules_list(self, request): async def custom_modules_refresh(self, request): self._prune_custom_node_cache() modules = self._refresh_custom_module_registry() - return web.json_response({ - 'error': False, - 'message': 'Custom module registry refreshed.', - 'instance': self.instance, - 'count': len(modules), - 'modules': modules, - }) + return web.json_response( + { + "error": False, + "message": "Custom module registry refreshed.", + "instance": self.instance, + "count": len(modules), + "modules": modules, + } + ) async def custom_modules_install(self, request): try: data = await request.json() - source = str(data.get('source') or data.get('url') or '').strip() - name = self._safe_custom_module_name(data.get('name') or self._derive_custom_module_name(source)) + source = str(data.get("source") or data.get("url") or "").strip() + name = self._safe_custom_module_name(data.get("name") or self._derive_custom_module_name(source)) target = self._custom_module_path(name) disabled_target = self._custom_module_path(name, disabled=True) if target.exists() or disabled_target.exists(): - return web.json_response({'error': True, 'message': f'Custom module `{name}` already exists.'}, status=409) + return web.json_response( + {"error": True, "message": f"Custom module `{name}` already exists."}, status=409 + ) if self._is_git_source(source): - self._run_git(['clone', source, str(target)], timeout=900) + self._run_git(["clone", source, str(target)], timeout=900) else: source_path = Path(source).expanduser().resolve() if not source_path.is_dir(): - return web.json_response({'error': True, 'message': 'Source must be a Git URL or an existing local directory.'}, status=400) - shutil.copytree(source_path, target, ignore=shutil.ignore_patterns('__pycache__', '.pytest_cache', '.mypy_cache')) + return web.json_response( + {"error": True, "message": "Source must be a Git URL or an existing local directory."}, + status=400, + ) + shutil.copytree( + source_path, target, ignore=shutil.ignore_patterns("__pycache__", ".pytest_cache", ".mypy_cache") + ) modules = self._refresh_custom_module_registry() - return web.json_response({ - 'error': False, - 'message': f'Custom module `{name}` installed.', - 'module': next((item for item in modules if item['name'] == name), None), - 'modules': modules, - 'instance': self.instance, - }) + return web.json_response( + { + "error": False, + "message": f"Custom module `{name}` installed.", + "module": next((item for item in modules if item["name"] == name), None), + "modules": modules, + "instance": self.instance, + } + ) except Exception as e: logger.error(f"Error installing custom module: {e}", exc_info=True) - return web.json_response({'error': True, 'message': str(e)}, status=500) + return web.json_response({"error": True, "message": str(e)}, status=500) async def custom_modules_update(self, request): try: - name = self._safe_custom_module_name(request.match_info.get('name')) + name = self._safe_custom_module_name(request.match_info.get("name")) enabled_path = self._custom_module_path(name) disabled_path = self._custom_module_path(name, disabled=True) module_path = enabled_path if enabled_path.exists() else disabled_path if not module_path.exists(): - return web.json_response({'error': True, 'message': f'Custom module `{name}` was not found.'}, status=404) + return web.json_response( + {"error": True, "message": f"Custom module `{name}` was not found."}, status=404 + ) - if not (module_path / '.git').exists(): - return web.json_response({'error': True, 'message': f'Custom module `{name}` is not a Git checkout.'}, status=400) + if not (module_path / ".git").exists(): + return web.json_response( + {"error": True, "message": f"Custom module `{name}` is not a Git checkout."}, status=400 + ) - git_result = self._run_git(['pull', '--ff-only'], cwd=module_path, timeout=900) - removed_cache_nodes = self._prune_custom_node_cache(f'custom.{name}') + git_result = self._run_git(["pull", "--ff-only"], cwd=module_path, timeout=900) + removed_cache_nodes = self._prune_custom_node_cache(f"custom.{name}") modules = self._refresh_custom_module_registry() if enabled_path.exists() else self._list_custom_modules() - return web.json_response({ - 'error': False, - 'message': f'Custom module `{name}` updated.', - 'git': git_result, - 'removedCacheNodes': removed_cache_nodes, - 'module': next((item for item in modules if item['name'] == name), None), - 'modules': modules, - 'instance': self.instance, - }) + return web.json_response( + { + "error": False, + "message": f"Custom module `{name}` updated.", + "git": git_result, + "removedCacheNodes": removed_cache_nodes, + "module": next((item for item in modules if item["name"] == name), None), + "modules": modules, + "instance": self.instance, + } + ) except Exception as e: logger.error(f"Error updating custom module: {e}", exc_info=True) - return web.json_response({'error': True, 'message': str(e)}, status=500) + return web.json_response({"error": True, "message": str(e)}, status=500) async def custom_modules_disable(self, request): try: - name = self._safe_custom_module_name(request.match_info.get('name')) + name = self._safe_custom_module_name(request.match_info.get("name")) source = self._custom_module_path(name) target = self._custom_module_path(name, disabled=True) if not source.exists(): - return web.json_response({'error': True, 'message': f'Custom module `{name}` is not enabled.'}, status=404) + return web.json_response( + {"error": True, "message": f"Custom module `{name}` is not enabled."}, status=404 + ) if target.exists(): - return web.json_response({'error': True, 'message': f'Disabled custom module `{name}` already exists.'}, status=409) + return web.json_response( + {"error": True, "message": f"Disabled custom module `{name}` already exists."}, status=409 + ) shutil.move(str(source), str(target)) - removed_cache_nodes = self._prune_custom_node_cache(f'custom.{name}') + removed_cache_nodes = self._prune_custom_node_cache(f"custom.{name}") modules = self._refresh_custom_module_registry() - return web.json_response({ - 'error': False, - 'message': f'Custom module `{name}` disabled.', - 'removedCacheNodes': removed_cache_nodes, - 'module': next((item for item in modules if item['name'] == name), None), - 'modules': modules, - 'instance': self.instance, - }) + return web.json_response( + { + "error": False, + "message": f"Custom module `{name}` disabled.", + "removedCacheNodes": removed_cache_nodes, + "module": next((item for item in modules if item["name"] == name), None), + "modules": modules, + "instance": self.instance, + } + ) except Exception as e: logger.error(f"Error disabling custom module: {e}", exc_info=True) - return web.json_response({'error': True, 'message': str(e)}, status=500) + return web.json_response({"error": True, "message": str(e)}, status=500) async def custom_modules_enable(self, request): try: - name = self._safe_custom_module_name(request.match_info.get('name')) + name = self._safe_custom_module_name(request.match_info.get("name")) source = self._custom_module_path(name, disabled=True) target = self._custom_module_path(name) if not source.exists(): - return web.json_response({'error': True, 'message': f'Custom module `{name}` is not disabled.'}, status=404) + return web.json_response( + {"error": True, "message": f"Custom module `{name}` is not disabled."}, status=404 + ) if target.exists(): - return web.json_response({'error': True, 'message': f'Enabled custom module `{name}` already exists.'}, status=409) + return web.json_response( + {"error": True, "message": f"Enabled custom module `{name}` already exists."}, status=409 + ) shutil.move(str(source), str(target)) modules = self._refresh_custom_module_registry() - return web.json_response({ - 'error': False, - 'message': f'Custom module `{name}` enabled.', - 'module': next((item for item in modules if item['name'] == name), None), - 'modules': modules, - 'instance': self.instance, - }) + return web.json_response( + { + "error": False, + "message": f"Custom module `{name}` enabled.", + "module": next((item for item in modules if item["name"] == name), None), + "modules": modules, + "instance": self.instance, + } + ) except Exception as e: logger.error(f"Error enabling custom module: {e}", exc_info=True) - return web.json_response({'error': True, 'message': str(e)}, status=500) + return web.json_response({"error": True, "message": str(e)}, status=500) async def hf_cache_delete(self, request): - hashes = request.match_info.get('hash').split(',') + hashes = request.match_info.get("hash").split(",") if not hashes: return web.json_response({"error": "Incorrect request, `hash` is required."}, status=400) result = delete_model(*hashes) return web.json_response({"error": not result}) - # TODO: not yet implemented async def hf_hub(self, request): - query = request.query.get('q', '') - sid = request.query.get('sid') + query = request.query.get("q", "") + sid = request.query.get("sid") future = asyncio.Future() - await self.queue_task(search_hub, query, future, sid, name=f"Hugging Face search") + await self.queue_task(search_hub, query, future, sid, name="Hugging Face search") try: result = await future @@ -5013,6 +10220,7 @@ async def hf_hub(self, request): async def _run_hf_download_task(self, repo_id, entry): task_id = entry["task_id"] + def progress_cb(progress): message = { "type": "hf_download_progress", @@ -5034,60 +10242,153 @@ def progress_cb(progress): self.queue_message(message, download_sid) for download_sid in list(entry.get("sids", [])): - self.queue_message({ - "type": "hf_download_progress", - "repo_id": repo_id, - "task_id": task_id, - "download_id": task_id, - "progress": 0, - "status": "queued", - "phase": "queued", - "started_at": entry.get("started_at"), - "updated_at": time.time(), - }, download_sid) - - async with self.hf_download_semaphore: - for download_sid in list(entry.get("sids", [])): - self.queue_message({ + self.queue_message( + { "type": "hf_download_progress", "repo_id": repo_id, "task_id": task_id, "download_id": task_id, "progress": 0, - "status": "planning", - "phase": "planning", + "status": "queued", + "phase": "queued", "started_at": entry.get("started_at"), "updated_at": time.time(), - }, download_sid) + }, + download_sid, + ) + + async with self.hf_download_semaphore: + for download_sid in list(entry.get("sids", [])): + self.queue_message( + { + "type": "hf_download_progress", + "repo_id": repo_id, + "task_id": task_id, + "download_id": task_id, + "progress": 0, + "status": "planning", + "phase": "planning", + "started_at": entry.get("started_at"), + "updated_at": time.time(), + }, + download_sid, + ) - result = await self.loop.run_in_executor(None, partial(download_hub_model, repo_id, progress_cb, bool(entry.get("repair")))) + if self.serialize_model_io and self.model_io_lock.locked(): + for download_sid in list(entry.get("sids", [])): + self.queue_message( + { + "type": "hf_download_progress", + "repo_id": repo_id, + "task_id": task_id, + "download_id": task_id, + "progress": 0, + "status": "queued", + "phase": "waiting_for_model_io", + "message": "Waiting for active generation or model I/O to finish safely.", + "started_at": entry.get("started_at"), + "updated_at": time.time(), + }, + download_sid, + ) + result = await self._run_executor_callback( + partial( + download_hub_model, + repo_id, + progress_cb, + bool(entry.get("repair")), + entry.get("repair_source_repo_id"), + entry.get("requested_files"), + ), + serialize_model_io=True, + ) if result: modelstore.actualize() return result async def hf_download(self, request): - repo_id = request.query.get('repo_id') - sid = request.query.get('sid') - repair = str(request.query.get('repair') or '').lower() in {'1', 'true', 'yes'} + payload = {} + if getattr(request, "can_read_body", False): + try: + payload = await request.json() + except (ValueError, TypeError): + return web.json_response({"error": "Invalid JSON body."}, status=400) + if not isinstance(payload, dict): + return web.json_response({"error": "The download request must be a JSON object."}, status=400) + + query = getattr(request, "query", {}) or {} + repo_id = payload.get("repo_id") or query.get("repo_id") + sid = payload.get("sid") or query.get("sid") + repair_value = payload.get("repair") if "repair" in payload else query.get("repair") + repair = repair_value is True or str(repair_value or "").lower() in {"1", "true", "yes"} + repair_source_repo_id = ( + str(payload.get("repair_source_repo_id") or query.get("repair_source_repo_id") or "").strip() or None + ) + raw_files = payload.get("files", payload.get("file", [])) + if isinstance(raw_files, str): + raw_files = [raw_files] + elif not isinstance(raw_files, list): + raw_files = [] + if not raw_files and hasattr(query, "getall"): + raw_files = query.getall("file", []) + if not raw_files and query.get("file"): + raw_files = [query.get("file")] + requested_files = sorted( + {item.strip() for raw in raw_files for item in str(raw or "").split(",") if item.strip()} + ) + if not requested_files and repo_id: + matching_capability = next( + ( + capability + for capability in STUDIO_MODEL_CAPABILITIES.values() + if capability.get("defaultRepo") == repo_id and capability.get("downloadFiles") + ), + None, + ) + if matching_capability: + requested_files = sorted(set(matching_capability["downloadFiles"])) + if repair and not repair_source_repo_id: + repair_source_repo_id = VERIFIED_REPAIR_SOURCES.get(repo_id) if not repo_id: return web.json_response({"error": "Incorrect request, `repo_id` is required."}, status=400) + try: + repo_id = validate_hf_repo_id(repo_id) + if repair_source_repo_id is not None: + repair_source_repo_id = validate_hf_repo_id(repair_source_repo_id) + except (TypeError, ValueError) as error: + return web.json_response( + {"error": str(error), "code": "invalid_huggingface_repo_id", "retryable": False}, + status=400, + ) if repo_id in self.hf_download_tasks: entry = self.hf_download_tasks[repo_id] + if sorted(entry.get("requested_files") or []) != requested_files: + return web.json_response( + { + "error": "A different file selection is already downloading for this repository.", + "repo_id": repo_id, + "retryable": True, + }, + status=409, + ) if sid: entry["sids"].add(sid) - self.queue_message({ - "type": "hf_download_progress", - "repo_id": repo_id, - "task_id": entry["task_id"], - "download_id": entry["task_id"], - "status": "joined", - "phase": "queued", - "progress": None, - "started_at": entry.get("started_at"), - "updated_at": time.time(), - }, sid) + self.queue_message( + { + "type": "hf_download_progress", + "repo_id": repo_id, + "task_id": entry["task_id"], + "download_id": entry["task_id"], + "status": "joined", + "phase": "queued", + "progress": None, + "started_at": entry.get("started_at"), + "updated_at": time.time(), + }, + sid, + ) else: task_id = nanoid.generate(size=12) entry = { @@ -5095,6 +10396,8 @@ async def hf_download(self, request): "sids": set([sid] if sid else []), "started_at": time.time(), "repair": repair, + "repair_source_repo_id": repair_source_repo_id, + "requested_files": requested_files, } entry["future"] = self.loop.create_task(self._run_hf_download_task(repo_id, entry)) self.hf_download_tasks[repo_id] = entry @@ -5108,19 +10411,39 @@ async def hf_download(self, request): if isinstance(result, dict) and isinstance(result.get("validation"), dict) else None ) or "The downloaded snapshot is incomplete and requires repair." - return web.json_response({ - "error": reason, - "result": result, - "task_id": entry["task_id"], - "repo_id": repo_id, - "repair_required": True, - }, status=409) - return web.json_response({"error": False, "result": result, "task_id": entry["task_id"], "repo_id": repo_id}) + return web.json_response( + { + "error": reason, + "result": result, + "task_id": entry["task_id"], + "repo_id": repo_id, + "repair_required": True, + }, + status=409, + ) + return web.json_response( + {"error": False, "result": result, "task_id": entry["task_id"], "repo_id": repo_id} + ) except Exception as e: logger.error(f"Error in hf_download endpoint: {e}") - return web.json_response({"error": str(e)}, status=500) + status, code, message, retryable = classify_hf_download_error(e) + return web.json_response( + { + "error": message, + "code": code, + "repo_id": repo_id, + "task_id": entry["task_id"], + "retryable": retryable, + "preserved_partial_download": True, + }, + status=status, + ) finally: - if repo_id in self.hf_download_tasks and self.hf_download_tasks[repo_id].get("future") is entry.get("future") and entry["future"].done(): + if ( + repo_id in self.hf_download_tasks + and self.hf_download_tasks[repo_id].get("future") is entry.get("future") + and entry["future"].done() + ): self.hf_download_tasks.pop(repo_id, None) async def hf_token(self, request): @@ -5134,6 +10457,7 @@ async def hf_token(self, request): try: from huggingface_hub import HfApi + identity = await self.loop.run_in_executor(None, lambda: HfApi(token=token).whoami()) except Exception as error: status = getattr(getattr(error, "response", None), "status_code", None) @@ -5158,79 +10482,85 @@ async def hf_token(self, request): if temp_path.exists(): temp_path.unlink(missing_ok=True) CONFIG.hf["token"] = token - return web.json_response({ - "error": False, - "token_configured": True, - "account_type": identity.get("type") if isinstance(identity, dict) else None, - }) - + return web.json_response( + { + "error": False, + "token_configured": True, + "account_type": identity.get("type") if isinstance(identity, dict) else None, + } + ) """ ╭───────────────╮ Websocket ╰───────────────╯ """ + async def websocket(self, request): + origin_error = self._untrusted_websocket_origin_response(request) + if origin_error is not None: + return origin_error + ws = web.WebSocketResponse() await ws.prepare(request) - sid = request.query.get('sid') + sid = request.query.get("sid") if not sid: sid = nanoid.generate(size=10) if sid in self.ws_sessions: # close the connection and remove the old session logger.debug(f"Websocket session {sid} already exists, closing the old session.") - #await self.ws_sessions[sid].close() + # await self.ws_sessions[sid].close() sid = nanoid.generate(size=10) - #del self.ws_sessions[sid] + # del self.ws_sessions[sid] self.ws_sessions[sid] = ws logger.debug(f"Websocket connection opened: {sid}") - # Update the session id in all cached nodes - for node in self.node_cache: - # check if the node has a _sid attribute - if hasattr(self.node_cache[node], '_sid') and self.node_cache[node]._sid != sid: - self.node_cache[node]._sid = sid - # Restore global execution truth as part of the connection handshake so # panels never become responsible for discovering active work. queued_tasks, current_task = self._get_queue() - await self.broadcast({ - "type": "welcome", - "instance": self.instance, - "sid": sid, - "cachedNodes": list(self.node_cache.keys()), - "queued": queued_tasks, - "current": current_task, - "recent": self.recent_tasks, - }, sid) + await self.broadcast( + { + "type": "welcome", + "instance": self.instance, + "sid": sid, + "cachedNodes": list(self.node_cache.keys()), + "queued": queued_tasks, + "current": current_task, + # Keep the welcome contract aligned with /queue: workflow graphs + # remain available lazily through /runs/{task_id}, but are not + # disclosed or retransmitted in the initial handshake. + "recent": compact_task_history(self.recent_tasks), + }, + sid, + ) try: async for msg in ws: if msg.type == WSMsgType.TEXT: data = json.loads(msg.data) - if data['type'] == 'close': + if data["type"] == "close": await ws.close() break - elif data['type'] == 'ping': + elif data["type"] == "ping": await self.broadcast({"type": "pong"}, sid) - elif data['type'] == 'signal_value': - request_id = data.get('request_id') + elif data["type"] == "signal_value": + request_id = data.get("request_id") if not request_id: logger.warning("[Websocket] signal_value received without request_id") continue # Resolve the pending request future if present try: - promised_sid = data.get('sid', None) + promised_sid = data.get("sid", None) future = self.pending_ws_requests.pop(request_id, None) if future is None: logger.debug(f"[Websocket] signal_value received for unknown request_id: {request_id}") continue if not future.done(): - result = data.get('value') + result = data.get("value") if promised_sid != sid: - result = { '__MODIFF_ERROR': 'sid_mismatch' } + result = {"__MODIFF_ERROR": "sid_mismatch"} future.set_result(result) except Exception as e: logger.error(f"[Websocket] Error resolving signal_value for request_id {request_id}: {e}") @@ -5264,36 +10594,52 @@ async def broadcast(self, message: dict | bytes, sid: list[str] | str = None, ex sessions = [s for s in sessions if s not in exclude] for session in sessions: + websocket = self.ws_sessions.get(session) + if websocket is None: + continue + if websocket.closed: + if self.ws_sessions.get(session) is websocket: + self.ws_sessions.pop(session, None) + continue try: - if session in self.ws_sessions and not self.ws_sessions[session].closed: - if isinstance(message, dict): - await self.ws_sessions[session].send_json(message) - else: - await self.ws_sessions[session].send_bytes(message) + if isinstance(message, dict): + await websocket.send_json(message) + else: + await websocket.send_bytes(message) except Exception as e: - logger.error(f"[Websocket] Error broadcasting message: {e}") - pass + # A browser refresh can close the transport after the `closed` + # check but before aiohttp begins the write. Prune that stale + # session immediately; otherwise every subsequent progress + # event repeats the same noisy failure until the receive loop's + # finally block gets scheduled. + if self.ws_sessions.get(session) is websocket: + self.ws_sessions.pop(session, None) + if websocket.closed or "closing transport" in str(e).lower(): + logger.debug(f"[Websocket] Dropped closing session {session}: {e}") + else: + logger.warning(f"[Websocket] Dropped failed session {session}: {e}") def queue_message(self, message: dict | bytes, sid: list[str] | str = None, exclude: list[str] | str = None): if self.loop.is_running() and not self._shutdown_event.is_set(): asyncio.run_coroutine_threadsafe( - self.background_queue.put((self.broadcast, (message, sid, exclude))), - self.loop + self.background_queue.put((self.broadcast, (message, sid, exclude))), self.loop ) def get_signal_value(self, node: str, field: str, sid: str, timeout: int = 2): try: if not sid or sid not in self.ws_sessions or self.ws_sessions[sid].closed: - return { '__MODIFF_ERROR': 'invalid_sid' } + return {"__MODIFF_ERROR": "invalid_sid"} - if not getattr(self, 'loop', None) or not self.loop.is_running(): - return { '__MODIFF_ERROR': 'server_not_running' } + if not getattr(self, "loop", None) or not self.loop.is_running(): + return {"__MODIFF_ERROR": "server_not_running"} try: running_loop = asyncio.get_running_loop() if running_loop is self.loop: - logger.warning("[Server] get_signal_value called from event loop thread; returning None to avoid deadlock.") - return { '__MODIFF_ERROR': 'called_from_event_loop' } + logger.warning( + "[Server] get_signal_value called from event loop thread; returning None to avoid deadlock." + ) + return {"__MODIFF_ERROR": "called_from_event_loop"} except RuntimeError: # No running loop in this thread; safe to proceed pass @@ -5304,13 +10650,9 @@ def get_signal_value(self, node: str, field: str, sid: str, timeout: int = 2): self.pending_ws_requests[request_id] = future # Send the request to the target client - self.queue_message({ - "type": "get_signal_value", - "request_id": request_id, - "node": node, - "field": field, - "sid": sid - }, sid) + self.queue_message( + {"type": "get_signal_value", "request_id": request_id, "node": node, "field": field, "sid": sid}, sid + ) # Await the future result from outside the event loop thread # Use run_coroutine_threadsafe to wait with a timeout safely @@ -5319,7 +10661,7 @@ def get_signal_value(self, node: str, field: str, sid: str, timeout: int = 2): try: result = cfut.result(timeout=timeout + 0.5) except Exception: - result = { '__MODIFF_ERROR': 'timeout' } + result = {"__MODIFF_ERROR": "timeout"} finally: # Cleanup any leftover pending entry self.pending_ws_requests.pop(request_id, None) @@ -5327,23 +10669,24 @@ def get_signal_value(self, node: str, field: str, sid: str, timeout: int = 2): return result except Exception as e: logger.error(f"[Server] get_signal_value error: {e}") - return { '__MODIFF_ERROR': 'exception' } + return {"__MODIFF_ERROR": "exception"} -def to_base64(type, value, options={}): +def to_base64(type, value, options=None): import io import base64 + options = options or {} out = value - if type == 'image': - format = options.get('format', 'WEBP').upper() - quality = options.get('quality') - if format == 'WEBP' and not quality: + if type == "image": + format = options.get("format", "WEBP").upper() + quality = options.get("quality") + if format == "WEBP" and not quality: quality = 100 - elif format == 'JPEG' and not quality: + elif format == "JPEG" and not quality: quality = 75 - elif format == 'PNG' and not quality: + elif format == "PNG" and not quality: quality = None mime_type = f"image/{format.lower()}" @@ -5352,26 +10695,28 @@ def to_base64(type, value, options={}): if quality is not None: save_kwargs["quality"] = int(quality) value.save(byte_arr, **save_kwargs) - # TODO: check shutil.copyfile header = f"data:{mime_type};base64," - out = header + base64.b64encode(byte_arr.getvalue()).decode('utf-8') + out = header + base64.b64encode(byte_arr.getvalue()).decode("utf-8") return out -def to_bytes(data_type, value, options={}): + +def to_bytes(data_type, value, options=None): import io + import wave from PIL import Image + options = options or {} out = value if isinstance(value, Image.Image): - format = options.get('format', 'WEBP').upper() - quality = options.get('quality') - if format == 'WEBP' and not quality: + format = options.get("format", "WEBP").upper() + quality = options.get("quality") + if format == "WEBP" and not quality: quality = 100 - elif format == 'JPEG' and not quality: + elif format == "JPEG" and not quality: quality = 75 - elif format == 'PNG' and not quality: + elif format == "PNG" and not quality: quality = None byte_arr = io.BytesIO() @@ -5380,21 +10725,51 @@ def to_bytes(data_type, value, options={}): save_kwargs["quality"] = int(quality) value.save(byte_arr, **save_kwargs) out = byte_arr.getvalue() + elif data_type == "audio" and isinstance(value, (str, os.PathLike)): + from modiff.path_identifiers import resolve_runtime_input_path + + path = resolve_runtime_input_path(value) + if path.is_file(): + out = path.read_bytes() + elif data_type == "audio" and isinstance(value, dict): + import numpy as np + + samples = value.get("samples", value.get("audio", value.get("array"))) + if samples is None: + raise ValueError("Audio output must contain samples, audio, or array data.") + if hasattr(samples, "detach"): + samples = samples.detach().float().cpu().numpy() + array = np.asarray(samples, dtype=np.float32) + if array.ndim == 1: + array = array[None, :] + elif array.ndim == 2 and array.shape[0] > array.shape[1]: + array = array.T + if array.ndim != 2: + raise ValueError(f"Audio output must be one or two dimensional; received shape {array.shape}.") + pcm = (np.clip(array, -1.0, 1.0).T * 32767.0).round().astype(" bool: + normalized = str(host or "").strip().strip("[]").lower() + if normalized == "localhost": + return True + try: + return ipaddress.ip_address(normalized).is_loopback + except ValueError: + return False + + +def _allowed_browser_origin(origin: str | None) -> bool: + """The privileged supervisor is callable only by local browser origins.""" + + if not origin: + return True + try: + parsed = urlparse(origin) + host = str(parsed.hostname or "").strip().strip("[]").lower() + except (TypeError, ValueError): + return False + if parsed.scheme not in {"http", "https"} or not host: + return False + return _loopback_host(host) + + +def _read_json(path: Path) -> dict[str, Any]: + try: + value = json.loads(path.read_text(encoding="utf-8")) + except (OSError, ValueError, TypeError): + return {} + return value if isinstance(value, dict) else {} + + +def _write_json_atomic(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_suffix(path.suffix + ".tmp") + temporary.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8") + os.replace(temporary, path) + + +def compact_task_history(tasks: Any) -> list[dict[str, Any]]: + """Return polling-safe task summaries while retaining lazy detail lookup. + + Full workflow snapshots remain in the worker's task history and are served + by ``/runs/{task_id}``. Repeating every completed graph in each `/queue` + poll makes refresh requests unnecessarily large and especially harmful on + constrained connections. + """ + if not isinstance(tasks, list): + return [] + compacted = [] + for task in tasks: + if not isinstance(task, dict): + continue + summary = {key: value for key, value in task.items() if key != "workflow_snapshot"} + runtime_fingerprint = summary.get("runtimeFingerprint") + if isinstance(runtime_fingerprint, dict): + summary["runtimeFingerprint"] = runtime_fingerprint.get("resourceFingerprint") or runtime_fingerprint.get( + "fingerprint" + ) + summary["has_workflow_snapshot"] = isinstance(task.get("workflow_snapshot"), dict) + compacted.append(summary) + return compacted + + +class SupervisorController: + """Small process-external control plane for an unresponsive model worker.""" + + def __init__(self, queue_state_path: str | os.PathLike[str]): + self.queue_state_path = Path(queue_state_path) + self._lock = threading.RLock() + self._worker: Popen[Any] | None = None + self._restart_requested = False + self._shutting_down = False + + def set_worker(self, worker: Popen[Any] | None) -> None: + with self._lock: + self._worker = worker + if worker is not None: + self._restart_requested = False + + def set_shutting_down(self) -> None: + with self._lock: + self._shutting_down = True + + def consume_restart_request(self) -> bool: + with self._lock: + requested = self._restart_requested + self._restart_requested = False + return requested + + def status(self) -> dict[str, Any]: + with self._lock: + worker = self._worker + running = bool(worker is not None and worker.poll() is None) + return { + "error": False, + "ready": running and not self._shutting_down, + "workerPid": worker.pid if worker is not None else None, + "workerRunning": running, + "restartRequested": self._restart_requested, + } + + def queue(self) -> dict[str, Any]: + state = _read_json(self.queue_state_path) + status = self.status() + worker_pid = status.get("workerPid") + recent = compact_task_history(state.get("recent")) + if not status.get("workerRunning") or state.get("workerPid") != worker_pid: + return {"queued": {}, "current": None, "recent": recent} + return { + "queued": state.get("queued") if isinstance(state.get("queued"), dict) else {}, + "current": state.get("current") if isinstance(state.get("current"), dict) else None, + "recent": recent, + } + + def stop(self) -> tuple[int, dict[str, Any]]: + with self._lock: + worker = self._worker + if worker is None or worker.poll() is not None: + return HTTPStatus.CONFLICT, { + "error": True, + "message": "No backend worker is currently running.", + } + if self._shutting_down: + return HTTPStatus.CONFLICT, { + "error": True, + "message": "The backend is already shutting down.", + } + + raw_state = _read_json(self.queue_state_path) + snapshot_matches_worker = raw_state.get("workerPid") == worker.pid + queue = self.queue() + current = queue.get("current") + queued = queue.get("queued") if isinstance(queue.get("queued"), dict) else {} + if snapshot_matches_worker and current is None and not queued: + return HTTPStatus.CONFLICT, { + "error": True, + "message": "Nothing to do. No task is currently running or queued.", + } + + self._restart_requested = True + worker_pid = worker.pid + try: + worker.kill() + except ProcessLookupError: + pass + + cancelled = [] + if isinstance(current, dict): + cancelled.append( + { + **current, + "status": "cancelled", + "completed_at": time.time(), + "message": "Execution stopped by the supervisor control plane.", + } + ) + cancelled.extend( + { + **task, + "status": "cancelled", + "completed_at": time.time(), + "message": "Cancelled before execution.", + } + for task in queued.values() + if isinstance(task, dict) + ) + # `queue()` intentionally strips completed workflow snapshots for + # lightweight polling. Persist from the raw state so Stop never + # destroys the lazy `/runs/{task_id}` recovery data. + prior_recent = raw_state.get("recent") if isinstance(raw_state.get("recent"), list) else [] + _write_json_atomic( + self.queue_state_path, + { + "workerPid": worker_pid, + "updatedAt": time.time(), + "queued": {}, + "current": None, + "recent": [*cancelled, *prior_recent][:30], + }, + ) + return HTTPStatus.OK, { + "error": False, + "message": "Execution stopped. The backend worker is restarting to release RAM and VRAM.", + "task_id": current.get("task_id") if isinstance(current, dict) else None, + "cancelled_queued_task_ids": list(queued), + "cleanup_pending": True, + "backend_restart": True, + "snapshot_recovery": not snapshot_matches_worker, + } + + +def _handler(controller: SupervisorController): + class SupervisorControlHandler(BaseHTTPRequestHandler): + server_version = "MoDiffSupervisorControl/1" + + def log_message(self, format: str, *args: object) -> None: + logger.debug("Supervisor control: " + format, *args) + + def _trusted_request_boundary(self) -> bool: + try: + request_host = urlparse(f"//{self.headers.get('Host', '')}").hostname + peer_host = self.client_address[0] + except (AttributeError, IndexError, TypeError, ValueError): + return False + return _loopback_host(request_host) and _loopback_host(peer_host) + + def _reject_untrusted_boundary(self) -> bool: + if self._trusted_request_boundary(): + return False + self._send( + HTTPStatus.FORBIDDEN, + {"error": True, "message": "Supervisor requests require a literal loopback Host and peer."}, + ) + return True + + def _send(self, status: int, payload: dict[str, Any]) -> None: + body = json.dumps(payload).encode("utf-8") + self.send_response(status) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + origin = self.headers.get("Origin") + if origin and _allowed_browser_origin(origin): + self.send_header("Access-Control-Allow-Origin", origin) + self.send_header("Vary", "Origin") + self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + self.send_header("Access-Control-Allow-Headers", "Content-Type") + self.send_header("Cache-Control", "no-store") + self.end_headers() + self.wfile.write(body) + + def do_OPTIONS(self) -> None: + if self._reject_untrusted_boundary(): + return + if not _allowed_browser_origin(self.headers.get("Origin")): + self._send(HTTPStatus.FORBIDDEN, {"error": True, "message": "Cross-site requests are not allowed."}) + return + self._send(HTTPStatus.NO_CONTENT, {}) + + def do_GET(self) -> None: + if self._reject_untrusted_boundary(): + return + if self.path == "/health": + self._send(HTTPStatus.OK, controller.status()) + return + if self.path == "/queue": + self._send(HTTPStatus.OK, controller.queue()) + return + self._send(HTTPStatus.NOT_FOUND, {"error": True, "message": "Control route not found."}) + + def do_POST(self) -> None: + if self._reject_untrusted_boundary(): + return + if not _allowed_browser_origin(self.headers.get("Origin")): + self._send(HTTPStatus.FORBIDDEN, {"error": True, "message": "Cross-site requests are not allowed."}) + return + if self.path == "/stop": + status, payload = controller.stop() + self._send(status, payload) + return + self._send(HTTPStatus.NOT_FOUND, {"error": True, "message": "Control route not found."}) + + return SupervisorControlHandler + + +class SupervisorControlServer: + def __init__(self, controller: SupervisorController, host: str, port: int): + self.controller = controller + self.server = ThreadingHTTPServer((host, port), _handler(controller)) + self.thread = threading.Thread(target=self.server.serve_forever, name="modiff-supervisor-control", daemon=True) + + def start(self) -> None: + self.thread.start() + + def close(self) -> None: + self.server.shutdown() + self.server.server_close() + self.thread.join(timeout=2) diff --git a/modiff/workflow_store.py b/modiff/workflow_store.py new file mode 100644 index 0000000..12b1b7a --- /dev/null +++ b/modiff/workflow_store.py @@ -0,0 +1,91 @@ +"""Backend-authoritative saved workflow documents for every local frontend.""" + +from __future__ import annotations + +import json +import re +import threading +import time +from pathlib import Path +from typing import Any + + +_LOCK = threading.RLock() +_ID = re.compile(r"^[A-Za-z0-9_-]{1,96}$") + + +def _root(data_dir: str | Path) -> Path: + return Path(data_dir) / "user-workflows" + + +def _workflow_id(value: Any) -> str: + identifier = str(value or "") + if not _ID.fullmatch(identifier): + raise ValueError("Workflow id must contain only letters, numbers, underscores, and hyphens.") + return identifier + + +def _path(data_dir: str | Path, workflow_id: Any) -> Path: + return _root(data_dir) / f"{_workflow_id(workflow_id)}.json" + + +def _read(path: Path) -> dict[str, Any]: + value = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(value, dict) or not isinstance(value.get("snapshot"), dict): + raise ValueError(f"Saved workflow {path.name} is invalid.") + return value + + +def list_workflows(data_dir: str | Path) -> list[dict[str, Any]]: + root = _root(data_dir) + if not root.exists(): + return [] + with _LOCK: + records = [] + for path in root.glob("*.json"): + try: + records.append(_read(path)) + except (OSError, ValueError, json.JSONDecodeError): + continue + return sorted(records, key=lambda item: float(item.get("updatedAt") or 0), reverse=True) + + +def get_workflow(data_dir: str | Path, workflow_id: Any) -> dict[str, Any] | None: + path = _path(data_dir, workflow_id) + if not path.is_file(): + return None + with _LOCK: + return _read(path) + + +def save_workflow(data_dir: str | Path, workflow_id: Any, payload: Any) -> dict[str, Any]: + if not isinstance(payload, dict) or not isinstance(payload.get("snapshot"), dict): + raise ValueError("Saved workflow payload requires a snapshot object.") + path = _path(data_dir, workflow_id) + now = int(time.time() * 1000) + with _LOCK: + existing = _read(path) if path.is_file() else None + record = { + "id": _workflow_id(workflow_id), + "title": str(payload.get("title") or "Workflow").strip()[:160] or "Workflow", + "snapshot": payload["snapshot"], + "source": payload.get("source") if isinstance(payload.get("source"), str) else "manual", + "sourceLabel": payload.get("sourceLabel") if isinstance(payload.get("sourceLabel"), str) else None, + "createdAt": int((existing or {}).get("createdAt") or payload.get("createdAt") or now), + "updatedAt": now, + "revision": int((existing or {}).get("revision") or 0) + 1, + "clientId": str(payload.get("clientId") or ""), + } + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_suffix(".tmp") + temporary.write_text(json.dumps(record, ensure_ascii=False, separators=(",", ":")), encoding="utf-8") + temporary.replace(path) + return record + + +def delete_workflow(data_dir: str | Path, workflow_id: Any) -> bool: + path = _path(data_dir, workflow_id) + with _LOCK: + existed = path.is_file() + path.unlink(missing_ok=True) + return existed diff --git a/modules/Audio/__init__.py b/modules/Audio/__init__.py index 15b6a64..216bacc 100644 --- a/modules/Audio/__init__.py +++ b/modules/Audio/__init__.py @@ -1 +1 @@ -from .main import * +from .main import * # noqa: F403 diff --git a/modules/Audio/main.py b/modules/Audio/main.py index 8c5fb9d..51e5c52 100644 --- a/modules/Audio/main.py +++ b/modules/Audio/main.py @@ -1,13 +1,35 @@ import logging +import json +import re +import subprocess +import tempfile from pathlib import Path import numpy as np from modiff.NodeBase import NodeBase from modiff.config import CONFIG +from modiff.path_identifiers import resolve_runtime_input_path from utils.paths import parse_filename logger = logging.getLogger("modiff") +AUDIO_SAMPLE_RATE_OPTIONS = { + "44100": "44.1 kHz", + "48000": "48 kHz", + "88200": "88.2 kHz", + "96000": "96 kHz", +} + + +def _pcm_to_float32(array): + if array.dtype.kind == "u": + midpoint = float(np.iinfo(array.dtype).max + 1) / 2.0 + return (array.astype(np.float32) - midpoint) / midpoint + if array.dtype.kind == "i": + limits = np.iinfo(array.dtype) + scale = float(max(abs(int(limits.min)), abs(int(limits.max)))) + return array.astype(np.float32) / scale + return array.astype(np.float32, copy=False) def _resolve_file(value): @@ -15,10 +37,7 @@ def _resolve_file(value): file = str(file or "") if not file: return None - path = Path(file) - if not path.is_absolute(): - path = Path(CONFIG.paths["work_dir"]) / path - return path + return resolve_runtime_input_path(file) def _collapse_single(values): @@ -41,12 +60,9 @@ def _audio_to_numpy(audio): if isinstance(data, torch.Tensor): array = data.detach().float().cpu().numpy() elif isinstance(data, np.ndarray): - if data.dtype.kind in ("i", "u"): - array = data.astype(np.float32) / float(np.iinfo(data.dtype).max) - else: - array = data.astype(np.float32, copy=False) + array = _pcm_to_float32(data) elif isinstance(data, str): - loaded = _read_wav(Path(data)) + loaded = _read_wav(resolve_runtime_input_path(data)) return loaded["samples"], loaded["sample_rate"] else: array = np.asarray(data, dtype=np.float32) @@ -65,11 +81,7 @@ def _read_wav(path): sample_rate, data = wavfile.read(path) array = np.asarray(data) - if array.dtype.kind in ("i", "u"): - max_value = np.iinfo(array.dtype).max - array = array.astype(np.float32) / float(max_value) - else: - array = array.astype(np.float32) + array = _pcm_to_float32(array) if array.ndim == 1: channels = 1 else: @@ -96,6 +108,152 @@ def _write_wav(path, samples, sample_rate): wavfile.write(path, int(sample_rate), pcm) +def _loudnorm_filter(target_lufs, target_lra, target_peak_dbfs): + options = [ + f"I={float(target_lufs):.2f}", + f"LRA={float(target_lra):.2f}", + f"TP={float(target_peak_dbfs):.2f}", + ] + options.append("print_format=json") + return "loudnorm=" + ":".join(options) + + +def _parse_loudnorm_measurement(stderr): + matches = re.findall(r"\{\s*\"input_i\".*?\}", stderr, flags=re.DOTALL) + if not matches: + raise RuntimeError("FFmpeg did not return a loudness measurement.") + payload = json.loads(matches[-1]) + required = ("input_i", "input_lra", "input_tp", "input_thresh", "target_offset") + measurement = {} + for key in required: + value = float(payload[key]) + if not np.isfinite(value): + raise ValueError("Audio is silent or too short to measure loudness.") + measurement[key] = value + return measurement + + +def _measure_loudness(samples, sample_rate, target_lufs=-16.0, target_lra=7.0, target_peak_dbfs=-1.0): + from imageio_ffmpeg import get_ffmpeg_exe + + with tempfile.TemporaryDirectory(prefix="modiff-audio-loudness-") as temporary_dir: + source_path = Path(temporary_dir) / "source.wav" + _write_wav(source_path, samples, sample_rate) + result = subprocess.run( + [ + get_ffmpeg_exe(), + "-hide_banner", + "-nostdin", + "-i", + str(source_path), + "-af", + _loudnorm_filter(target_lufs, target_lra, target_peak_dbfs), + "-f", + "null", + "-", + ], + check=False, + capture_output=True, + ) + stderr = result.stderr.decode("utf-8", errors="replace") + if result.returncode != 0: + raise RuntimeError(f"FFmpeg loudness analysis failed: {stderr.strip() or 'unknown error'}") + return _parse_loudnorm_measurement(stderr) + + +def _atempo_factors(tempo_ratio): + """Split a tempo ratio into conservative FFmpeg atempo stages.""" + + ratio = float(tempo_ratio) + if not np.isfinite(ratio) or ratio <= 0: + raise ValueError("Audio tempo ratio must be a finite positive number.") + + factors = [] + while ratio > 2.0: + factors.append(2.0) + ratio /= 2.0 + while ratio < 0.5: + factors.append(0.5) + ratio /= 0.5 + if not factors or abs(ratio - 1.0) > 1e-9: + factors.append(ratio) + return factors + + +def _run_pitch_preserving_stretch(samples, sample_rate, tempo_ratio): + """Time-stretch audio through FFmpeg while preserving its pitch.""" + + from imageio_ffmpeg import get_ffmpeg_exe + + rubberband_filter = ( + f"rubberband=tempo={tempo_ratio:.15f}:pitch=1:" + "transients=crisp:detector=compound:phase=laminar:window=standard:" + "smoothing=on:formant=preserved:pitchq=quality:channels=together" + ) + atempo_filter = ",".join(f"atempo={factor:.15f}" for factor in _atempo_factors(tempo_ratio)) + + with tempfile.TemporaryDirectory(prefix="modiff-audio-fit-") as temporary_dir: + temporary_root = Path(temporary_dir) + source_path = temporary_root / "source.wav" + output_path = temporary_root / "stretched.wav" + _write_wav(source_path, samples, sample_rate) + + command_prefix = [ + get_ffmpeg_exe(), + "-y", + "-v", + "error", + "-i", + str(source_path), + "-map", + "0:a:0", + ] + command_suffix = [ + "-ar", + str(sample_rate), + "-c:a", + "pcm_s16le", + "-f", + "wav", + str(output_path), + ] + errors = [] + for engine, audio_filter in (("rubberband", rubberband_filter), ("atempo", atempo_filter)): + output_path.unlink(missing_ok=True) + command = [*command_prefix, "-af", audio_filter, *command_suffix] + result = subprocess.run(command, check=False, capture_output=True) + if result.returncode == 0 and output_path.is_file(): + loaded = _read_wav(output_path) + return loaded["samples"], engine + errors.append(result.stderr.decode("utf-8", errors="replace").strip()) + if engine == "rubberband": + logger.warning("FFmpeg rubberband filter is unavailable; using the atempo fallback.") + + detail = next((error for error in reversed(errors) if error), "unknown FFmpeg error") + raise RuntimeError(f"Pitch-preserving audio fit failed: {detail}") + + +def _apply_half_cosine_fades(samples, sample_rate, fade_in_seconds, fade_out_seconds, content_start, content_end): + output = np.asarray(samples, dtype=np.float32).copy() + content_start = max(0, min(int(content_start), output.shape[0])) + content_end = max(content_start, min(int(content_end), output.shape[0])) + content_frames = content_end - content_start + + fade_in_frames = min(content_frames, round(max(0.0, float(fade_in_seconds)) * sample_rate)) + if fade_in_frames > 0: + phase = np.arange(fade_in_frames, dtype=np.float64) / max(1, fade_in_frames - 1) + gain = np.sin((np.pi / 2) * phase).astype(np.float32) + output[content_start : content_start + fade_in_frames] *= gain[:, None] + + fade_out_frames = min(content_frames, round(max(0.0, float(fade_out_seconds)) * sample_rate)) + if fade_out_frames > 0: + phase = np.arange(fade_out_frames, dtype=np.float64) / max(1, fade_out_frames - 1) + gain = np.cos((np.pi / 2) * phase).astype(np.float32) + output[content_end - fade_out_frames : content_end] *= gain[:, None] + + return output + + class Load(NodeBase): """Load an audio file as a reusable audio object.""" @@ -125,7 +283,9 @@ def execute(self, **kwargs): if path is None or not path.exists(): raise ValueError("Load Audio needs an existing audio file.") if path.suffix.lower() != ".wav": - raise ValueError("Load Audio currently supports WAV files. Convert source audio to WAV before loading.") + from modiff.media_import import audio_as_wav + + path = audio_as_wav(path) loaded = _read_wav(path) return { @@ -187,6 +347,327 @@ def execute(self, **kwargs): return {"output": output, "sample_rate": output["sample_rate"], "duration": output["duration_seconds"]} +class FitDuration(NodeBase): + """Fit a source window to an exact timeline without changing pitch.""" + + label = "Fit Audio Duration" + category = "Audio" + resizable = True + params = { + "audio": {"label": "Audio", "display": "input", "type": ["audio", "str"]}, + "source_start_seconds": {"label": "Source Start", "type": "float", "default": 0.0, "min": 0, "step": 0.001}, + "source_duration_seconds": { + "label": "Source Duration", + "type": "float", + "default": 0.0, + "min": 0, + "step": 0.001, + }, + "target_duration_seconds": { + "label": "Target Duration", + "type": "float", + "default": 5.0, + "min": 0.001, + "step": 0.001, + }, + "delay_seconds": {"label": "Delay", "type": "float", "default": 0.0, "step": 0.001}, + "target_sample_rate": {"label": "Target SR", "type": "int", "default": 48000, "min": 8000, "max": 192000}, + "fade_in_seconds": {"label": "Fade In", "type": "float", "default": 0.0, "min": 0, "step": 0.001}, + "fade_out_seconds": {"label": "Fade Out", "type": "float", "default": 0.0, "min": 0, "step": 0.001}, + "output": {"label": "Audio", "display": "output", "type": "audio"}, + "sample_rate": {"label": "Sample Rate", "display": "output", "type": "int"}, + "duration": {"label": "Duration", "display": "output", "type": "float"}, + "tempo_ratio": {"label": "Tempo Ratio", "display": "output", "type": "float"}, + "stretch_engine": {"label": "Stretch Engine", "display": "output", "type": "str"}, + } + + def execute(self, **kwargs): + from math import gcd + + from scipy.signal import resample_poly + + if kwargs.get("audio") is None: + raise ValueError("Fit Audio Duration needs audio input.") + + samples, sample_rate = _audio_to_numpy(kwargs.get("audio")) + target_sample_rate = int(kwargs.get("target_sample_rate") or sample_rate) + if target_sample_rate < 8000 or target_sample_rate > 192000: + raise ValueError("Target sample rate must be between 8000 and 192000 Hz.") + if target_sample_rate != sample_rate: + divisor = gcd(sample_rate, target_sample_rate) + samples = resample_poly( + samples, + target_sample_rate // divisor, + sample_rate // divisor, + axis=0, + ).astype(np.float32) + sample_rate = target_sample_rate + + source_start = max(0, round(float(kwargs.get("source_start_seconds") or 0) * sample_rate)) + if source_start >= samples.shape[0]: + raise ValueError("Source start is outside the input audio.") + + source_duration_seconds = float(kwargs.get("source_duration_seconds") or 0) + if source_duration_seconds > 0: + source_frames = max(1, round(source_duration_seconds * sample_rate)) + source_end = source_start + source_frames + selected = samples[source_start : min(source_end, samples.shape[0])] + if selected.shape[0] < source_frames: + padding = np.zeros((source_frames - selected.shape[0], selected.shape[1]), dtype=np.float32) + selected = np.concatenate([selected, padding], axis=0) + else: + selected = samples[source_start:] + source_frames = selected.shape[0] + + target_duration_seconds = float(kwargs.get("target_duration_seconds") or 0) + if not np.isfinite(target_duration_seconds) or target_duration_seconds <= 0: + raise ValueError("Target duration must be a finite positive number.") + target_frames = max(1, round(target_duration_seconds * sample_rate)) + tempo_ratio = source_frames / target_frames + + if source_frames == target_frames: + fitted = selected.astype(np.float32, copy=True) + stretch_engine = "none" + else: + fitted, stretch_engine = _run_pitch_preserving_stretch(selected, sample_rate, tempo_ratio) + + if fitted.ndim == 1: + fitted = fitted[:, None] + if fitted.shape[0] < target_frames: + padding = np.zeros((target_frames - fitted.shape[0], fitted.shape[1]), dtype=np.float32) + fitted = np.concatenate([fitted, padding], axis=0) + else: + fitted = fitted[:target_frames] + + delay_frames = round(float(kwargs.get("delay_seconds") or 0) * sample_rate) + shifted = np.zeros((target_frames, fitted.shape[1]), dtype=np.float32) + if delay_frames >= 0: + retained_frames = max(0, target_frames - delay_frames) + if retained_frames > 0: + shifted[delay_frames : delay_frames + retained_frames] = fitted[:retained_frames] + content_start = min(delay_frames, target_frames) + content_end = target_frames + else: + source_offset = min(-delay_frames, target_frames) + retained_frames = target_frames - source_offset + if retained_frames > 0: + shifted[:retained_frames] = fitted[source_offset:] + content_start = 0 + content_end = retained_frames + + shifted = _apply_half_cosine_fades( + shifted, + sample_rate, + kwargs.get("fade_in_seconds") or 0, + kwargs.get("fade_out_seconds") or 0, + content_start, + content_end, + ) + shifted = np.clip(shifted, -1.0, 1.0) + output = { + "samples": shifted, + "sample_rate": int(sample_rate), + "channels": int(shifted.shape[1]), + "duration_seconds": target_frames / sample_rate, + } + return { + "output": output, + "sample_rate": output["sample_rate"], + "duration": output["duration_seconds"], + "tempo_ratio": float(tempo_ratio), + "stretch_engine": stretch_engine, + } + + +class MatchLoudness(NodeBase): + """Match generated audio to a reference window without changing its dynamics.""" + + label = "Match Audio Loudness" + category = "Audio" + resizable = True + params = { + "audio": {"label": "Audio", "display": "input", "type": ["audio", "str"]}, + "reference": {"label": "Reference", "display": "input", "type": ["audio", "str"]}, + "reference_window_seconds": { + "label": "Reference Tail", + "type": "float", + "default": 15.0, + "min": 0, + "step": 0.1, + }, + "target_peak_dbfs": { + "label": "Peak Ceiling", + "type": "float", + "default": -1.0, + "min": -9.0, + "max": 0.0, + "step": 0.1, + }, + "max_adjustment_db": { + "label": "Max Adjustment", + "type": "float", + "default": 12.0, + "min": 0.0, + "max": 30.0, + "step": 0.5, + }, + "output": {"label": "Audio", "display": "output", "type": "audio"}, + "reference_lufs": {"label": "Reference LUFS", "display": "output", "type": "float"}, + "input_lufs": {"label": "Input LUFS", "display": "output", "type": "float"}, + "output_lufs": {"label": "Output LUFS", "display": "output", "type": "float"}, + "adjustment_db": {"label": "Adjustment", "display": "output", "type": "float"}, + "true_peak_dbfs": {"label": "True Peak", "display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + if kwargs.get("audio") is None: + raise ValueError("Match Audio Loudness needs generated audio.") + if kwargs.get("reference") is None: + raise ValueError("Match Audio Loudness needs reference audio.") + + samples, sample_rate = _audio_to_numpy(kwargs.get("audio")) + reference, reference_sample_rate = _audio_to_numpy(kwargs.get("reference")) + window_seconds = max(0.0, float(kwargs.get("reference_window_seconds") or 0)) + if window_seconds > 0: + window_frames = max(1, round(window_seconds * reference_sample_rate)) + reference = reference[-window_frames:] + + target_peak_dbfs = min(0.0, max(-9.0, float(kwargs.get("target_peak_dbfs") or -1.0))) + max_adjustment_db = min(30.0, max(0.0, float(kwargs.get("max_adjustment_db") or 0.0))) + reference_measurement = _measure_loudness( + reference, + reference_sample_rate, + target_peak_dbfs=target_peak_dbfs, + ) + reference_lufs = min(-5.0, max(-70.0, reference_measurement["input_i"])) + input_measurement = _measure_loudness( + samples, + sample_rate, + target_lufs=reference_lufs, + target_peak_dbfs=target_peak_dbfs, + ) + original_input_lufs = input_measurement["input_i"] + requested_gain_db = min( + max_adjustment_db, + max(-max_adjustment_db, reference_lufs - original_input_lufs), + ) + # Continuation matching must not behave like an automatic gain + # controller. FFmpeg's dynamic loudnorm mode can apply very different + # gain to consecutive phrases (and audibly pump quiet endings). Apply + # one constant gain to every sample instead, capped so the measured + # true peak remains below the requested ceiling. + peak_limited_gain_db = target_peak_dbfs - input_measurement["input_tp"] + applied_gain_db = min(requested_gain_db, peak_limited_gain_db) + matched = samples * (10.0 ** (applied_gain_db / 20.0)) + if matched.ndim == 1: + matched = matched[:, None] + matched = np.clip(matched, -1.0, 1.0) + output_measurement = _measure_loudness( + matched, + sample_rate, + target_lufs=reference_lufs, + target_peak_dbfs=target_peak_dbfs, + ) + output = { + "samples": matched, + "sample_rate": int(sample_rate), + "channels": int(matched.shape[1] if matched.ndim == 2 else 1), + "duration_seconds": float(matched.shape[0] / sample_rate) if sample_rate else 0.0, + } + return { + "output": output, + "reference_lufs": float(reference_lufs), + "input_lufs": float(original_input_lufs), + "output_lufs": float(output_measurement["input_i"]), + "adjustment_db": float(output_measurement["input_i"] - original_input_lufs), + "true_peak_dbfs": float(output_measurement["input_tp"]), + } + + +class Join(NodeBase): + """Append a continuation to its source while preserving exact duration.""" + + label = "Join Audio" + category = "Audio" + resizable = True + params = { + "source": {"label": "Source", "display": "input", "type": ["audio", "str"]}, + "continuation": {"label": "Continuation", "display": "input", "type": ["audio", "str"]}, + "boundary_fade_seconds": { + "label": "Boundary Fade", + "type": "float", + "default": 0.01, + "min": 0.0, + "max": 1.0, + "step": 0.001, + }, + "output": {"label": "Audio", "display": "output", "type": "audio"}, + "sample_rate": {"label": "Sample Rate", "display": "output", "type": "int"}, + "duration": {"label": "Duration", "display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + from math import gcd + + from scipy.signal import resample_poly + + if kwargs.get("source") is None: + raise ValueError("Join Audio needs source audio.") + if kwargs.get("continuation") is None: + raise ValueError("Join Audio needs continuation audio.") + + source, sample_rate = _audio_to_numpy(kwargs.get("source")) + continuation, continuation_sample_rate = _audio_to_numpy(kwargs.get("continuation")) + if continuation_sample_rate != sample_rate: + divisor = gcd(sample_rate, continuation_sample_rate) + continuation = resample_poly( + continuation, + sample_rate // divisor, + continuation_sample_rate // divisor, + axis=0, + ).astype(np.float32) + + source_channels = source.shape[1] + continuation_channels = continuation.shape[1] + if source_channels != continuation_channels: + if source_channels == 1: + source = np.repeat(source, continuation_channels, axis=1) + elif continuation_channels == 1: + continuation = np.repeat(continuation, source_channels, axis=1) + else: + raise ValueError( + f"Join Audio cannot combine {source_channels}-channel source " + f"with {continuation_channels}-channel continuation." + ) + + fade_frames = min( + source.shape[0], + continuation.shape[0], + round(max(0.0, float(kwargs.get("boundary_fade_seconds") or 0)) * sample_rate), + ) + if fade_frames > 0: + phase = np.arange(fade_frames, dtype=np.float64) / max(1, fade_frames - 1) + source_fade = np.cos((np.pi / 2) * phase).astype(np.float32) + continuation_fade = np.sin((np.pi / 2) * phase).astype(np.float32) + source = source.copy() + continuation = continuation.copy() + source[-fade_frames:] *= source_fade[:, None] + continuation[:fade_frames] *= continuation_fade[:, None] + + joined = np.concatenate([source, continuation], axis=0) + output = { + "samples": np.clip(joined, -1.0, 1.0), + "sample_rate": int(sample_rate), + "channels": int(joined.shape[1]), + "duration_seconds": float(joined.shape[0] / sample_rate) if sample_rate else 0.0, + } + return { + "output": output, + "sample_rate": output["sample_rate"], + "duration": output["duration_seconds"], + } + + class Export(NodeBase): """Save audio to a WAV file and expose a preview.""" @@ -200,18 +681,38 @@ class Export(NodeBase): "type": "str", "default": "{PATH:audio}/MoDiff_{HASH:6}.wav", }, - "sample_rate": {"label": "Sample Rate", "type": "int", "default": 48000, "min": 8000, "max": 192000}, + "sample_rate": { + "label": "Export Sample Rate", + "type": "int", + "default": 48000, + "options": AUDIO_SAMPLE_RATE_OPTIONS, + }, "preview": {"display": "ui_audio", "type": "url", "dataSource": "file"}, "file": {"label": "File", "display": "output", "type": "audio"}, "duration_seconds": {"label": "Duration", "display": "output", "type": "float"}, } def execute(self, **kwargs): + from math import gcd + + from scipy.signal import resample_poly + audio = kwargs.get("audio") if audio is None: raise ValueError("Export Audio needs audio input.") samples, detected_sample_rate = _audio_to_numpy(audio) sample_rate = int(kwargs.get("sample_rate") or detected_sample_rate or 48000) + if sample_rate not in {int(value) for value in AUDIO_SAMPLE_RATE_OPTIONS}: + supported = ", ".join(AUDIO_SAMPLE_RATE_OPTIONS.values()) + raise ValueError(f"Export sample rate must be one of: {supported}.") + if sample_rate != detected_sample_rate: + divisor = gcd(detected_sample_rate, sample_rate) + samples = resample_poly( + samples, + sample_rate // divisor, + detected_sample_rate // divisor, + axis=0, + ).astype(np.float32) parsed_filename = Path(parse_filename(kwargs.get("filename") or "{PATH:audio}/MoDiff_{HASH:6}.wav")) if not parsed_filename.is_absolute(): diff --git a/modules/Color/main.py b/modules/Color/main.py index e61141b..12952e3 100644 --- a/modules/Color/main.py +++ b/modules/Color/main.py @@ -1,6 +1,7 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. from modiff.NodeBase import NodeBase -from PIL import Image, ImageOps +from PIL import ImageOps class Invert(NodeBase): """ diff --git a/modules/DiffusersAdapters/__init__.py b/modules/DiffusersAdapters/__init__.py new file mode 100644 index 0000000..216bacc --- /dev/null +++ b/modules/DiffusersAdapters/__init__.py @@ -0,0 +1 @@ +from .main import * # noqa: F403 diff --git a/modules/DiffusersAdapters/main.py b/modules/DiffusersAdapters/main.py new file mode 100644 index 0000000..9c95b43 --- /dev/null +++ b/modules/DiffusersAdapters/main.py @@ -0,0 +1,384 @@ +"""Model-neutral Diffusers LoRA inspection and lifecycle nodes.""" + +import json +from pathlib import Path +from typing import Any + +from modiff.NodeBase import NodeBase + + +def _string_list(value: Any) -> list[str]: + if value in (None, ""): + return [] + if isinstance(value, str): + values = value.replace("\n", ",").split(",") + elif isinstance(value, (list, tuple, set)): + values = value + else: + values = [value] + return [str(item).strip() for item in values if str(item).strip()] + + +def _resolve_local_adapter(selection: Any, weight_name: str | None = None) -> tuple[Path, str | None]: + if isinstance(selection, dict): + value = str(selection.get("value") or "").strip() + source = selection.get("source") or "hub" + else: + value = str(selection or "").strip() + source = "local" if Path(value).expanduser().exists() else "hub" + if not value: + raise ValueError("A LoRA adapter is required.") + weight_name = str(weight_name or "").strip() or None + if source == "hub": + from utils.huggingface import cached_file_path + + repo_id = value + if not weight_name: + parts = value.split("/") + if len(parts) >= 3: + repo_id, weight_name = "/".join(parts[:2]), "/".join(parts[2:]) + if not weight_name: + raise ValueError("A Hub LoRA needs a pinned weight name installed through Model Manager.") + cached = cached_file_path(repo_id, weight_name) + if not cached: + raise FileNotFoundError(f"LoRA {repo_id}/{weight_name} is not installed.") + path = Path(cached) + return path.parent, path.name + path = Path(value).expanduser() + if path.is_file(): + return path.parent, path.name + if not path.is_dir(): + raise FileNotFoundError(f"LoRA path does not exist: {path}") + return path, weight_name + + +def inspect_lora_file(path: Path, *, base_model: str = "") -> dict[str, Any]: + from safetensors import safe_open + + if path.suffix.lower() != ".safetensors": + raise ValueError("LoRA inspection currently requires a Safetensors weight file.") + ranks = set() + targets = set() + key_count = 0 + with safe_open(path, framework="pt", device="cpu") as handle: + metadata = dict(handle.metadata() or {}) + for key in handle.keys(): + key_count += 1 + normalized = str(key) + if ".lora_A." in normalized or ".lora_down." in normalized: + shape = tuple(handle.get_slice(key).get_shape()) + if shape: + ranks.add(int(shape[0])) + prefix = normalized.split(".", 1)[0] + if prefix in {"transformer", "unet", "text_encoder", "text_encoder_2"}: + targets.add(prefix) + elif normalized.startswith("lora_unet_"): + targets.add("unet") + elif normalized.startswith(("lora_te_", "lora_te1_")): + targets.add("text_encoder") + elif normalized.startswith("lora_te2_"): + targets.add("text_encoder_2") + declared_base = next( + (metadata.get(key) for key in ("ss_base_model_version", "modelspec.architecture", "base_model") if metadata.get(key)), + None, + ) + requested_base = str(base_model or "").strip() or None + compatibility = "unknown" + compatibility_reason = "Safetensors keys and metadata cannot prove base-model compatibility." + if requested_base and declared_base: + compatibility = "declared_match" if requested_base.lower() in str(declared_base).lower() else "declared_mismatch" + compatibility_reason = f"Adapter declares {declared_base!r}; requested base is {requested_base!r}." + triggers = _string_list(metadata.get("ss_tag_frequency") or metadata.get("trigger_words")) + return { + "schema_version": 1, + "path": str(path), + "size_bytes": path.stat().st_size, + "tensor_count": key_count, + "ranks": sorted(ranks), + "target_components": sorted(targets), + "declared_base_model": declared_base, + "requested_base_model": requested_base, + "compatibility": compatibility, + "compatibility_reason": compatibility_reason, + "trigger_words": triggers, + "metadata": metadata, + } + + +def _adapter_descriptor(value: Any) -> dict[str, Any]: + if not isinstance(value, dict): + raise TypeError("LoRA operations need adapter objects from a LoRA node.") + required = ("lora_path", "adapter_name") + missing = [name for name in required if not value.get(name)] + if missing: + raise ValueError(f"LoRA adapter is missing: {', '.join(missing)}.") + return dict(value) + + +def active_adapter_names(pipeline: Any) -> set[str]: + getter = getattr(pipeline, "get_list_adapters", None) + if not callable(getter): + return set() + listed = getter() or {} + if isinstance(listed, dict): + return {str(name) for names in listed.values() for name in (names or [])} + return set(_string_list(listed)) + + +def apply_lora_mix(pipeline: Any, adapters: Any) -> dict[str, Any]: + if pipeline is None: + raise ValueError("LoRA Stack / Mix needs a pipeline.") + values = adapters if isinstance(adapters, list) else [adapters] + values = [_adapter_descriptor(item) for item in values if item is not None] + if not values: + raise ValueError("LoRA Stack / Mix needs at least one adapter.") + load = getattr(pipeline, "load_lora_weights", None) + activate = getattr(pipeline, "set_adapters", None) + if not callable(load) or not callable(activate): + raise ValueError("This Diffusers pipeline does not expose the multi-adapter LoRA API.") + loaded = active_adapter_names(pipeline) + names = [] + weights = [] + for adapter in values: + name = str(adapter["adapter_name"]) + if name not in loaded: + load_kwargs = {"adapter_name": name} + if adapter.get("weight_name"): + load_kwargs["weight_name"] = adapter["weight_name"] + load(adapter["lora_path"], **load_kwargs) + loaded.add(name) + names.append(name) + weights.append(float(adapter.get("scale", 1.0))) + activate(names, weights) + return {"adapter_names": names, "adapter_weights": weights} + + +class LoRAInspectValidate(NodeBase): + label = "Inspect / Validate Diffusers LoRA" + category = "Diffusers Adapters" + resizable = True + params = { + "adapter": {"label": "Adapter", "display": "modelselect", "type": "string", "fieldOptions": {"noValidation": True, "sources": ["hub", "local"]}}, + "weight_name": {"label": "Weight Name", "type": "string", "default": ""}, + "base_model": {"label": "Expected Base Model", "type": "string", "default": ""}, + "report": {"label": "Inspection", "display": "output", "type": "string"}, + "compatibility": {"label": "Compatibility", "display": "output", "type": "string"}, + "rank": {"label": "Maximum Rank", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + directory, weight_name = _resolve_local_adapter(kwargs.get("adapter"), kwargs.get("weight_name")) + if not weight_name: + candidates = sorted(directory.glob("*.safetensors")) + if len(candidates) != 1: + raise ValueError("Choose a weight name when the adapter directory does not contain exactly one Safetensors file.") + path = candidates[0] + else: + path = directory / weight_name + if not path.is_file(): + raise FileNotFoundError(f"LoRA weight file does not exist: {path}") + report = inspect_lora_file(path, base_model=kwargs.get("base_model") or "") + return { + "report": json.dumps(report, sort_keys=True), + "compatibility": report["compatibility"], + "rank": max(report["ranks"], default=0), + } + + +class LoRAStackMix(NodeBase): + label = "Diffusers LoRA Stack / Mix" + category = "Diffusers Adapters" + params = { + "pipeline": {"label": "Pipeline", "display": "input", "type": "any"}, + "adapters": {"label": "LoRAs", "display": "input", "type": ["custom_lora", "collection"]}, + "output": {"label": "Pipeline", "display": "output", "type": "any"}, + "active_mix": {"label": "Active Mix", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + report = apply_lora_mix(kwargs.get("pipeline"), kwargs.get("adapters")) + return {"output": kwargs.get("pipeline"), "active_mix": json.dumps(report, sort_keys=True)} + + +class LoRAHotswap(NodeBase): + label = "Hotswap Diffusers LoRA" + category = "Diffusers Adapters" + params = { + "pipeline": {"label": "Pipeline", "display": "input", "type": "any"}, + "replacement": {"label": "Replacement", "display": "input", "type": "custom_lora"}, + "slot_name": {"label": "Existing Slot", "type": "string", "default": "default_0"}, + "output": {"label": "Pipeline", "display": "output", "type": "any"}, + } + + def execute(self, **kwargs): + pipeline = kwargs.get("pipeline") + adapter = _adapter_descriptor(kwargs.get("replacement")) + slot = str(kwargs.get("slot_name") or "default_0") + if slot not in active_adapter_names(pipeline): + raise ValueError(f"LoRA hotswap slot {slot!r} is not loaded. Load the initial adapter before hotswapping it.") + load_kwargs = {"adapter_name": slot, "hotswap": True} + if adapter.get("weight_name"): + load_kwargs["weight_name"] = adapter["weight_name"] + pipeline.load_lora_weights(adapter["lora_path"], **load_kwargs) + pipeline.set_adapters([slot], [float(adapter.get("scale", 1.0))]) + return {"output": pipeline} + + +class LoRAFuseUnfuse(NodeBase): + label = "Fuse / Unfuse Diffusers LoRA" + category = "Diffusers Adapters" + params = { + "pipeline": {"label": "Pipeline", "display": "input", "type": "any"}, + "operation": {"label": "Operation", "type": "string", "options": ["fuse", "unfuse"], "default": "fuse"}, + "adapter_names": {"label": "Adapters", "type": "string", "default": ""}, + "components": {"label": "Components", "type": "string", "default": ""}, + "scale": {"label": "Scale", "type": "float", "default": 1.0}, + "safe_fusing": {"label": "Safe Fusing", "type": "bool", "default": True}, + "output": {"label": "Pipeline", "display": "output", "type": "any"}, + } + + def execute(self, **kwargs): + pipeline = kwargs.get("pipeline") + operation = str(kwargs.get("operation") or "fuse") + components = _string_list(kwargs.get("components")) or None + if operation == "fuse": + method = getattr(pipeline, "fuse_lora", None) + if not callable(method): + raise ValueError("This pipeline does not support LoRA fusion.") + method( + components=components, + adapter_names=_string_list(kwargs.get("adapter_names")) or None, + lora_scale=float(kwargs.get("scale") or 1.0), + safe_fusing=bool(kwargs.get("safe_fusing", True)), + ) + elif operation == "unfuse": + method = getattr(pipeline, "unfuse_lora", None) + if not callable(method): + raise ValueError("This pipeline does not support LoRA unfusing.") + method(components=components) + else: + raise ValueError(f"Unsupported LoRA fusion operation {operation!r}.") + return {"output": pipeline} + + +class LoRAUnloadReset(NodeBase): + label = "Unload / Reset Diffusers LoRA" + category = "Diffusers Adapters" + params = { + "pipeline": {"label": "Pipeline", "display": "input", "type": "any"}, + "adapter_names": {"label": "Adapters (empty = all)", "type": "string", "default": ""}, + "output": {"label": "Pipeline", "display": "output", "type": "any"}, + } + + def execute(self, **kwargs): + pipeline = kwargs.get("pipeline") + names = _string_list(kwargs.get("adapter_names")) + if names: + delete = getattr(pipeline, "delete_adapters", None) + if not callable(delete): + raise ValueError("This pipeline cannot delete individual LoRA adapters.") + for name in names: + delete(name) + else: + unload = getattr(pipeline, "unload_lora_weights", None) + if not callable(unload): + raise ValueError("This pipeline cannot unload LoRA weights.") + unload() + return {"output": pipeline} + + +class LoRAMergeArtifact(NodeBase): + """Merge loaded adapters with PEFT and save a reloadable adapter artifact.""" + + label = "Merge Diffusers LoRA Artifact" + category = "Diffusers Adapters" + resizable = True + params = { + "pipeline": {"label": "Pipeline", "display": "input", "type": "any"}, + "component": {"label": "Component", "type": "string", "options": ["transformer", "unet"], "default": "transformer"}, + "adapter_names": {"label": "Adapters", "type": "string", "default": ""}, + "weights": {"label": "Weights", "type": "string", "default": ""}, + "merge_method": { + "label": "Method", + "type": "string", + "options": ["cat", "linear", "svd", "ties", "ties_svd", "dare_ties", "dare_linear", "magnitude_prune"], + "default": "ties", + }, + "density": {"label": "Density", "type": "float", "default": 0.5, "min": 0.01, "max": 1.0}, + "merged_name": {"label": "Merged Adapter Name", "type": "string", "default": "merged"}, + "output_directory": {"label": "Output Directory", "type": "string", "default": "{PATH:models}/merged_lora_{HASH:6}"}, + "artifact_path": {"label": "Artifact Path", "display": "output", "type": "string"}, + "manifest": {"label": "Manifest", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + from utils.paths import parse_filename + + pipeline = kwargs.get("pipeline") + component_name = str(kwargs.get("component") or "transformer") + component = getattr(pipeline, component_name, None) + add_weighted = getattr(component, "add_weighted_adapter", None) + save = getattr(component, "save_pretrained", None) + if not callable(add_weighted) or not callable(save): + raise ValueError( + f"Pipeline component {component_name!r} does not expose PEFT weighted-adapter merge and save APIs." + ) + names = _string_list(kwargs.get("adapter_names")) + weights = [float(value) for value in _string_list(kwargs.get("weights"))] + if not names or len(names) != len(weights): + raise ValueError("LoRA merge needs the same non-zero number of adapter names and weights.") + loaded = active_adapter_names(pipeline) + missing = [name for name in names if name not in loaded] + if missing: + raise ValueError(f"LoRA merge adapters are not loaded: {', '.join(missing)}.") + merged_name = str(kwargs.get("merged_name") or "merged").strip() + method = str(kwargs.get("merge_method") or "ties") + density = float(kwargs.get("density") or 0.5) + merge_kwargs = {"combination_type": method} + if method.startswith(("ties", "dare", "magnitude_prune")): + merge_kwargs["density"] = density + add_weighted(names, weights, merged_name, **merge_kwargs) + + destination = Path(parse_filename(kwargs.get("output_directory") or "{PATH:models}/merged_lora_{HASH:6}")) + destination.mkdir(parents=True, exist_ok=False) + save(str(destination), safe_serialization=True, selected_adapters=[merged_name]) + manifest = { + "schema_version": 1, + "component": component_name, + "source_adapters": names, + "weights": weights, + "merged_adapter": merged_name, + "combination_type": method, + "density": density if "density" in merge_kwargs else None, + "artifact_path": str(destination), + } + (destination / "modiff_merge_manifest.json").write_text( + json.dumps(manifest, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + return {"artifact_path": str(destination), "manifest": json.dumps(manifest, sort_keys=True)} + + +class LoRAComparisonJobs(NodeBase): + label = "Build LoRA Comparison Jobs" + category = "Diffusers Adapters" + params = { + "prompt": {"label": "Prompt", "display": "textarea", "type": "text", "default": ""}, + "mixes": {"label": "Mixes (JSON)", "display": "textarea", "type": "text", "default": "[]"}, + "seed": {"label": "Seed", "type": "int", "default": 0}, + "jobs": {"label": "Comparison Jobs", "display": "output", "type": "collection"}, + } + + def execute(self, **kwargs): + raw = kwargs.get("mixes") or "[]" + mixes = json.loads(raw) if isinstance(raw, str) else raw + if not isinstance(mixes, list) or any(not isinstance(item, dict) for item in mixes): + raise ValueError("LoRA comparison mixes must be a JSON array of objects.") + if not mixes: + raise ValueError("Add at least one LoRA comparison mix.") + return { + "jobs": [ + {"index": index, "prompt": str(kwargs.get("prompt") or ""), "seed": int(kwargs.get("seed") or 0), "mix": mix} + for index, mix in enumerate(mixes) + ] + } diff --git a/modules/DiffusersAudio/__init__.py b/modules/DiffusersAudio/__init__.py index 15b6a64..216bacc 100644 --- a/modules/DiffusersAudio/__init__.py +++ b/modules/DiffusersAudio/__init__.py @@ -1 +1 @@ -from .main import * +from .main import * # noqa: F403 diff --git a/modules/DiffusersAudio/main.py b/modules/DiffusersAudio/main.py index 9dbb204..e153acc 100644 --- a/modules/DiffusersAudio/main.py +++ b/modules/DiffusersAudio/main.py @@ -1,5 +1,7 @@ import inspect +import hashlib import logging +from dataclasses import dataclass from pathlib import Path from typing import Any @@ -13,15 +15,16 @@ OFFLOAD_MODE_NONE, OFFLOAD_MODE_SEQUENTIAL_CPU, apply_pipeline_offload, - normalize_offload_mode, offload_mode_param, ) +from modiff.model_artifact_catalog import resolve_model_revision from utils.huggingface import local_files_only from utils.torch_utils import DEFAULT_DEVICE, DEVICE_LIST, str_to_dtype logger = logging.getLogger("modiff") ACE_STEP_DEFAULT_REPO = "ACE-Step/acestep-v15-xl-turbo-diffusers" +STABLE_AUDIO_DEFAULT_REPO = "stabilityai/stable-audio-open-1.0" DEVICE_OPTIONS = list(DEVICE_LIST.keys()) DIRECT_AUDIO_OFFLOAD_MODES = [ OFFLOAD_MODE_NONE, @@ -31,6 +34,41 @@ OFFLOAD_MODE_GROUP_DISK, ] ACE_TASK_TYPES = ["text2music", "cover", "repaint", "continuation", "extract", "lego", "complete"] +AUDIO_SAMPLE_RATE_OPTIONS = { + "44100": "44.1 kHz", + "48000": "48 kHz", + "88200": "88.2 kHz", + "96000": "96 kHz", +} + + +@dataclass(frozen=True) +class AudioPipelineAdapter: + pipeline_class: str + modes: frozenset[str] + task_types: frozenset[str] + + def resolve_pipeline_class(self): + import diffusers + + pipeline = getattr(diffusers, self.pipeline_class, None) + if pipeline is None: + raise ValueError(f"Diffusers does not expose audio pipeline class {self.pipeline_class}.") + return pipeline + + +AUDIO_PIPELINE_ADAPTERS = { + "AceStepPipeline": AudioPipelineAdapter( + pipeline_class="AceStepPipeline", + modes=frozenset({"text_to_audio", "audio_variation", "audio_continuation", "audio_repaint"}), + task_types=frozenset(ACE_TASK_TYPES), + ), + "StableAudioPipeline": AudioPipelineAdapter( + pipeline_class="StableAudioPipeline", + modes=frozenset({"text_to_audio"}), + task_types=frozenset({"text2audio"}), + ), +} def repo_value(value: Any) -> str: @@ -47,12 +85,26 @@ def none_if_blank(value: Any): return value +def value_or_default(value: Any, default: Any): + return default if value is None else value + + +def _pcm_to_float32(array: np.ndarray) -> np.ndarray: + if array.dtype.kind == "u": + midpoint = float(np.iinfo(array.dtype).max + 1) / 2.0 + return (array.astype(np.float32) - midpoint) / midpoint + if array.dtype.kind == "i": + limits = np.iinfo(array.dtype) + scale = float(max(abs(int(limits.min)), abs(int(limits.max)))) + return array.astype(np.float32) / scale + return array.astype(np.float32, copy=False) + + def pipeline_class_from_name(name: str): - if name != "AceStepPipeline": + adapter = AUDIO_PIPELINE_ADAPTERS.get(name) + if adapter is None: raise ValueError(f"Unsupported Diffusers audio pipeline class: {name}") - from diffusers import AceStepPipeline - - return AceStepPipeline + return adapter.resolve_pipeline_class() def supports_arg(pipeline: Any, arg_name: str) -> bool: @@ -82,10 +134,7 @@ def audio_to_numpy(audio: Any) -> tuple[np.ndarray, int]: if isinstance(data, torch.Tensor): data = data.detach().float().cpu().numpy() array = np.asarray(data) - if array.dtype.kind in ("i", "u"): - array = array.astype(np.float32) / float(np.iinfo(array.dtype).max) - else: - array = array.astype(np.float32, copy=False) + array = _pcm_to_float32(array) if array.ndim == 1: array = array[None, :] elif array.ndim == 2 and array.shape[0] > array.shape[1]: @@ -93,30 +142,55 @@ def audio_to_numpy(audio: Any) -> tuple[np.ndarray, int]: return np.clip(array, -1.0, 1.0), sample_rate -def audio_to_tensor(audio: Any, device: Any): +def audio_to_tensor(audio: Any, device: Any, target_sample_rate: int | None = None): import torch - - array, _sample_rate = audio_to_numpy(audio) + from scipy.signal import resample_poly + from math import gcd + + array, sample_rate = audio_to_numpy(audio) + if target_sample_rate and sample_rate != target_sample_rate: + divisor = gcd(sample_rate, target_sample_rate) + array = resample_poly( + array, + target_sample_rate // divisor, + sample_rate // divisor, + axis=-1, + ).astype(np.float32, copy=False) return torch.from_numpy(array).to(device=device, dtype=torch.float32) -def output_to_audio_object(result: Any, sample_rate: int = 48000) -> dict[str, Any]: - import torch +def resample_audio_object(audio: dict[str, Any], target_sample_rate: int) -> dict[str, Any]: + from math import gcd - audio = getattr(result, "audios", result) - if isinstance(audio, (list, tuple)) and len(audio) > 0: - audio = audio[0] - if isinstance(audio, dict): - if "samples" in audio: - return audio - if "audios" in audio: - return output_to_audio_object(audio["audios"], sample_rate) - if isinstance(audio, torch.Tensor): - array = audio.detach().float().cpu().numpy() - else: - array = np.asarray(audio, dtype=np.float32) - if array.ndim == 3: - array = array[0] + from scipy.signal import resample_poly + + source_sample_rate = int(audio.get("sample_rate") or 48000) + target_sample_rate = int(target_sample_rate) + if target_sample_rate not in {int(value) for value in AUDIO_SAMPLE_RATE_OPTIONS}: + supported = ", ".join(AUDIO_SAMPLE_RATE_OPTIONS.values()) + raise ValueError(f"Audio sample rate must be one of: {supported}.") + if source_sample_rate == target_sample_rate: + return audio + + samples = np.asarray(audio["samples"], dtype=np.float32) + divisor = gcd(source_sample_rate, target_sample_rate) + resampled = resample_poly( + samples, + target_sample_rate // divisor, + source_sample_rate // divisor, + axis=-1, + ).astype(np.float32, copy=False) + return { + **audio, + "samples": np.clip(resampled, -1.0, 1.0), + "sample_rate": target_sample_rate, + "channels": int(resampled.shape[0]) if resampled.ndim > 1 else 1, + "duration_seconds": float(resampled.shape[-1] / target_sample_rate), + } + + +def _audio_array_to_object(array: np.ndarray, sample_rate: int) -> dict[str, Any]: + array = np.asarray(array, dtype=np.float32) if array.ndim == 2 and array.shape[0] <= 8 and array.shape[1] > array.shape[0]: channels = int(array.shape[0]) duration_samples = int(array.shape[1]) @@ -136,6 +210,28 @@ def output_to_audio_object(result: Any, sample_rate: int = 48000) -> dict[str, A } +def output_to_audio_objects(result: Any, sample_rate: int = 48000) -> list[dict[str, Any]]: + import torch + + audio = getattr(result, "audios", result) + if isinstance(audio, dict): + if "samples" in audio: + return [audio] + if "audios" in audio: + return output_to_audio_objects(audio["audios"], sample_rate) + if isinstance(audio, torch.Tensor): + array = audio.detach().float().cpu().numpy() + else: + array = np.asarray(audio, dtype=np.float32) + if array.ndim == 3: + return [_audio_array_to_object(waveform, sample_rate) for waveform in array] + return [_audio_array_to_object(array, sample_rate)] + + +def output_to_audio_object(result: Any, sample_rate: int = 48000) -> dict[str, Any]: + return output_to_audio_objects(result, sample_rate)[0] + + def crop_tail(audio: dict[str, Any], start_seconds: float, duration_seconds: float | None): samples = np.asarray(audio["samples"], dtype=np.float32) sample_rate = int(audio.get("sample_rate") or 48000) @@ -167,9 +263,17 @@ class LoadPipeline(NodeBase): "pipeline_class": { "label": "Pipeline Class", "type": "string", - "options": ["AceStepPipeline"], + # Keep this literal so the static registry parser can expose the + # choices without importing Diffusers or executing this module. + "options": ["AceStepPipeline", "StableAudioPipeline"], "default": "AceStepPipeline", }, + "mode": { + "label": "Mode", + "type": "string", + "options": ["text_to_audio", "audio_variation", "audio_continuation", "audio_repaint"], + "default": "text_to_audio", + }, "revision": {"label": "Revision", "type": "string", "default": ""}, "dtype": { "label": "DType", @@ -180,31 +284,61 @@ class LoadPipeline(NodeBase): "device": {"label": "Device", "type": "string", "options": DEVICE_OPTIONS, "default": DEFAULT_DEVICE}, "auto_offload": {"label": "Auto offload", "type": "bool", "default": True}, "offload_mode": offload_mode_param(modes=DIRECT_AUDIO_OFFLOAD_MODES), + "execution_recipe": { + "label": "Execution Recipe", + "display": "input", + "type": "diffusers_execution_recipe", + }, "enable_vae_tiling": {"label": "VAE tiling", "type": "bool", "default": True}, "low_cpu_mem_usage": {"label": "Low CPU memory", "type": "bool", "default": True}, "resolved_artifact": {"label": "Resolved Artifact", "display": "output", "type": "string"}, } def execute(self, **kwargs): - model_id = repo_value(kwargs.get("model_id")) or ACE_STEP_DEFAULT_REPO + from modules.DiffusersRuntime.main import apply_execution_recipe_to_pipeline, loader_runtime_options + pipeline_class_name = str(kwargs.get("pipeline_class") or "AceStepPipeline") + model_selection = kwargs.get("model_id") + model_id = repo_value(model_selection) + if not model_id or (pipeline_class_name == "StableAudioPipeline" and model_id == ACE_STEP_DEFAULT_REPO): + model_id = STABLE_AUDIO_DEFAULT_REPO if pipeline_class_name == "StableAudioPipeline" else ACE_STEP_DEFAULT_REPO + model_source = "hub" + else: + model_source = model_selection.get("source") if isinstance(model_selection, dict) else None + mode = str(kwargs.get("mode") or "text_to_audio") + adapter = AUDIO_PIPELINE_ADAPTERS.get(pipeline_class_name) + if adapter is None or mode not in adapter.modes: + supported = ', '.join(sorted(adapter.modes if adapter else [])) or 'none' + raise ValueError(f"{pipeline_class_name} does not support {mode}. Supported modes: {supported}.") pipeline_class = pipeline_class_from_name(pipeline_class_name) dtype = str_to_dtype(kwargs.get("dtype") or "bfloat16") - device = kwargs.get("device") or DEFAULT_DEVICE - revision = none_if_blank(kwargs.get("revision")) - auto_offload = bool(kwargs.get("auto_offload", True)) - offload_mode = normalize_offload_mode(kwargs.get("offload_mode") or OFFLOAD_MODE_MODEL_CPU, auto_offload=auto_offload) + recipe, device, offload_mode, recipe_load_kwargs = loader_runtime_options( + kwargs, + default_device=DEFAULT_DEVICE, + default_offload_mode=OFFLOAD_MODE_MODEL_CPU, + direct_device_load=True, + ) + revision = resolve_model_revision( + model_id, + none_if_blank(kwargs.get("revision")), + source=model_source, + ) load_kwargs = { "torch_dtype": dtype, "revision": revision, "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), "local_files_only": local_files_only(model_id), + **recipe_load_kwargs, } self.progress(-1, phase="loading", message=f"Loading {pipeline_class_name}") - pipeline = pipeline_class.from_pretrained(model_id, **load_kwargs) - if kwargs.get("enable_vae_tiling", True): + with self.diffusers_loading_progress(): + pipeline = pipeline_class.from_pretrained(model_id, **load_kwargs) + setattr(pipeline, "_modiff_audio_pipeline_class", pipeline_class_name) + if recipe: + apply_execution_recipe_to_pipeline(pipeline, recipe) + elif kwargs.get("enable_vae_tiling", True): vae = getattr(pipeline, "vae", None) for method_name in ("enable_tiling", "enable_vae_tiling"): method = getattr(vae or pipeline, method_name, None) @@ -212,7 +346,7 @@ def execute(self, **kwargs): method() break - self.progress(-1, phase="loading", message=f"Applying {offload_mode} offload") + self.progress(99, phase="component_placement", message=f"Applying {offload_mode} offload") apply_pipeline_offload( pipeline, mode=offload_mode, @@ -225,6 +359,144 @@ def execute(self, **kwargs): return {"pipeline": pipeline, "resolved_artifact": model_id} +class LoadAdapter(NodeBase): + """Load an ACE-Step LoRA from MoDiff's managed cache or a local folder.""" + + label = "Load Diffusers Audio LoRA" + category = "Diffusers Audio" + resizable = True + params = { + "pipeline": {"label": "Pipeline", "display": "input", "type": "audio_diffusion_pipeline", "required": True}, + "adapter_path": { + "label": "LoRA", + "display": "modelselect", + "type": "string", + "fieldOptions": {"noValidation": True, "sources": ["hub", "local"]}, + }, + "weight_name": {"label": "Weight name", "type": "string", "default": "adapter_model.safetensors"}, + "expected_sha256": {"label": "Expected SHA-256", "type": "string", "default": ""}, + "adapter_name": {"label": "Adapter name", "type": "string", "default": "audio_style"}, + "replace_existing": {"label": "Replace existing adapters", "type": "bool", "default": True}, + "scale": {"label": "Strength", "display": "slider", "type": "float", "default": 0.7, "min": 0, "max": 2, "step": 0.05}, + "output": {"label": "Pipeline", "display": "output", "type": "audio_diffusion_pipeline"}, + } + + def execute(self, **kwargs): + pipeline = kwargs.get("pipeline") + if pipeline is None: + raise ValueError("Load Audio LoRA needs a pipeline input.") + if getattr(pipeline, "_modiff_audio_pipeline_class", None) != "AceStepPipeline": + raise ValueError("Audio LoRA loading is currently supported only for AceStepPipeline.") + if not callable(getattr(pipeline, "load_lora_weights", None)): + raise RuntimeError( + "This Diffusers revision does not expose ACE-Step LoRA support. Install MoDiff's pinned dependencies." + ) + + selection = kwargs.get("adapter_path") + adapter_path = repo_value(selection) + if not adapter_path: + return {"output": pipeline} + weight_name = str(kwargs.get("weight_name") or "adapter_model.safetensors").strip() + source = selection.get("source") if isinstance(selection, dict) else "local" + if source == "hub": + from utils.huggingface import cached_file_path + + cached = cached_file_path(adapter_path, weight_name) + if not cached: + raise FileNotFoundError( + f"Audio LoRA {adapter_path}/{weight_name} is not installed. Install it through Model Manager first." + ) + cached_path = Path(cached) + expected = str(kwargs.get("expected_sha256") or "").strip().lower().removeprefix("sha256:") + if expected: + digest = hashlib.sha256() + with cached_path.open("rb") as handle: + for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""): + digest.update(chunk) + if digest.hexdigest() != expected: + raise ValueError("The audio LoRA failed its pinned SHA-256 verification. Repair it in Model Manager.") + adapter_path = str(cached_path.parent) + weight_name = cached_path.name + + adapter_name = str(kwargs.get("adapter_name") or "audio_style").strip() + if kwargs.get("replace_existing", True) and callable(getattr(pipeline, "unload_lora_weights", None)): + pipeline.unload_lora_weights() + pipeline.load_lora_weights(adapter_path, weight_name=weight_name, adapter_name=adapter_name) + if callable(getattr(pipeline, "set_adapters", None)): + pipeline.set_adapters([adapter_name], [float(kwargs.get("scale", 0.7))]) + return {"output": pipeline} + + +class SetAdapters(NodeBase): + """Select and blend already loaded ACE-Step LoRA adapters.""" + + label = "Set Audio LoRA Blend" + category = "Diffusers Audio" + params = { + "pipeline": { + "label": "Pipeline", + "display": "input", + "type": "audio_diffusion_pipeline", + "required": True, + }, + "adapter_names": {"label": "Adapter names", "type": "string", "default": "audio_style"}, + "adapter_weights": {"label": "Weights", "type": "string", "default": "0.7"}, + "output": {"label": "Pipeline", "display": "output", "type": "audio_diffusion_pipeline"}, + } + + def execute(self, **kwargs): + pipeline = kwargs.get("pipeline") + if pipeline is None: + raise ValueError("Set Audio LoRA Blend needs a pipeline input.") + names = [part.strip() for part in str(kwargs.get("adapter_names") or "").split(",") if part.strip()] + try: + weights = [float(part.strip()) for part in str(kwargs.get("adapter_weights") or "").split(",") if part.strip()] + except ValueError as exc: + raise ValueError("Audio LoRA weights must be comma-separated numbers.") from exc + if not names or len(names) != len(weights): + raise ValueError("Audio LoRA adapter names and weights must contain the same number of entries.") + setter = getattr(pipeline, "set_adapters", None) + if not callable(setter): + raise RuntimeError("This pipeline does not support selecting LoRA adapters.") + setter(names, weights) + return {"output": pipeline} + + +class FuseAdapters(NodeBase): + """Optionally fuse active ACE-Step LoRAs for lower per-step overhead.""" + + label = "Fuse Audio LoRA" + category = "Diffusers Audio" + params = { + "pipeline": { + "label": "Pipeline", + "display": "input", + "type": "audio_diffusion_pipeline", + "required": True, + }, + "enabled": {"label": "Fuse", "type": "bool", "default": True}, + "safe_fusing": {"label": "Safe fusing", "type": "bool", "default": True}, + "output": {"label": "Pipeline", "display": "output", "type": "audio_diffusion_pipeline"}, + } + + def execute(self, **kwargs): + pipeline = kwargs.get("pipeline") + if pipeline is None: + raise ValueError("Fuse Audio LoRA needs a pipeline input.") + method_name = "fuse_lora" if kwargs.get("enabled", True) else "unfuse_lora" + method = getattr(pipeline, method_name, None) + if not callable(method): + raise RuntimeError(f"This pipeline does not support {method_name}().") + if method_name == "fuse_lora": + try: + method(safe_fusing=bool(kwargs.get("safe_fusing", True))) + except TypeError: + method() + else: + method() + return {"output": pipeline} + + class Generate(NodeBase): """Generate audio with a Diffusers audio pipeline.""" @@ -232,9 +504,15 @@ class Generate(NodeBase): category = "Diffusers Audio" resizable = True params = { - "pipeline": {"label": "Pipeline", "display": "input", "type": "audio_diffusion_pipeline"}, + "pipeline": { + "label": "Pipeline", + "display": "input", + "type": "audio_diffusion_pipeline", + "required": True, + }, "task_type": {"label": "Task", "type": "string", "options": ACE_TASK_TYPES, "default": "text2music"}, "prompt": {"label": "Prompt", "display": "textarea", "type": "text", "default": ""}, + "negative_prompt": {"label": "Negative Prompt", "display": "textarea", "type": "text", "default": ""}, "lyrics": {"label": "Lyrics", "display": "textarea", "type": "text", "default": ""}, "audio_duration": {"label": "Duration", "type": "float", "default": 30.0, "min": 1, "max": 240, "step": 0.5}, "extension_duration": {"label": "Extension", "type": "float", "default": 15.0, "min": 1, "max": 180, "step": 0.5}, @@ -258,19 +536,67 @@ class Generate(NodeBase): "step": 0.1, "description": "XL Turbo is guidance-distilled; values above 1 are ignored by the Diffusers pipeline.", }, + "lora_scale": { + "label": "LoRA call strength", + "display": "slider", + "type": "float", + "default": 1.0, + "min": 0, + "max": 2, + "step": 0.05, + # Retained for backward compatibility with saved graphs. Diffusers + # applies this per-call multiplier on top of the active adapter + # weight. Managed ACE-Step graphs keep it at 1.0 and expose the + # loader Strength as their single, non-duplicated control. + "hidden": True, + "description": ( + "Advanced per-call multiplier for active adapters. Managed ACE-Step graphs use the LoRA loader " + "Strength and keep this multiplier at 1.0." + ), + }, "shift": {"label": "Shift", "display": "slider", "type": "float", "default": 3.0, "min": 0, "max": 10, "step": 0.1}, "seed": {"label": "Seed", "type": "int", "display": "random", "default": 0, "min": 0, "max": 4294967295}, "bpm": {"label": "BPM", "type": "int", "default": 0, "min": 0, "max": 400}, "keyscale": {"label": "Key", "type": "string", "default": ""}, "timesignature": {"label": "Time", "type": "string", "default": "4"}, - "source_audio": {"label": "Source Audio", "display": "input", "type": ["audio", "str"]}, - "reference_audio": {"label": "Reference Audio", "display": "input", "type": ["audio", "str"]}, + "source_audio": {"label": "Source Audio", "display": "input", "type": ["audio", "str"], "required": False}, + "reference_audio": {"label": "Reference Audio", "display": "input", "type": ["audio", "str"], "required": False}, "repainting_start": {"label": "Repaint Start", "type": "float", "default": 0.0, "min": 0, "step": 0.01}, "repainting_end": {"label": "Repaint End", "type": "float", "default": 0.0, "min": 0, "step": 0.01}, "audio_cover_strength": {"label": "Cover Strength", "display": "slider", "type": "float", "default": 0.85, "min": 0, "max": 1, "step": 0.01}, "return_continuation_tail": {"label": "Return Tail Only", "type": "bool", "default": True}, - "sample_rate": {"label": "Sample Rate", "type": "int", "default": 48000, "min": 8000, "max": 192000}, + "sample_rate": { + "label": "Sample Rate", + "type": "int", + "default": 48000, + "options": AUDIO_SAMPLE_RATE_OPTIONS, + }, + "stable_audio_steps": { + "label": "Stable Audio Steps", + "type": "int", + "default": 100, + "min": 1, + "max": 300, + "description": "StableAudioPipeline-only denoising steps; ignored by ACE-Step.", + }, + "stable_audio_guidance": { + "label": "Stable Audio Guidance", + "type": "float", + "default": 7, + "min": 0, + "max": 20, + "description": "StableAudioPipeline-only classifier-free guidance; ignored by ACE-Step.", + }, + "num_waveforms": { + "label": "Variations", + "type": "int", + "default": 1, + "min": 1, + "max": 8, + "description": "Number of StableAudioPipeline waveforms; ignored by ACE-Step.", + }, "audio": {"label": "Audio", "display": "output", "type": "audio"}, + "audio_variations": {"label": "Audio Variations", "display": "output", "type": "collection"}, "sample_rate_out": {"label": "Sample Rate", "display": "output", "type": "int"}, "duration_seconds": {"label": "Duration", "display": "output", "type": "float"}, } @@ -282,7 +608,18 @@ def execute(self, **kwargs): if pipeline is None: raise ValueError("Diffusers audio pipeline is required.") - sample_rate = int(kwargs.get("sample_rate") or 48000) + if getattr(pipeline, "_modiff_audio_pipeline_class", None) == "StableAudioPipeline": + return self._execute_stable_audio(pipeline, kwargs) + + requested_sample_rate = int(kwargs.get("sample_rate") or 48000) + if requested_sample_rate not in {int(value) for value in AUDIO_SAMPLE_RATE_OPTIONS}: + supported = ", ".join(AUDIO_SAMPLE_RATE_OPTIONS.values()) + raise ValueError(f"Audio sample rate must be one of: {supported}.") + # The decoded tensor is produced at the VAE's native rate. Labeling it + # with a different UI/export rate changes its duration and can truncate + # valid samples, so generation stays at the pipeline's native rate and + # the completed audio is resampled to the requested delivery rate. + sample_rate = int(getattr(pipeline, "sample_rate", None) or 48000) task_type = str(kwargs.get("task_type") or "text2music") source_audio = kwargs.get("source_audio") reference_audio = kwargs.get("reference_audio") @@ -308,13 +645,15 @@ def execute(self, **kwargs): "audio_duration": audio_duration, "vocal_language": str(kwargs.get("vocal_language") or "en"), "num_inference_steps": int(kwargs.get("num_inference_steps") or 8), - "guidance_scale": float(kwargs.get("guidance_scale") or 1.0), - "shift": float(kwargs.get("shift") or 3.0), + "guidance_scale": float(value_or_default(kwargs.get("guidance_scale"), 1.0)), + "shift": float(value_or_default(kwargs.get("shift"), 3.0)), "generator": generator, "output_type": "pt", "return_dict": True, "task_type": call_task_type, } + if supports_arg(pipeline, "attention_kwargs"): + call_kwargs["attention_kwargs"] = {"scale": float(kwargs.get("lora_scale", 1.0))} for optional in ("bpm", "keyscale", "timesignature"): value = none_if_blank(kwargs.get(optional)) if optional == "bpm" and value is not None: @@ -328,13 +667,28 @@ def execute(self, **kwargs): call_kwargs[optional] = value if source_audio not in (None, ""): - tensor = audio_to_tensor(source_audio, device=device) - if supports_arg(pipeline, "src_audio"): - call_kwargs["src_audio"] = tensor - if task_type in ("repaint", "continuation") and supports_arg(pipeline, "reference_audio"): + tensor = audio_to_tensor(source_audio, device=device, target_sample_rate=sample_rate) + if task_type == "continuation": + target_samples = int(round(audio_duration * sample_rate)) + if tensor.shape[-1] < target_samples: + tensor = torch.nn.functional.pad(tensor, (0, target_samples - tensor.shape[-1])) + # ACE-Step cover/variation treats the supplied track as a timbre + # and style reference. Sending it as src_audio instead asks the + # pipeline for semantic-code cover conditioning, which requires + # optional audio tokenizer/detokenizer modules that the official + # Diffusers artifact does not publish. Repaint and continuation, + # by contrast, need src_audio so the VAE can preserve the source + # waveform outside the edited interval. + if task_type == "cover" and supports_arg(pipeline, "reference_audio"): call_kwargs["reference_audio"] = tensor + elif supports_arg(pipeline, "src_audio"): + call_kwargs["src_audio"] = tensor if reference_audio not in (None, "") and supports_arg(pipeline, "reference_audio"): - call_kwargs["reference_audio"] = audio_to_tensor(reference_audio, device=device) + call_kwargs["reference_audio"] = audio_to_tensor( + reference_audio, + device=device, + target_sample_rate=sample_rate, + ) if task_type in ("repaint", "continuation"): start = float(kwargs.get("repainting_start") or 0.0) @@ -347,21 +701,86 @@ def execute(self, **kwargs): if supports_arg(pipeline, "repainting_end"): call_kwargs["repainting_end"] = end if task_type == "cover" and supports_arg(pipeline, "audio_cover_strength"): - call_kwargs["audio_cover_strength"] = float(kwargs.get("audio_cover_strength") or 0.85) + call_kwargs["audio_cover_strength"] = float( + value_or_default(kwargs.get("audio_cover_strength"), 0.85) + ) if supports_arg(pipeline, "callback_on_step_end"): call_kwargs["callback_on_step_end"] = self.pipe_callback if supports_arg(pipeline, "callback_on_step_end_tensor_inputs"): call_kwargs["callback_on_step_end_tensor_inputs"] = [] - self.progress(0, phase="denoising", message=f"Generating audio ({task_type})") + self.progress( + -1, + phase="denoising", + message=f"Generating audio ({task_type})", + current_step=0, + total_steps=call_kwargs["num_inference_steps"], + ) result = pipeline(**call_kwargs) audio = output_to_audio_object(result, sample_rate=sample_rate) if task_type == "continuation" and kwargs.get("return_continuation_tail", True): audio = crop_tail(audio, source_duration, float(kwargs.get("extension_duration") or 15.0)) + else: + # Diffusion audio decoders may emit a frame-aligned tail beyond the + # requested duration. Keep the shared node contract exact without + # padding outputs that are genuinely shorter. + audio = crop_tail(audio, 0.0, audio_duration) + audio = resample_audio_object(audio, requested_sample_rate) return { "audio": audio, - "sample_rate_out": int(audio.get("sample_rate") or sample_rate), + "audio_variations": [audio], + "sample_rate_out": int(audio.get("sample_rate") or requested_sample_rate), "duration_seconds": float(audio.get("duration_seconds") or 0.0), } + + def _execute_stable_audio(self, pipeline, kwargs): + import torch + + if kwargs.get("source_audio") not in (None, "") or kwargs.get("reference_audio") not in (None, ""): + raise ValueError("Stable Audio supports text-to-audio only and does not accept source audio.") + duration = float(kwargs.get("audio_duration") or 30) + if not 0 < duration <= 47: + raise ValueError("Stable Audio duration must be greater than 0 and at most 47 seconds.") + device = getattr(pipeline, "_execution_device", None) or getattr(pipeline, "device", None) or "cpu" + try: + generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed") or 0)) + except Exception: + generator = torch.Generator(device="cpu").manual_seed(int(kwargs.get("seed") or 0)) + + def callback(step, timestep, latents): + if not hasattr(pipeline, "_num_timesteps"): + pipeline._num_timesteps = int(kwargs.get("stable_audio_steps") or 100) + self.pipe_callback(pipeline, step, timestep, {"latents": latents}) + + result = pipeline( + prompt=str(kwargs.get("prompt") or ""), + negative_prompt=none_if_blank(kwargs.get("negative_prompt")), + audio_start_in_s=0, + audio_end_in_s=duration, + num_inference_steps=int(kwargs.get("stable_audio_steps") or 100), + guidance_scale=float(value_or_default(kwargs.get("stable_audio_guidance"), 7)), + num_waveforms_per_prompt=int(kwargs.get("num_waveforms") or 1), + generator=generator, + callback=callback, + callback_steps=1, + output_type="pt", + return_dict=True, + ) + vae = getattr(pipeline, "vae", None) + vae_config = getattr(vae, "config", None) + configured_rate = vae_config.get("sampling_rate") if hasattr(vae_config, "get") else None + sample_rate = int(getattr(vae, "sampling_rate", None) or configured_rate or 44100) + requested_sample_rate = int(kwargs.get("sample_rate") or 48000) + audio_variations = [ + resample_audio_object(crop_tail(audio, 0, duration), requested_sample_rate) + for audio in output_to_audio_objects(result, sample_rate) + ] + audio = audio_variations[0] + return { + "audio": audio, + "audio_variations": audio_variations, + "sample_rate_out": requested_sample_rate, + "duration_seconds": float(audio["duration_seconds"]), + } diff --git a/modules/DiffusersImage/__init__.py b/modules/DiffusersImage/__init__.py index 1ab031f..834defe 100644 --- a/modules/DiffusersImage/__init__.py +++ b/modules/DiffusersImage/__init__.py @@ -1,4 +1,4 @@ -from .main import * +from .main import * # noqa: F403 def _registry_entry(node_class): @@ -22,5 +22,5 @@ def _registry_entry(node_class): # precomputed-map path. MODULE_MAP = { node_class.__name__: _registry_entry(node_class) - for node_class in (Edit, Inpaint, ControlGenerate) + for node_class in (Edit, Inpaint, ControlGenerate, OutpaintCanvas) # noqa: F405 } diff --git a/modules/DiffusersImage/main.py b/modules/DiffusersImage/main.py index 4b97463..a0494b7 100644 --- a/modules/DiffusersImage/main.py +++ b/modules/DiffusersImage/main.py @@ -1,7 +1,11 @@ import inspect +import hashlib import logging +from dataclasses import dataclass from typing import Any +from PIL import Image, ImageColor, ImageDraw, ImageFilter + from modiff.NodeBase import NodeBase from modiff.diffusers_offload import ( OFFLOAD_MODE_GROUP_CPU, @@ -13,21 +17,101 @@ normalize_offload_mode, offload_mode_param, ) +from modiff.model_artifact_catalog import require_catalog_revision, resolve_model_revision from utils.huggingface import local_files_only from utils.torch_utils import DEFAULT_DEVICE, DEVICE_LIST, str_to_dtype logger = logging.getLogger("modiff") FLUX_SCHNELL_REPO = "black-forest-labs/FLUX.1-schnell" +FLUX_DEV_REPO = "black-forest-labs/FLUX.1-dev" +QWEN_IMAGE_2512_REPO = "Qwen/Qwen-Image-2512" +QWEN_IMAGE_2512_PREQUANTIZED_REPO = "unsloth/Qwen-Image-2512-unsloth-bnb-4bit" DEVICE_OPTIONS = list(DEVICE_LIST.keys()) -IMAGE_PIPELINE_CLASSES = [ - "FluxPipeline", - "FluxImg2ImgPipeline", - "FluxInpaintPipeline", - "FluxFillPipeline", - "FluxControlPipeline", - "FluxControlNetPipeline", - "FluxKontextPipeline", + + +@dataclass(frozen=True) +class ImagePipelineAdapter: + pipeline_class: str + modes: frozenset[str] + default_repo: str = FLUX_SCHNELL_REPO + guidance_parameter: str = "guidance_scale" + multi_image_strategy: str = "list" + + def apply_generation_parameters(self, pipeline: Any, values: dict[str, Any], target: dict[str, Any]) -> None: + aliases = { + "negative_prompt": "negative_prompt", + "width": "width", + "height": "height", + "max_sequence_length": "max_sequence_length", + "strength": "strength", + "padding_mask_crop": "padding_mask_crop", + "reference_strength": "reference_strength", + } + for source, destination in aliases.items(): + value = values.get(source) + # The graph uses zero to mean "no crop". Diffusers uses None for + # that contract; passing 0 enters Qwen's overlay path and can make + # its internally resized image conflict with the original-size + # mask during wide outpaint finalization. + if source == "padding_mask_crop" and (value is None or int(value) <= 0): + continue + if supports_arg(pipeline, destination) and value is not None: + target[destination] = value + if supports_arg(pipeline, self.guidance_parameter) and values.get("guidance_scale") is not None: + target[self.guidance_parameter] = values.get("guidance_scale") + + +IMAGE_PIPELINE_ADAPTERS = { + "QwenImagePipeline": ImagePipelineAdapter( + "QwenImagePipeline", + frozenset({"text_to_image"}), + default_repo=QWEN_IMAGE_2512_REPO, + guidance_parameter="true_cfg_scale", + ), + "ZImagePipeline": ImagePipelineAdapter("ZImagePipeline", frozenset({"text_to_image"})), + "FluxPipeline": ImagePipelineAdapter("FluxPipeline", frozenset({"text_to_image"})), + "Flux2KleinPipeline": ImagePipelineAdapter( + "Flux2KleinPipeline", frozenset({"text_to_image", "edit_image", "multi_image_reference_edit"}) + ), + "FluxImg2ImgPipeline": ImagePipelineAdapter( + "FluxImg2ImgPipeline", frozenset({"edit_image", "multi_image_reference_edit"}) + ), + "FluxInpaintPipeline": ImagePipelineAdapter("FluxInpaintPipeline", frozenset({"inpaint"})), + "FluxFillPipeline": ImagePipelineAdapter("FluxFillPipeline", frozenset({"inpaint", "outpaint"})), + "FluxControlPipeline": ImagePipelineAdapter("FluxControlPipeline", frozenset({"control_image"})), + "FluxControlNetPipeline": ImagePipelineAdapter("FluxControlNetPipeline", frozenset({"control_image"})), + "FluxKontextPipeline": ImagePipelineAdapter( + "FluxKontextPipeline", + frozenset({"edit_image", "multi_image_reference_edit"}), + multi_image_strategy="stitch_horizontal", + ), + # Virtual adapter class: FLUX Redux is a prior that supplies embeddings to + # a base FLUX pipeline, not a standalone img2img checkpoint. + "FluxReduxPipeline": ImagePipelineAdapter( + "FluxReduxPipeline", + frozenset({"edit_image", "multi_image_reference_edit"}), + # Current Diffusers performs the documented per-reference scaling and + # weighted sum inside FluxPriorReduxPipeline. Keep the references as a + # list and delegate the conditioning math to the upstream pipeline. + multi_image_strategy="upstream_weighted_sum", + ), + "QwenImageEditInpaintPipeline": ImagePipelineAdapter( + "QwenImageEditInpaintPipeline", + frozenset({"inpaint", "outpaint"}), + default_repo="Qwen/Qwen-Image-Edit", + guidance_parameter="true_cfg_scale", + ), +} +IMAGE_PIPELINE_CLASSES = list(IMAGE_PIPELINE_ADAPTERS) +IMAGE_PIPELINE_MODES = {name: set(adapter.modes) for name, adapter in IMAGE_PIPELINE_ADAPTERS.items()} +IMAGE_PIPELINE_MODE_OPTIONS = [ + "text_to_image", + "edit_image", + "multi_image_reference_edit", + "inpaint", + "outpaint", + "control_image", ] DIFFUSERS_IMAGE_OFFLOAD_MODES = [ OFFLOAD_MODE_NONE, @@ -36,7 +120,7 @@ OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK, ] -QUANT_COMPONENTS = ["transformer", "text_encoder", "text_encoder_2", "vae"] +QUANT_COMPONENTS = ["transformer", "transformer_2", "text_encoder", "text_encoder_2", "vae"] def repo_value(value: Any) -> str: @@ -53,6 +137,197 @@ def none_if_blank(value: Any): return value +def output_image_dimensions(images: Any, output_type: str = "pil") -> tuple[int | None, int | None]: + """Return width/height for Diffusers PIL, NumPy, or Torch outputs.""" + + first = images[0] if isinstance(images, (list, tuple)) and images else images + size = getattr(first, "size", None) + if isinstance(size, (tuple, list)) and len(size) >= 2: + return int(size[0]), int(size[1]) + + shape_value = getattr(first, "shape", None) + if shape_value is None: + shape_value = getattr(images, "shape", None) + try: + shape = tuple(int(value) for value in shape_value) + except (TypeError, ValueError): + return None, None + if len(shape) < 2: + return None, None + if str(output_type).lower() == "pt": + height, width = shape[-2], shape[-1] + elif len(shape) >= 4: + height, width = shape[-3], shape[-2] + else: + height, width = shape[0], shape[1] + return width, height + + +def ensure_single_image(value: Any, field_name: str) -> Image.Image: + if value in (None, ""): + raise ValueError(f"Outpaint Canvas requires a {field_name}.") + if isinstance(value, list): + images = [item for item in value if item not in (None, "")] + if len(images) != 1: + raise ValueError(f"Outpaint Canvas requires exactly one {field_name}; received {len(images)}.") + value = images[0] + if not isinstance(value, Image.Image): + raise ValueError(f"Outpaint Canvas {field_name} must be a PIL image loaded by the Image Load node.") + return value + + +def _int_in_range(value: Any, fallback: int, minimum: int, maximum: int) -> int: + try: + parsed = int(value) + except (TypeError, ValueError): + parsed = fallback + return max(minimum, min(maximum, parsed)) + + +def _float_in_range(value: Any, fallback: float, minimum: float, maximum: float) -> float: + try: + parsed = float(value) + except (TypeError, ValueError): + parsed = fallback + return max(minimum, min(maximum, parsed)) + + +def _placement_offset(available: int, start_margin: int, end_margin: int) -> int: + if available <= 0: + return 0 + requested = start_margin + end_margin + if requested <= 0: + return available // 2 + if requested > available: + return int(round((start_margin / requested) * available)) + return min(start_margin, available) + + +def _outpaint_source_size( + source_size: tuple[int, int], + target_size: tuple[int, int], + margins: tuple[int, int, int, int], +) -> tuple[int, int]: + source_width, source_height = source_size + target_width, target_height = target_size + left, right, top, bottom = margins + available_width = max(1, target_width - left - right) + available_height = max(1, target_height - top - bottom) + scale = min( + target_width / source_width, + target_height / source_height, + available_width / source_width, + available_height / source_height, + 1.0, + ) + return ( + max(1, int(round(source_width * scale))), + max(1, int(round(source_height * scale))), + ) + + +class OutpaintCanvas(NodeBase): + """Prepare a model-neutral expanded canvas and white-generate boundary mask.""" + + label = "Outpaint Canvas" + category = "Diffusers Image" + resizable = True + params = { + "image": {"label": "Source image", "display": "input", "type": "image"}, + "width": {"label": "Canvas width", "type": "int", "default": 1344, "min": 64, "max": 2048, "step": 16}, + "height": {"label": "Canvas height", "type": "int", "default": 768, "min": 64, "max": 2048, "step": 16}, + "left": {"label": "Left margin", "type": "int", "default": 256, "min": 0, "max": 2048, "step": 16}, + "right": {"label": "Right margin", "type": "int", "default": 256, "min": 0, "max": 2048, "step": 16}, + "top": {"label": "Top margin", "type": "int", "default": 0, "min": 0, "max": 2048, "step": 16}, + "bottom": {"label": "Bottom margin", "type": "int", "default": 0, "min": 0, "max": 2048, "step": 16}, + "overlap": {"label": "Seam overlap", "type": "int", "default": 24, "min": 0, "max": 256, "step": 4}, + "feather": {"label": "Mask feather", "type": "float", "default": 8.0, "min": 0, "max": 128, "step": 1}, + "fill_color": {"label": "Fill color", "type": "string", "default": "#000000"}, + "canvas": {"label": "Canvas", "display": "output", "type": "image"}, + "mask_image": {"label": "Mask image", "display": "output", "type": "image"}, + "width_out": {"label": "Width", "display": "output", "type": "int"}, + "height_out": {"label": "Height", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + source = ensure_single_image(kwargs.get("image"), "source image") + target_width = _int_in_range(kwargs.get("width"), 1344, 64, 2048) + target_height = _int_in_range(kwargs.get("height"), 768, 64, 2048) + left = _int_in_range(kwargs.get("left"), 256, 0, target_width) + right = _int_in_range(kwargs.get("right"), 256, 0, target_width) + top = _int_in_range(kwargs.get("top"), 0, 0, target_height) + bottom = _int_in_range(kwargs.get("bottom"), 0, 0, target_height) + overlap = _int_in_range(kwargs.get("overlap"), 24, 0, 256) + feather = _float_in_range(kwargs.get("feather"), 8.0, 0.0, 128.0) + try: + fill_color = ImageColor.getrgb(str(kwargs.get("fill_color") or "#000000"))[:3] + except ValueError as exc: + raise ValueError("Outpaint Canvas fill color must be a CSS color name or hex color.") from exc + + source_rgba = source.convert("RGBA") + pasted_width, pasted_height = _outpaint_source_size( + source_rgba.size, + (target_width, target_height), + (left, right, top, bottom), + ) + if (pasted_width, pasted_height) != source_rgba.size: + source_rgba = source_rgba.resize((pasted_width, pasted_height), Image.Resampling.LANCZOS) + + offset_x = _placement_offset(target_width - pasted_width, left, right) + offset_y = _placement_offset(target_height - pasted_height, top, bottom) + canvas = Image.new("RGB", (target_width, target_height), fill_color) + canvas.paste(source_rgba, (offset_x, offset_y), source_rgba) + mask = Image.new("L", (target_width, target_height), 255) + keep_left = min(target_width, max(0, offset_x + overlap)) + keep_top = min(target_height, max(0, offset_y + overlap)) + keep_right = min(target_width, max(0, offset_x + pasted_width - overlap)) + keep_bottom = min(target_height, max(0, offset_y + pasted_height - overlap)) + if keep_right > keep_left and keep_bottom > keep_top: + ImageDraw.Draw(mask).rectangle((keep_left, keep_top, keep_right, keep_bottom), fill=0) + if feather > 0: + mask = mask.filter(ImageFilter.GaussianBlur(radius=feather)) + return { + "canvas": canvas, + "mask_image": mask, + "width_out": target_width, + "height_out": target_height, + } + + +def composite_masked_pil_outputs(outputs: Any, source: Any, mask: Any) -> list[Image.Image]: + """Keep generic inpaint outputs byte-stable outside a white-generate mask.""" + source_image = source if isinstance(source, Image.Image) else None + mask_image = mask if isinstance(mask, Image.Image) else None + generated = ( + [outputs] + if isinstance(outputs, Image.Image) + else list(outputs or []) + if isinstance(outputs, (list, tuple)) + else [] + ) + if ( + source_image is None + or mask_image is None + or not generated + or any(not isinstance(item, Image.Image) for item in generated) + ): + raise ValueError( + "Diffusers image inpaint requires PIL source, mask, and output images for mask-safe compositing." + ) + + composited = [] + for image in generated: + target_size = image.size + normalized_source = source_image.convert("RGB") + if normalized_source.size != target_size: + normalized_source = normalized_source.resize(target_size, Image.Resampling.LANCZOS) + normalized_mask = mask_image.convert("L") + if normalized_mask.size != target_size: + normalized_mask = normalized_mask.resize(target_size, Image.Resampling.LANCZOS) + composited.append(Image.composite(image.convert("RGB"), normalized_source, normalized_mask)) + return composited + + def normalize_component_list(value: Any) -> list[str]: if value in (None, ""): return [] @@ -81,23 +356,88 @@ def pipeline_class_from_name(name: str): return pipeline_class -def quant_config_for(method: str, dtype: Any): +def quant_config_for(method: str, dtype: Any, modules_to_not_convert: list[str] | None = None): + excluded = list(dict.fromkeys(modules_to_not_convert or [])) or None if method == "bnb_4bit": from diffusers import BitsAndBytesConfig - return BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=dtype) + return BitsAndBytesConfig( + load_in_4bit=True, + bnb_4bit_quant_type="nf4", + bnb_4bit_compute_dtype=dtype, + llm_int8_skip_modules=excluded, + ) if method == "bnb_8bit": from diffusers import BitsAndBytesConfig - return BitsAndBytesConfig(load_in_8bit=True) + return BitsAndBytesConfig(load_in_8bit=True, llm_int8_skip_modules=excluded) if method == "quanto_float8": from diffusers import QuantoConfig - return QuantoConfig(weights="float8") + return QuantoConfig(weights_dtype="float8", modules_to_not_convert=excluded) + if method == "quanto_int8": + from diffusers import QuantoConfig + + return QuantoConfig(weights_dtype="int8", modules_to_not_convert=excluded) if method == "torchao_float8": from diffusers import TorchAoConfig - return TorchAoConfig(quant_type="float8wo_e4m3") + try: + from torchao.quantization import Float8WeightOnlyConfig + except ImportError as exc: + raise RuntimeError( + "TorchAO FP8 requires the optional torchao quantization package. " + "Install MoDiff's quantization dependencies and retry." + ) from exc + + return TorchAoConfig( + quant_type=Float8WeightOnlyConfig(), + modules_to_not_convert=excluded, + ) + if method == "torchao_int8_weight_only": + from diffusers import TorchAoConfig + + try: + from torchao.quantization import Int8WeightOnlyConfig + except ImportError as exc: + raise RuntimeError( + "TorchAO INT8 weight-only quantization requires the optional torchao package. " + "Install MoDiff's quantization dependencies and retry." + ) from exc + + return TorchAoConfig( + quant_type=Int8WeightOnlyConfig(), + modules_to_not_convert=excluded, + ) + if method in {"torchao_mxfp8", "torchao_nvfp4"}: + from diffusers import TorchAoConfig + + try: + if method == "torchao_mxfp8": + from torchao.prototype.mx_formats.inference_workflow import MXDynamicActivationMXWeightConfig + from torchao.quantization.quantize_.common import KernelPreference + import torch + + config = MXDynamicActivationMXWeightConfig( + activation_dtype=torch.float8_e4m3fn, + weight_dtype=torch.float8_e4m3fn, + kernel_preference=KernelPreference.AUTO, + ) + else: + from torchao.prototype.mx_formats.inference_workflow import ( + NVFP4DynamicActivationNVFP4WeightConfig, + ) + + config = NVFP4DynamicActivationNVFP4WeightConfig( + use_dynamic_per_tensor_scale=True, + use_triton_kernel=True, + ) + except ImportError as exc: + raise RuntimeError( + f"{method.removeprefix('torchao_').upper()} artifact creation requires a compatible " + "Blackwell PyTorch, TorchAO, and kernel environment." + ) from exc + return TorchAoConfig(quant_type=config, modules_to_not_convert=excluded) return None @@ -112,34 +452,196 @@ def build_pipeline_quantization_config(method: str, components: list[str], dtype return PipelineQuantizationConfig(quant_mapping={component: config for component in components}) +def coerce_pipeline_quantization_config(quant_config: Any): + """Normalize graph-provided component mappings to Diffusers' public config.""" + + if not quant_config: + return None + if isinstance(quant_config, str): + if not quant_config.strip(): + return None + raise ValueError("Quantization config must be connected to a Quantization Config node output.") + from diffusers.quantizers import PipelineQuantizationConfig + + if isinstance(quant_config, PipelineQuantizationConfig): + return quant_config + if isinstance(quant_config, dict): + return PipelineQuantizationConfig(quant_mapping=quant_config) + raise ValueError("Quantization config must be a Diffusers PipelineQuantizationConfig or component mapping.") + + +def build_qwen_pipeline_quantization_config( + *, + components: list[str], + quantization_mode: str, + compute_dtype: Any, + quant_type: str = "nf4", + double_quant: bool = True, +): + """Build component-correct BnB configs for Qwen's Diffusers and Transformers parts.""" + + if quantization_mode != "bnb_4bit" or not components: + return None + from importlib.util import find_spec + + if find_spec("bitsandbytes") is None: + raise RuntimeError( + "BitsAndBytes 4-bit quantization is not installed. Install the CUDA quantization extra or choose another mode." + ) + from diffusers import BitsAndBytesConfig as DiffusersBitsAndBytesConfig + from diffusers.quantizers import PipelineQuantizationConfig + from transformers import BitsAndBytesConfig as TransformersBitsAndBytesConfig + + quant_mapping = {} + if "transformer" in components: + quant_mapping["transformer"] = DiffusersBitsAndBytesConfig( + load_in_4bit=True, + bnb_4bit_quant_type=quant_type, + bnb_4bit_compute_dtype=compute_dtype, + bnb_4bit_use_double_quant=bool(double_quant), + ) + if "text_encoder" in components: + quant_mapping["text_encoder"] = TransformersBitsAndBytesConfig( + load_in_4bit=True, + bnb_4bit_quant_type=quant_type, + bnb_4bit_compute_dtype=compute_dtype, + bnb_4bit_use_double_quant=bool(double_quant), + ) + return PipelineQuantizationConfig(quant_mapping=quant_mapping) if quant_mapping else None + + def add_progress_callback(node: NodeBase, pipeline: Any, call_kwargs: dict[str, Any], steps: int): + node.progress( + 0, + phase="denoising", + message=f"Denoising 0/{steps}", + current_step=0, + total_steps=steps, + elapsed_seconds=0.0, + ) if not supports_arg(pipeline, "callback_on_step_end"): return - total_steps = max(1, int(steps or 1)) - def callback(pipe, step_index, timestep, callback_kwargs): - progress = int(((step_index + 1) / total_steps) * 100) - node.progress( - min(100, max(0, progress)), - phase="denoising", - message=f"Denoising {step_index + 1}/{total_steps}", - current_step=step_index + 1, - total_steps=total_steps, - ) - return callback_kwargs + # NodeBase owns the common cancellation contract. Propagating it here + # makes /stop interrupt generic Diffusers pipelines at the next step, + # just like the established video/audio nodes. + return node.pipe_callback(pipe, step_index, timestep, callback_kwargs) or callback_kwargs call_kwargs["callback_on_step_end"] = callback if supports_arg(pipeline, "callback_on_step_end_tensor_inputs"): call_kwargs["callback_on_step_end_tensor_inputs"] = [] +def prepare_reference_images(image: Any, adapter: ImagePipelineAdapter) -> Any: + """Translate the generic multi-reference contract for a pipeline adapter.""" + if not isinstance(image, (list, tuple)) or len(image) <= 1: + return image[0] if isinstance(image, (list, tuple)) and image else image + if adapter.multi_image_strategy != "stitch_horizontal": + return image if isinstance(image, list) else list(image) + + from PIL import Image + + if not all(isinstance(item, Image.Image) for item in image): + raise ValueError("Horizontal multi-reference stitching currently requires PIL image inputs.") + converted = [item.convert("RGB") for item in image] + target_height = max(item.height for item in converted) + resized = [ + item + if item.height == target_height + else item.resize( + (max(1, round(item.width * target_height / item.height)), target_height), Image.Resampling.LANCZOS + ) + for item in converted + ] + canvas = Image.new("RGB", (sum(item.width for item in resized), target_height)) + left = 0 + for item in resized: + canvas.paste(item, (left, 0)) + left += item.width + return canvas + + +class FluxReduxPipelineBundle: + """Callable adapter combining the official Redux prior with FLUX.1-dev.""" + + def __init__(self, prior: Any, base: Any): + self.prior = prior + self.base = base + self._modiff_image_adapter = IMAGE_PIPELINE_ADAPTERS["FluxReduxPipeline"] + + @property + def device(self): + return getattr(self.base, "device", "cpu") + + @property + def _execution_device(self): + return getattr(self.base, "_execution_device", self.device) + + def __call__( + self, + *, + image, + prompt="", + negative_prompt=None, + width=None, + height=None, + num_inference_steps=28, + guidance_scale=3.5, + strength=None, + padding_mask_crop=None, + max_sequence_length=512, + generator=None, + output_type="pil", + return_dict=True, + callback_on_step_end=None, + callback_on_step_end_tensor_inputs=None, + reference_strength=1.0, + ): + # FluxPriorReduxPipeline produces the exact reference/text embeddings + # consumed by FluxPipeline. For multiple images, current Diffusers + # applies the per-image scales and returns one upstream weighted sum. + prior_kwargs = {"image": image, "prompt": prompt or None, "return_dict": True} + if isinstance(image, (list, tuple)) and len(image) > 1: + secondary_strength = float(reference_strength) + if not 0.0 <= secondary_strength <= 1.0: + raise ValueError("reference_strength must be between 0 and 1.") + reference_scales = [1.0, *([secondary_strength] * (len(image) - 1))] + prior_kwargs["prompt_embeds_scale"] = reference_scales + prior_kwargs["pooled_prompt_embeds_scale"] = reference_scales + prior_output = self.prior(**prior_kwargs) + prompt_embeds = prior_output.prompt_embeds + pooled_prompt_embeds = prior_output.pooled_prompt_embeds + base_kwargs = { + "prompt_embeds": prompt_embeds, + "pooled_prompt_embeds": pooled_prompt_embeds, + "num_inference_steps": num_inference_steps, + "guidance_scale": guidance_scale, + "generator": generator, + "output_type": output_type, + "return_dict": return_dict, + "max_sequence_length": max_sequence_length, + } + for key, value in (("width", width), ("height", height)): + if value is not None: + base_kwargs[key] = value + if callback_on_step_end is not None: + base_kwargs["callback_on_step_end"] = callback_on_step_end + if callback_on_step_end_tensor_inputs is not None: + base_kwargs["callback_on_step_end_tensor_inputs"] = callback_on_step_end_tensor_inputs + return self.base(**base_kwargs) + + class LoadPipeline(NodeBase): """Load a generic Diffusers image pipeline.""" label = "Load Diffusers Image Pipeline" category = "Diffusers Image" resizable = True + # Flux2KleinPipeline uses one resident pipeline for generation and edit + # calls. Mode validates the requested operation but does not participate in + # from_pretrained(), so changing it must not force another 13-minute load. + cache_ignored_params = frozenset({"mode"}) params = { "pipeline": {"label": "Pipeline", "display": "output", "type": "image_diffusion_pipeline"}, "model_id": { @@ -156,6 +658,12 @@ class LoadPipeline(NodeBase): "default": "FluxPipeline", "fieldOptions": {"noValidation": True}, }, + "mode": { + "label": "Mode", + "type": "string", + "options": IMAGE_PIPELINE_MODE_OPTIONS, + "default": "text_to_image", + }, "revision": {"label": "Revision", "type": "string", "default": ""}, "dtype": { "label": "DType", @@ -167,7 +675,15 @@ class LoadPipeline(NodeBase): "quantization_mode": { "label": "Quantization", "type": "string", - "options": ["none", "bnb_4bit", "bnb_8bit", "quanto_float8", "torchao_float8"], + "options": [ + "none", + "bnb_4bit", + "bnb_8bit", + "quanto_float8", + "quanto_int8", + "torchao_float8", + "torchao_int8_weight_only", + ], "default": "none", }, "quantized_components": { @@ -178,6 +694,17 @@ class LoadPipeline(NodeBase): "fieldOptions": {"multiple": True}, "default": [], }, + "execution_recipe": { + "label": "Execution Recipe", + "display": "input", + "type": "diffusers_execution_recipe", + }, + "device_map": { + "label": "Device Map", + "type": "string", + "options": ["none", "cuda", "auto", "balanced", "balanced_low_0", "cpu"], + "default": "none", + }, "auto_offload": {"label": "Auto offload", "type": "bool", "default": True}, "offload_mode": offload_mode_param(modes=DIFFUSERS_IMAGE_OFFLOAD_MODES), "enable_vae_slicing": {"label": "VAE slicing", "type": "bool", "default": True}, @@ -186,18 +713,86 @@ class LoadPipeline(NodeBase): "resolved_artifact": {"label": "Resolved Artifact", "display": "output", "type": "string"}, } + @staticmethod + def _validate_mode(pipeline_class_name: str, requested_mode: str): + adapter = IMAGE_PIPELINE_ADAPTERS.get(pipeline_class_name) + if adapter is None or requested_mode not in adapter.modes: + supported = ", ".join(sorted(adapter.modes if adapter else [])) or "none" + raise ValueError(f"{pipeline_class_name} does not support {requested_mode}. Supported modes: {supported}.") + return adapter + + def __call__(self, **kwargs): + pipeline_class_name = str(kwargs.get("pipeline_class") or "FluxPipeline") + requested_mode = str(kwargs.get("mode") or "text_to_image") + self._validate_mode(pipeline_class_name, requested_mode) + return super().__call__(**kwargs) + + def prepare_for_workflow_reuse(self): + """Restore a resident image pipeline before another graph adopts it.""" + pipeline = self.output.get("pipeline") + unload_lora_weights = getattr(pipeline, "unload_lora_weights", None) + if callable(unload_lora_weights): + unload_lora_weights() + def execute(self, **kwargs): - model_id = repo_value(kwargs.get("model_id")) or FLUX_SCHNELL_REPO + from modules.DiffusersRuntime.main import ( + apply_execution_recipe_to_pipeline, + assert_runtime_quantization_full_residency, + enable_parallel_weight_loading, + ) + + execution_recipe = kwargs.get("execution_recipe") + if execution_recipe is None: + execution_recipe = {} + if not isinstance(execution_recipe, dict): + raise TypeError("Execution Recipe must come from a Diffusers Execution Recipe node.") pipeline_class_name = str(kwargs.get("pipeline_class") or "FluxPipeline") - pipeline_class = pipeline_class_from_name(pipeline_class_name) + requested_mode = str(kwargs.get("mode") or "text_to_image") + adapter = self._validate_mode(pipeline_class_name, requested_mode) + model_selection = kwargs.get("model_id") + selected_model_id = repo_value(model_selection) + model_id = selected_model_id or adapter.default_repo + model_source = model_selection.get("source") if selected_model_id and isinstance(model_selection, dict) else "hub" dtype = str_to_dtype(kwargs.get("dtype") or "bfloat16") - device = kwargs.get("device") or DEFAULT_DEVICE - revision = none_if_blank(kwargs.get("revision")) + device = execution_recipe.get("device") or kwargs.get("device") or DEFAULT_DEVICE + revision = resolve_model_revision( + model_id, + none_if_blank(kwargs.get("revision")), + source=model_source, + ) auto_offload = bool(kwargs.get("auto_offload", True)) - offload_mode = normalize_offload_mode(kwargs.get("offload_mode") or OFFLOAD_MODE_MODEL_CPU, auto_offload=auto_offload) + recipe_offload = execution_recipe.get("offload_mode") + if recipe_offload is not None: + auto_offload = str(recipe_offload) != "none" + offload_mode = normalize_offload_mode( + recipe_offload if recipe_offload is not None else kwargs.get("offload_mode") or OFFLOAD_MODE_MODEL_CPU, + auto_offload=auto_offload, + device=device, + ) quantization_mode = str(kwargs.get("quantization_mode") or "none") quantized_components = normalize_component_list(kwargs.get("quantized_components")) - quant_config = build_pipeline_quantization_config(quantization_mode, quantized_components, dtype) + if model_id == QWEN_IMAGE_2512_PREQUANTIZED_REPO: + quantization_mode = "none" + quantized_components = [] + quant_config = coerce_pipeline_quantization_config(execution_recipe.get("quantization_config")) + if quant_config is None: + if pipeline_class_name.startswith("QwenImage") and quantization_mode == "bnb_4bit": + quant_config = build_qwen_pipeline_quantization_config( + components=quantized_components, + quantization_mode=quantization_mode, + compute_dtype=dtype, + ) + else: + quant_config = build_pipeline_quantization_config(quantization_mode, quantized_components, dtype) + if quant_config is not None: + assert_runtime_quantization_full_residency( + model_id=model_id, + revision=revision, + quantization_config=quant_config, + device=str(device), + offload_mode=offload_mode, + device_map=execution_recipe.get("device_map") or "none", + ) load_kwargs = { "torch_dtype": dtype, @@ -207,25 +802,91 @@ def execute(self, **kwargs): } if quant_config is not None: load_kwargs["quantization_config"] = quant_config - if str(device).startswith("cuda"): - load_kwargs["device_map"] = "cuda" + recipe_device_map = execution_recipe.get("device_map") + direct_device_map = kwargs.get("device_map") + device_map = ( + direct_device_map + if direct_device_map not in (None, "", "none") + else recipe_device_map or direct_device_map or "none" + ) + if device_map == "none" and offload_mode == "none" and str(device) in {"cuda", "cuda:0"}: + # A no-offload complete pipeline is meant to be fully resident. + # Stream its shards directly to the target accelerator instead of + # materializing a second full CPU copy before pipeline.to(device). + device_map = "cuda" + if device_map == "cuda" and offload_mode == "none" and str(device) in {"cuda", "cuda:0"}: + enable_parallel_weight_loading() + if device_map != "none": + load_kwargs["device_map"] = device_map + elif quant_config is not None and str(device).startswith("cuda"): + load_kwargs["device_map"] = "cuda" + max_memory = execution_recipe.get("max_memory") + if isinstance(max_memory, dict) and max_memory: + load_kwargs["max_memory"] = max_memory self.progress(-1, phase="loading", message=f"Loading {pipeline_class_name}") - pipeline = pipeline_class.from_pretrained(model_id, **load_kwargs) - if kwargs.get("enable_vae_slicing", True) and hasattr(pipeline, "enable_vae_slicing"): - pipeline.enable_vae_slicing() - if kwargs.get("enable_vae_tiling", True) and hasattr(pipeline, "enable_vae_tiling"): - pipeline.enable_vae_tiling() - - self.progress(-1, phase="loading", message=f"Applying {offload_mode} offload") - apply_pipeline_offload( - pipeline, - mode=offload_mode, - device=device, - node_id=self.node_id, - scope="diffusers-image", + if pipeline_class_name == "FluxReduxPipeline": + from diffusers import FluxPipeline, FluxPriorReduxPipeline + + base_kwargs = dict(load_kwargs) + # The graph revision belongs to the Redux prior. Its fixed FLUX.1 + # base is a separate Hub snapshot and must carry its own pin. + base_kwargs["revision"] = require_catalog_revision(FLUX_DEV_REPO, model_type="FluxDevPipeline") + with self.diffusers_loading_progress(): + base = FluxPipeline.from_pretrained(FLUX_DEV_REPO, **base_kwargs) + + prior_kwargs = dict(load_kwargs) + prior_kwargs.pop("quantization_config", None) + prior_kwargs.pop("device_map", None) + # Redux exposes optional text components. Share the base FLUX + # encoders/tokenizers so its documented prompt input is real rather + # than silently ignored, then leave the base pipeline embedding-only. + for component in ("text_encoder", "text_encoder_2", "tokenizer", "tokenizer_2"): + prior_kwargs[component] = getattr(base, component, None) + prior = FluxPriorReduxPipeline.from_pretrained(model_id, **prior_kwargs) + if hasattr(base, "register_modules"): + base.register_modules( + text_encoder=None, + text_encoder_2=None, + tokenizer=None, + tokenizer_2=None, + ) + pipeline = FluxReduxPipelineBundle(prior, base) + else: + pipeline_class = pipeline_class_from_name(pipeline_class_name) + with self.diffusers_loading_progress(): + pipeline = pipeline_class.from_pretrained(model_id, **load_kwargs) + pipeline._modiff_image_adapter = adapter + runtime_recipe = { + **execution_recipe, + "vae_slicing": bool( + execution_recipe.get("vae_slicing", kwargs.get("enable_vae_slicing", True)) + ), + "vae_tiling": bool( + execution_recipe.get("vae_tiling", kwargs.get("enable_vae_tiling", True)) + ), + } + runtime_owner = pipeline.base if isinstance(pipeline, FluxReduxPipelineBundle) else pipeline + pipeline._modiff_runtime_config = apply_execution_recipe_to_pipeline(runtime_owner, runtime_recipe) + + self.progress(99, phase="component_placement", message=f"Applying {offload_mode} offload") + offload_targets = ( + [pipeline.prior, pipeline.base] if isinstance(pipeline, FluxReduxPipelineBundle) else [pipeline] ) - self.mm_add(pipeline, priority=2) + for index, target in enumerate(offload_targets): + offload_result = apply_pipeline_offload( + target, + mode=offload_mode, + device=device, + node_id=self.node_id, + scope=f"diffusers-image-{index}" if len(offload_targets) > 1 else "diffusers-image", + ) + self.progress( + -1, + phase="component_placement", + message=f"Registering pipeline memory ({getattr(offload_result, 'method', 'configured')})", + ) + self.mm_add(target, priority=2) return {"pipeline": pipeline, "resolved_artifact": model_id} @@ -236,15 +897,46 @@ class Generate(NodeBase): category = "Diffusers Image" resizable = True params = { - "pipeline": {"label": "Pipeline", "display": "input", "type": "image_diffusion_pipeline"}, + "pipeline": {"label": "Pipeline", "display": "input", "type": "image_diffusion_pipeline", "required": True}, "prompt": {"label": "Prompt", "display": "textarea", "type": "text", "default": ""}, "negative_prompt": {"label": "Negative Prompt", "display": "textarea", "type": "text", "default": ""}, "width": {"label": "Width", "type": "int", "default": 1024, "min": 16, "max": 2048, "step": 16}, "height": {"label": "Height", "type": "int", "default": 1024, "min": 16, "max": 2048, "step": 16}, "seed": {"label": "Seed", "type": "int", "display": "random", "default": 0, "min": 0, "max": 4294967295}, - "num_inference_steps": {"label": "Steps", "display": "slider", "type": "int", "default": 4, "min": 1, "max": 100}, - "guidance_scale": {"label": "Guidance", "display": "slider", "type": "float", "default": 0.0, "min": 0, "max": 20, "step": 0.1}, - "strength": {"label": "Strength", "display": "slider", "type": "float", "default": 0.8, "min": 0, "max": 1, "step": 0.01}, + "num_inference_steps": { + "label": "Steps", + "display": "slider", + "type": "int", + "default": 4, + "min": 1, + "max": 100, + }, + "guidance_scale": { + "label": "Guidance", + "display": "slider", + "type": "float", + "default": 0.0, + "min": 0, + "max": 20, + "step": 0.1, + }, + "strength": { + "label": "Strength", + "display": "slider", + "type": "float", + "default": 0.8, + "min": 0, + "max": 1, + "step": 0.01, + }, + "padding_mask_crop": { + "label": "Padding Mask Crop", + "type": "int", + "default": 0, + "min": 0, + "max": 512, + "step": 8, + }, "max_sequence_length": {"label": "Max Sequence Length", "type": "int", "default": 256, "min": 1, "max": 2048}, "output_type": {"label": "Output type", "type": "string", "options": ["pil", "np", "pt"], "default": "pil"}, "images": {"label": "Images", "display": "output", "type": "image"}, @@ -273,11 +965,16 @@ def execute(self, **kwargs): "output_type": kwargs.get("output_type") or "pil", "return_dict": True, } - for key in ("negative_prompt", "guidance_scale", "max_sequence_length", "strength"): - if supports_arg(pipeline, key): - call_kwargs[key] = kwargs.get(key) + adapter = getattr(pipeline, "_modiff_image_adapter", None) or ImagePipelineAdapter( + type(pipeline).__name__, frozenset() + ) + adapter.apply_generation_parameters(pipeline, kwargs, call_kwargs) add_progress_callback(self, pipeline, call_kwargs, steps) - result = pipeline(**call_kwargs) + self._active_pipeline = pipeline + try: + result = pipeline(**call_kwargs) + finally: + self._active_pipeline = None images = getattr(result, "images", result) return {"images": images, "width_out": call_kwargs["width"], "height_out": call_kwargs["height"]} @@ -289,13 +986,33 @@ class Edit(Generate): category = "Diffusers Image" params = { **Generate.params, - "image": {"label": "Image", "display": "input", "type": "image"}, + "image": { + "label": "Image or references", + "display": "input", + "type": "image", + "required": True, + "description": "A single source image or a list of references for pipelines that support multi-reference editing.", + }, + "reference_strength": { + "label": "Secondary reference strength", + "type": "float", + "default": 1.0, + "min": 0.0, + "max": 1.0, + "step": 0.05, + "description": "Relative influence of every reference after the first composition anchor, when supported by the selected adapter.", + }, } def execute(self, **kwargs): if kwargs.get("image") is None: raise ValueError("Diffusers Image Edit needs an input image.") - return self._execute_conditioned(kwargs, {"image": kwargs.get("image")}) + pipeline = kwargs.get("pipeline") + adapter = getattr(pipeline, "_modiff_image_adapter", None) or ImagePipelineAdapter( + type(pipeline).__name__ if pipeline is not None else "unknown", frozenset() + ) + image = prepare_reference_images(kwargs.get("image"), adapter) + return self._execute_conditioned(kwargs, {"image": image}) def _execute_conditioned(self, kwargs, extra_kwargs): import torch @@ -317,13 +1034,24 @@ def _execute_conditioned(self, kwargs, extra_kwargs): "return_dict": True, **extra_kwargs, } - for key in ("negative_prompt", "width", "height", "guidance_scale", "max_sequence_length", "strength"): - if supports_arg(pipeline, key): - call_kwargs[key] = kwargs.get(key) + adapter = getattr(pipeline, "_modiff_image_adapter", None) + if adapter is None: + guidance_parameter = "true_cfg_scale" if supports_arg(pipeline, "true_cfg_scale") else "guidance_scale" + adapter = ImagePipelineAdapter(type(pipeline).__name__, frozenset(), guidance_parameter=guidance_parameter) + adapter.apply_generation_parameters(pipeline, kwargs, call_kwargs) add_progress_callback(self, pipeline, call_kwargs, steps) - result = pipeline(**call_kwargs) + self._active_pipeline = pipeline + try: + result = pipeline(**call_kwargs) + finally: + self._active_pipeline = None images = getattr(result, "images", result) - return {"images": images, "width_out": int(kwargs.get("width") or 0), "height_out": int(kwargs.get("height") or 0)} + actual_width, actual_height = output_image_dimensions(images, call_kwargs["output_type"]) + return { + "images": images, + "width_out": actual_width if actual_width is not None else int(kwargs.get("width") or 0), + "height_out": actual_height if actual_height is not None else int(kwargs.get("height") or 0), + } class Inpaint(Edit): @@ -333,13 +1061,25 @@ class Inpaint(Edit): category = "Diffusers Image" params = { **Edit.params, - "mask_image": {"label": "Mask", "display": "input", "type": "image"}, + "mask_image": {"label": "Mask", "display": "input", "type": "image", "required": True}, + "output_type": {"label": "Output type", "type": "string", "options": ["pil"], "default": "pil"}, } def execute(self, **kwargs): if kwargs.get("image") is None or kwargs.get("mask_image") is None: raise ValueError("Diffusers Image Inpaint needs image and mask_image inputs.") - return self._execute_conditioned(kwargs, {"image": kwargs.get("image"), "mask_image": kwargs.get("mask_image")}) + if kwargs.get("output_type", "pil") != "pil": + raise ValueError("Diffusers Image Inpaint requires output_type='pil' for mask-safe compositing.") + result = self._execute_conditioned( + kwargs, + {"image": kwargs.get("image"), "mask_image": kwargs.get("mask_image")}, + ) + result["images"] = composite_masked_pil_outputs( + result.get("images"), + kwargs.get("image"), + kwargs.get("mask_image"), + ) + return result class ControlGenerate(Edit): @@ -349,7 +1089,7 @@ class ControlGenerate(Edit): category = "Diffusers Image" params = { **Generate.params, - "control_image": {"label": "Control Image", "display": "input", "type": "image"}, + "control_image": {"label": "Control Image", "display": "input", "type": "image", "required": True}, } def execute(self, **kwargs): @@ -365,11 +1105,36 @@ class LoadAdapter(NodeBase): category = "Diffusers Image" resizable = True params = { - "pipeline": {"label": "Pipeline", "display": "input", "type": "image_diffusion_pipeline"}, - "adapter_path": {"label": "Adapter", "display": "modelselect", "type": "string", "fieldOptions": {"noValidation": True, "sources": ["hub", "local"]}}, + "pipeline": {"label": "Pipeline", "display": "input", "type": "image_diffusion_pipeline", "required": True}, + "adapter_path": { + "label": "Adapter", + "display": "modelselect", + "type": "string", + "fieldOptions": {"noValidation": True, "sources": ["hub", "local"]}, + }, "weight_name": {"label": "Weight name", "type": "string", "default": ""}, + "expected_sha256": { + "label": "Expected SHA-256", + "type": "string", + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + }, "adapter_name": {"label": "Adapter name", "type": "string", "default": "default"}, - "scale": {"label": "Scale", "display": "slider", "type": "float", "default": 1.0, "min": -2, "max": 2, "step": 0.01}, + "replace_existing": { + "label": "Replace existing adapters", + "type": "boolean", + "default": True, + "description": "Unload adapters already attached to this pipeline before loading this graph's adapter.", + }, + "scale": { + "label": "Scale", + "display": "slider", + "type": "float", + "default": 1.0, + "min": -2, + "max": 2, + "step": 0.01, + }, "output": {"label": "Pipeline", "display": "output", "type": "image_diffusion_pipeline"}, } @@ -377,7 +1142,8 @@ def execute(self, **kwargs): pipeline = kwargs.get("pipeline") if pipeline is None: raise ValueError("LoadAdapter needs a pipeline input.") - adapter_path = repo_value(kwargs.get("adapter_path")) + adapter_selection = kwargs.get("adapter_path") + adapter_path = repo_value(adapter_selection) if not adapter_path: return {"output": pipeline} if not hasattr(pipeline, "load_lora_weights"): @@ -385,9 +1151,50 @@ def execute(self, **kwargs): load_kwargs = { "adapter_name": kwargs.get("adapter_name") or "default", } - if none_if_blank(kwargs.get("weight_name")): - load_kwargs["weight_name"] = kwargs.get("weight_name") + weight_name = none_if_blank(kwargs.get("weight_name")) + source = adapter_selection.get("source") if isinstance(adapter_selection, dict) else "hub" + if source == "hub": + from pathlib import Path + from utils.huggingface import cached_file_path + + repo_id = adapter_path + if not weight_name: + parts = adapter_path.split("/") + if len(parts) >= 3: + repo_id, weight_name = "/".join(parts[:2]), "/".join(parts[2:]) + if not weight_name: + raise ValueError("A Hub adapter requires a pinned weight_name for app-managed installation.") + cached = cached_file_path(repo_id, weight_name) + if not cached: + raise FileNotFoundError( + f"Adapter {repo_id}/{weight_name} is not installed. Install the pinned file through Model Manager first." + ) + cached_path = Path(cached) + adapter_path = str(cached_path.parent) + weight_name = cached_path.name + expected_sha256 = str(kwargs.get("expected_sha256") or "").strip().lower().removeprefix("sha256:") + if expected_sha256: + digest = hashlib.sha256() + with cached_path.open("rb") as handle: + for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""): + digest.update(chunk) + if digest.hexdigest() != expected_sha256: + raise ValueError( + f"Adapter {repo_id}/{weight_name} failed its pinned SHA-256 verification. " + "Repair the adapter through Model Manager before running this graph." + ) + if weight_name: + load_kwargs["weight_name"] = weight_name + replace_existing = bool(kwargs.get("replace_existing", True)) + adapter_scales = dict(getattr(pipeline, "_modiff_adapter_scales", {}) or {}) + if replace_existing and hasattr(pipeline, "unload_lora_weights"): + pipeline.unload_lora_weights() + adapter_scales.clear() pipeline.load_lora_weights(adapter_path, **load_kwargs) + raw_scale = kwargs.get("scale") + scale = 1.0 if raw_scale is None else float(raw_scale) + adapter_scales[load_kwargs["adapter_name"]] = scale if hasattr(pipeline, "set_adapters"): - pipeline.set_adapters([load_kwargs["adapter_name"]], [float(kwargs.get("scale") or 1.0)]) + pipeline.set_adapters(list(adapter_scales), list(adapter_scales.values())) + pipeline._modiff_adapter_scales = adapter_scales return {"output": pipeline} diff --git a/modules/DiffusersRuntime/__init__.py b/modules/DiffusersRuntime/__init__.py new file mode 100644 index 0000000..216bacc --- /dev/null +++ b/modules/DiffusersRuntime/__init__.py @@ -0,0 +1 @@ +from .main import * # noqa: F403 diff --git a/modules/DiffusersRuntime/main.py b/modules/DiffusersRuntime/main.py new file mode 100644 index 0000000..3dd8303 --- /dev/null +++ b/modules/DiffusersRuntime/main.py @@ -0,0 +1,2226 @@ +"""Composable runtime configuration nodes shared by Diffusers pipelines.""" + +import gc +import json +import importlib.util +import os +from pathlib import Path +from typing import Any + +from modiff.NodeBase import NodeBase +from modiff.model_artifact_catalog import resolve_model_revision +from modules.DiffusersImage.main import QUANT_COMPONENTS, quant_config_for +from utils.torch_utils import str_to_dtype + + +ATTENTION_BACKENDS = [ + "auto", + "native", + "_native_flash", + "_native_efficient", + "_native_math", + "_native_cudnn", + "flex", + "flash", + "flash_hub", + "flash_varlen", + "flash_varlen_hub", + "flash_4_hub", + "_flash_3", + "_flash_varlen_3", + "_flash_3_hub", + "_flash_3_varlen_hub", + "aiter", + "sage", + "sage_hub", + "sage_varlen", + "xformers", +] + +ATTENTION_COMPONENTS = ( + "transformer", + "transformer_2", + "unet", + "controlnet", + "prior", +) + +SAFETENSORS_DTYPE_BYTES = { + "F64": 8, + "F32": 4, + "F16": 2, + "BF16": 2, + "I64": 8, + "I32": 4, + "I16": 2, + "I8": 1, + "U8": 1, + "BOOL": 1, +} + +NO_QUANTIZATION_CONFIG = { + "schema_version": 1, + "backend": "none", + "disabled": True, +} + + +def _normalize_optional_quantization_config(value: Any): + if isinstance(value, dict) and value.get("disabled") is True and value.get("backend") == "none": + return None + return value + + +def enable_parallel_weight_loading() -> bool: + """Enable Hugging Face's supported sharded-loader fast path. + + Diffusers recommends pairing parallel shard loading with a direct CUDA + device map so the allocator can reserve the destination up front. Respect + an explicit environment choice; otherwise enable the upstream default + worker count for complete pipelines that opt into direct loading. + """ + + configured = os.environ.get("HF_ENABLE_PARALLEL_LOADING") + if configured is None: + os.environ["HF_ENABLE_PARALLEL_LOADING"] = "YES" + return True + return configured.strip().upper() in {"1", "ON", "TRUE", "YES"} + + +def _string_list(value: Any) -> list[str]: + if value in (None, ""): + return [] + if isinstance(value, str): + values = value.replace("\n", ",").split(",") + elif isinstance(value, (list, tuple, set)): + values = value + else: + values = [value] + return list(dict.fromkeys(str(item).strip() for item in values if str(item).strip())) + + +def _component_names(value: Any) -> list[str]: + return [name for name in _string_list(value) if name in QUANT_COMPONENTS] + + +def _model_value(value: Any) -> str: + if isinstance(value, dict): + return str(value.get("value") or value.get("id") or "").strip() + if isinstance(value, list): + return _model_value(value[0]) if value else "" + return str(value or "").strip() + + +def _component_for_weight(filename: str) -> str: + normalized = str(filename).replace("\\", "/").lstrip("/") + parts = [part for part in normalized.split("/") if part] + return parts[0] if len(parts) > 1 else "root" + + +def summarize_safetensors_files(files: list[dict[str, Any]]) -> dict[str, Any]: + components: dict[str, dict[str, Any]] = {} + total_source_bytes = 0 + total_weight_bytes = 0 + total_parameters = 0 + largest_shard = None + + for item in files: + name = str(item.get("name") or "") + component = _component_for_weight(name) + source_bytes = item.get("source_bytes") + source_bytes = int(source_bytes) if isinstance(source_bytes, int) and source_bytes >= 0 else None + dtype_counts = { + str(dtype): int(count) + for dtype, count in dict(item.get("parameter_count") or {}).items() + if isinstance(count, int) and count >= 0 + } + parameter_count = sum(dtype_counts.values()) + weight_bytes = sum(SAFETENSORS_DTYPE_BYTES.get(dtype, 0) * count for dtype, count in dtype_counts.items()) + entry = components.setdefault( + component, + { + "component": component, + "source_bytes": 0, + "source_bytes_known": True, + "weight_bytes": 0, + "parameter_count": 0, + "dtype_counts": {}, + "files": [], + "largest_shard": None, + }, + ) + entry["files"].append(name) + entry["parameter_count"] += parameter_count + entry["weight_bytes"] += weight_bytes + for dtype, count in dtype_counts.items(): + entry["dtype_counts"][dtype] = entry["dtype_counts"].get(dtype, 0) + count + if source_bytes is None: + entry["source_bytes_known"] = False + else: + entry["source_bytes"] += source_bytes + total_source_bytes += source_bytes + shard = {"name": name, "bytes": source_bytes} + if entry["largest_shard"] is None or source_bytes > entry["largest_shard"]["bytes"]: + entry["largest_shard"] = shard + if largest_shard is None or source_bytes > largest_shard["bytes"]: + largest_shard = shard + total_parameters += parameter_count + total_weight_bytes += weight_bytes + + return { + "components": [components[name] for name in sorted(components)], + "total_source_bytes": total_source_bytes, + "total_weight_bytes": total_weight_bytes, + "total_parameter_count": total_parameters, + "largest_shard": largest_shard, + "file_count": len(files), + } + + +def inspect_diffusers_components(model_id: str, revision: str | None = None) -> dict[str, Any]: + """Read Safetensors headers without materializing model weights.""" + from huggingface_hub import HfApi, parse_local_safetensors_file_metadata, parse_safetensors_file_metadata + from modiff.config import CONFIG + + model_path = Path(model_id).expanduser() + files = [] + if model_path.exists(): + if model_path.is_file(): + candidates = [model_path] if model_path.suffix == ".safetensors" else [] + root = model_path.parent + else: + candidates = sorted(model_path.rglob("*.safetensors")) + root = model_path + for candidate in candidates: + metadata = parse_local_safetensors_file_metadata(candidate) + files.append( + { + "name": candidate.relative_to(root).as_posix(), + "source_bytes": candidate.stat().st_size, + "parameter_count": dict(metadata.parameter_count), + } + ) + source = "local" + else: + revision = resolve_model_revision(model_id, revision) + api = HfApi(token=CONFIG.hf["token"], library_name="MoDiff") + try: + info = api.model_info(model_id, revision=revision, files_metadata=True) + except TypeError: + info = api.model_info(model_id, revision=revision) + siblings = getattr(info, "siblings", []) or [] + for sibling in siblings: + filename = str(getattr(sibling, "rfilename", "") or "") + if not filename.endswith(".safetensors"): + continue + size = getattr(sibling, "size", None) + metadata = parse_safetensors_file_metadata( + model_id, + filename, + revision=revision, + token=CONFIG.hf["token"], + ) + files.append( + { + "name": filename, + "source_bytes": int(size) if isinstance(size, int) else None, + "parameter_count": dict(metadata.parameter_count), + } + ) + source = "hub" + + summary = summarize_safetensors_files(files) + summary.update({"model_id": model_id, "revision": revision, "source": source}) + return summary + + +def build_quantization_config_v2( + *, + backend: str, + components: Any, + dtype: Any, + excluded_modules: Any = None, + component_overrides: Any = None, +): + """Build a real per-component PipelineQuantizationConfig. + + ``component_overrides`` maps component names to either a backend string or + ``{"backend": ..., "excluded_modules": [...]}``. This keeps the common UI + compact while allowing graph authors to preserve sensitive projections, + norms, or modulation layers per component. + """ + selected = _component_names(components) + default_backend = str(backend or "none") + default_excluded = _string_list(excluded_modules) + + if component_overrides in (None, ""): + overrides = {} + elif isinstance(component_overrides, str): + try: + overrides = json.loads(component_overrides) + except json.JSONDecodeError as exc: + raise ValueError(f"Component overrides must be valid JSON: {exc.msg}.") from exc + elif isinstance(component_overrides, dict): + overrides = component_overrides + else: + raise TypeError("Component overrides must be a JSON object or dictionary.") + if not isinstance(overrides, dict): + raise ValueError("Component overrides must decode to a JSON object.") + + unknown = sorted(set(overrides) - set(QUANT_COMPONENTS)) + if unknown: + raise ValueError(f"Unknown quantized components: {', '.join(unknown)}.") + + component_order = list(dict.fromkeys([*selected, *overrides.keys()])) + quant_mapping = {} + summary = {} + for component in component_order: + override = overrides.get(component, {}) + if isinstance(override, str): + component_backend = override + component_excluded = default_excluded + elif isinstance(override, dict): + component_backend = str(override.get("backend") or default_backend) + component_excluded = _string_list(override.get("excluded_modules", default_excluded)) + else: + raise TypeError(f"Override for {component} must be a backend string or object.") + if component_backend == "none": + continue + config = quant_config_for(component_backend, dtype, component_excluded) + if config is None: + raise ValueError(f"Unsupported quantization backend {component_backend!r} for {component}.") + quant_mapping[component] = config + summary[component] = { + "backend": component_backend, + "excluded_modules": component_excluded, + } + + if not quant_mapping: + return None, summary + from diffusers.quantizers import PipelineQuantizationConfig + + return PipelineQuantizationConfig(quant_mapping=quant_mapping), summary + + +def apply_attention_backend(pipeline: Any, backend: str, components: Any = None) -> dict[str, Any]: + requested = str(backend or "auto") + if requested == "auto": + return {"requested": "auto", "applied": [], "default_selection": True} + + requested_components = _string_list(components) + candidate_names = requested_components or list(ATTENTION_COMPONENTS) + applied = [] + unsupported = [] + seen = set() + for name in candidate_names: + component = pipeline if name in ("pipeline", "self") else getattr(pipeline, name, None) + if component is None or id(component) in seen: + continue + seen.add(id(component)) + setter = getattr(component, "set_attention_backend", None) + if not callable(setter): + unsupported.append(name) + continue + try: + setter(requested) + except Exception as exc: + raise RuntimeError(f"Could not apply attention backend {requested!r} to {name}: {exc}") from exc + applied.append(name) + + if not applied: + raise RuntimeError( + f"Attention backend {requested!r} could not be applied because the selected pipeline components " + "do not expose set_attention_backend()." + ) + return { + "requested": requested, + "applied": applied, + "unsupported": unsupported, + "default_selection": False, + } + + +def configure_vae_memory(pipeline: Any, *, slicing: bool, tiling: bool) -> dict[str, Any]: + vae = getattr(pipeline, "vae", None) + owner = vae if vae is not None else pipeline + applied = [] + unsupported = [] + + def configure(enabled: bool, enable_names: tuple[str, ...], disable_names: tuple[str, ...], label: str): + names = enable_names if enabled else disable_names + method = next((getattr(owner, name, None) for name in names if callable(getattr(owner, name, None))), None) + if method is None: + unsupported.append(label) + return + method() + applied.append({"feature": label, "enabled": enabled}) + + configure(slicing, ("enable_slicing", "enable_vae_slicing"), ("disable_slicing", "disable_vae_slicing"), "slicing") + configure(tiling, ("enable_tiling", "enable_vae_tiling"), ("disable_tiling", "disable_vae_tiling"), "tiling") + return {"applied": applied, "unsupported": unsupported} + + +def configure_denoiser_cache( + pipeline: Any, + *, + strategy: str, + threshold: float = 0.05, + options: Any = None, +) -> dict[str, Any]: + requested = str(strategy or "none") + cache_options = _json_object(options, "Denoiser cache options") + candidates = [ + (name, getattr(pipeline, name, None)) + for name in ("transformer", "transformer_2", "unet", "prior") + if getattr(pipeline, name, None) is not None + ] + if requested == "none": + disabled = [] + for name, component in candidates: + if bool(getattr(component, "is_cache_enabled", False)): + disable = getattr(component, "disable_cache", None) + if callable(disable): + disable() + disabled.append(name) + return {"requested": requested, "applied": [], "disabled": disabled} + from diffusers.hooks import ( + FasterCacheConfig, + FirstBlockCacheConfig, + MagCacheConfig, + PyramidAttentionBroadcastConfig, + TaylorSeerCacheConfig, + TextKVCacheConfig, + ) + + if requested == "first_block": + config = FirstBlockCacheConfig(threshold=float(cache_options.get("threshold", threshold))) + elif requested == "magcache": + calibrate = bool(cache_options.get("calibrate", False)) + mag_ratios = cache_options.get("mag_ratios") + if not calibrate and mag_ratios is None: + raise ValueError( + "MagCache needs model-specific mag_ratios. Set calibrate=true and run one representative " + "generation to measure them, or paste a previously calibrated mag_ratios array into cache options." + ) + config = MagCacheConfig( + threshold=float(cache_options.get("threshold", 0.06)), + max_skip_steps=int(cache_options.get("max_skip_steps", 3)), + retention_ratio=float(cache_options.get("retention_ratio", 0.2)), + num_inference_steps=int(cache_options.get("num_inference_steps", 28)), + mag_ratios=mag_ratios, + calibrate=calibrate, + ) + elif requested == "taylorseer": + disable_after = cache_options.get("disable_cache_after_step") + config = TaylorSeerCacheConfig( + cache_interval=int(cache_options.get("cache_interval", 5)), + disable_cache_before_step=int(cache_options.get("disable_cache_before_step", 3)), + disable_cache_after_step=int(disable_after) if disable_after is not None else None, + max_order=int(cache_options.get("max_order", 1)), + use_lite_mode=bool(cache_options.get("use_lite_mode", False)), + ) + elif requested == "pab": + config = PyramidAttentionBroadcastConfig( + spatial_attention_block_skip_range=cache_options.get("spatial_attention_block_skip_range", 2), + temporal_attention_block_skip_range=cache_options.get("temporal_attention_block_skip_range"), + cross_attention_block_skip_range=cache_options.get("cross_attention_block_skip_range"), + spatial_attention_timestep_skip_range=tuple( + cache_options.get("spatial_attention_timestep_skip_range", (100, 800)) + ), + temporal_attention_timestep_skip_range=tuple( + cache_options.get("temporal_attention_timestep_skip_range", (100, 800)) + ), + cross_attention_timestep_skip_range=tuple( + cache_options.get("cross_attention_timestep_skip_range", (100, 800)) + ), + current_timestep_callback=lambda: getattr(pipeline, "current_timestep", None), + ) + elif requested == "fastercache": + config = FasterCacheConfig( + spatial_attention_block_skip_range=int(cache_options.get("spatial_attention_block_skip_range", 2)), + temporal_attention_block_skip_range=cache_options.get("temporal_attention_block_skip_range"), + tensor_format=str(cache_options.get("tensor_format", "BCFHW")), + is_guidance_distilled=bool(cache_options.get("is_guidance_distilled", False)), + current_timestep_callback=lambda: getattr(pipeline, "current_timestep", None), + ) + elif requested == "text_kv": + config = TextKVCacheConfig() + else: + raise ValueError(f"Unsupported denoiser cache strategy {requested!r}.") + + applied = [] + unsupported = [] + for name, component in candidates: + enable = getattr(component, "enable_cache", None) + if not callable(enable): + unsupported.append(name) + continue + try: + if bool(getattr(component, "is_cache_enabled", False)): + component.disable_cache() + enable(config) + except Exception as exc: + raise RuntimeError(f"Could not enable {requested} cache on {name}: {exc}") from exc + applied.append(name) + if not applied: + raise RuntimeError(f"{requested} cache is not supported by this pipeline's denoiser components.") + return { + "requested": requested, + "config": type(config).__name__, + "options": cache_options, + "applied": applied, + "unsupported": unsupported, + "quality_warning": "Denoiser caching can improve speed but may reduce generation quality; validate each model, scheduler, and shape.", + } + + +def configure_regional_compile( + pipeline: Any, + *, + enabled: bool, + components: Any = None, + backend: str = "inductor", + mode: str = "default", + fullgraph: bool = False, + dynamic: bool = False, +) -> dict[str, Any]: + if not enabled: + return {"requested": False, "applied": []} + requested_components = _string_list(components) or ["transformer", "transformer_2", "unet", "prior"] + applied = [] + unsupported = [] + for name in requested_components: + component = getattr(pipeline, name, None) + if component is None: + continue + compile_regions = getattr(component, "compile_repeated_blocks", None) + if not callable(compile_regions): + unsupported.append(name) + continue + try: + compile_regions( + backend=str(backend or "inductor"), + mode=str(mode or "default"), + fullgraph=bool(fullgraph), + dynamic=bool(dynamic), + ) + except Exception as exc: + raise RuntimeError(f"Could not regionally compile {name}: {exc}") from exc + applied.append(name) + if not applied: + raise RuntimeError("Regional compilation is not supported by the selected pipeline components.") + return { + "requested": True, + "applied": applied, + "unsupported": unsupported, + "backend": str(backend or "inductor"), + "mode": str(mode or "default"), + "fullgraph": bool(fullgraph), + "dynamic": bool(dynamic), + } + + +def configure_layerwise_casting( + pipeline: Any, + *, + enabled: bool, + components: Any = None, + storage_dtype: str = "float8_e4m3fn", + compute_dtype: str = "bfloat16", +) -> dict[str, Any]: + """Apply Diffusers' supported layerwise-casting hooks once per component. + + Layerwise casting mutates model weights and installs forward hooks. It + cannot be safely changed in-place after a pipeline has been cached, so a + changed signature is rejected with an actionable reload message instead of + stacking a second set of hooks. + """ + if not enabled: + return {"requested": False, "applied": []} + + import torch + from diffusers.hooks import apply_layerwise_casting + + storage = getattr(torch, str(storage_dtype or ""), None) + compute = getattr(torch, str(compute_dtype or ""), None) + if storage not in { + getattr(torch, "float8_e4m3fn", None), + getattr(torch, "float8_e5m2", None), + }: + raise ValueError( + "Layerwise casting storage dtype must be float8_e4m3fn or float8_e5m2 " + "on a runtime that exposes that dtype." + ) + if compute not in {torch.float16, torch.bfloat16, torch.float32}: + raise ValueError("Layerwise casting compute dtype must be float16, bfloat16, or float32.") + + requested_components = _string_list(components) or ["transformer", "transformer_2", "unet", "prior"] + signature = { + "storage_dtype": str(storage_dtype), + "compute_dtype": str(compute_dtype), + } + applied = [] + already_applied = [] + unavailable = [] + for name in requested_components: + component = getattr(pipeline, name, None) + if component is None: + continue + current = getattr(component, "_modiff_layerwise_casting_signature", None) + if current == signature: + already_applied.append(name) + continue + if current is not None: + raise RuntimeError( + f"Layerwise casting for {name} is already configured differently. " + "Reload the pipeline before changing its storage or compute dtype." + ) + try: + apply_layerwise_casting( + component, + storage_dtype=storage, + compute_dtype=compute, + skip_modules_pattern="auto", + non_blocking=False, + ) + except Exception as exc: + raise RuntimeError(f"Could not apply layerwise casting to {name}: {exc}") from exc + component._modiff_layerwise_casting_signature = dict(signature) + applied.append(name) + if not applied and not already_applied: + unavailable = requested_components + raise RuntimeError( + "Layerwise casting is not available because this pipeline has none of the selected denoiser components." + ) + return { + "requested": True, + "applied": applied, + "alreadyApplied": already_applied, + "unavailable": unavailable, + **signature, + } + + +def configure_channels_last( + pipeline: Any, + *, + enabled: bool, + components: Any = None, +) -> dict[str, Any]: + """Use channels-last only for explicitly selected convolutional modules.""" + if not enabled: + return {"requested": False, "applied": []} + + import torch + + requested_components = _string_list(components) or ["unet", "vae"] + applied = [] + already_applied = [] + unavailable = [] + for name in requested_components: + component = getattr(pipeline, name, None) + if component is None: + unavailable.append(name) + continue + if bool(getattr(component, "_modiff_channels_last", False)): + already_applied.append(name) + continue + move = getattr(component, "to", None) + if not callable(move): + unavailable.append(name) + continue + try: + move(memory_format=torch.channels_last) + except Exception as exc: + raise RuntimeError(f"Could not apply channels-last layout to {name}: {exc}") from exc + component._modiff_channels_last = True + applied.append(name) + if not applied and not already_applied: + raise RuntimeError( + "Channels-last layout is not available because this pipeline has none of the selected components." + ) + return { + "requested": True, + "applied": applied, + "alreadyApplied": already_applied, + "unavailable": unavailable, + } + + +def release_pipeline_memory( + pipeline: Any, + *, + move_to_cpu: bool = True, + disable_cache: bool = True, + reset_compile_cache: bool = False, +) -> dict[str, Any]: + released = [] + errors = [] + if disable_cache: + for name in ("transformer", "transformer_2", "unet", "prior"): + component = getattr(pipeline, name, None) + disable = getattr(component, "disable_cache", None) + if callable(disable) and bool(getattr(component, "is_cache_enabled", False)): + try: + disable() + released.append(f"{name}_cache") + except Exception as exc: + errors.append(f"{name} cache: {exc}") + free_hooks = getattr(pipeline, "maybe_free_model_hooks", None) + if callable(free_hooks): + try: + free_hooks() + released.append("model_hooks") + except Exception as exc: + errors.append(f"model hooks: {exc}") + reset_map = getattr(pipeline, "reset_device_map", None) + if callable(reset_map) and getattr(pipeline, "hf_device_map", None): + try: + reset_map() + released.append("device_map") + except Exception as exc: + errors.append(f"device map: {exc}") + if move_to_cpu: + move = getattr(pipeline, "to", None) + if callable(move): + try: + move("cpu") + released.append("pipeline_to_cpu") + except Exception as exc: + errors.append(f"pipeline to CPU: {exc}") + + try: + import torch + + if reset_compile_cache: + reset = getattr(getattr(torch, "_dynamo", None), "reset", None) + if callable(reset): + reset() + released.append("torch_compile_cache") + if bool(getattr(getattr(torch, "cuda", None), "is_available", lambda: False)()): + torch.cuda.empty_cache() + released.append("cuda_allocator_cache") + elif bool(getattr(getattr(torch, "xpu", None), "is_available", lambda: False)()): + empty = getattr(torch.xpu, "empty_cache", None) + if callable(empty): + empty() + released.append("xpu_allocator_cache") + elif bool(getattr(getattr(getattr(torch, "backends", None), "mps", None), "is_available", lambda: False)()): + empty = getattr(getattr(torch, "mps", None), "empty_cache", None) + if callable(empty): + empty() + released.append("mps_allocator_cache") + except Exception as exc: + errors.append(f"accelerator cache: {exc}") + gc.collect() + released.append("python_garbage_collection") + return {"released": released, "errors": errors} + + +def _json_object(value: Any, label: str) -> dict[str, Any]: + if value in (None, ""): + return {} + if isinstance(value, dict): + return dict(value) + if isinstance(value, str): + try: + parsed = json.loads(value) + except json.JSONDecodeError as exc: + raise ValueError(f"{label} must be valid JSON: {exc.msg}.") from exc + if isinstance(parsed, dict): + return parsed + raise ValueError(f"{label} must be a JSON object.") + + +def build_execution_recipe( + *, + quantization_config: Any = None, + device_map: str = "none", + device_map_overrides: Any = None, + max_memory: Any = None, + offload_mode: str = "none", + device: str = "cuda:0", + attention_backend: str = "auto", + attention_components: Any = None, + vae_slicing: bool = True, + vae_tiling: bool = True, + layerwise_casting: bool = False, + layerwise_casting_components: Any = None, + layerwise_storage_dtype: str = "float8_e4m3fn", + layerwise_compute_dtype: str = "bfloat16", + channels_last: bool = False, + channels_last_components: Any = None, + regional_compile: bool = False, + compile_components: Any = None, + compile_backend: str = "inductor", + compile_mode: str = "default", + compile_fullgraph: bool = False, + compile_dynamic: bool = False, + denoiser_cache: str = "none", + cache_threshold: float = 0.05, + cache_options: Any = None, +) -> dict[str, Any]: + quantization_config = _normalize_optional_quantization_config(quantization_config) + normalized_device_map = str(device_map or "none") + if normalized_device_map not in {"none", "cuda", "auto", "balanced", "balanced_low_0", "cpu", "manual"}: + raise ValueError(f"Unsupported device map strategy {normalized_device_map!r}.") + memory = _json_object(max_memory, "Max memory") + manual_map = _json_object(device_map_overrides, "Manual device map") + if normalized_device_map == "manual" and not manual_map: + raise ValueError("Manual device mapping needs at least one component placement entry.") + normalized_offload = str(offload_mode or "none") + if quantization_config is not None and ( + normalized_offload != "none" or normalized_device_map not in {"none", "cuda"} + ): + raise ValueError( + "Runtime quantization cannot use CPU/disk offload or a split device map. " + "Use Create optimized copy, or select no offload on a GPU that can hold the unquantized source model." + ) + normalized_memory = {} + for key, value in memory.items(): + normalized_key = int(key) if isinstance(key, str) and key.isdigit() else key + normalized_memory[normalized_key] = value + return { + "schema_version": 1, + "quantization_config": quantization_config, + "device_map": manual_map if normalized_device_map == "manual" else normalized_device_map, + "device_map_strategy": normalized_device_map, + "max_memory": normalized_memory, + "offload_mode": normalized_offload, + "device": str(device or "cuda:0"), + "attention_backend": str(attention_backend or "auto"), + "attention_components": _string_list(attention_components), + "vae_slicing": bool(vae_slicing), + "vae_tiling": bool(vae_tiling), + "layerwise_casting": bool(layerwise_casting), + "layerwise_casting_components": _string_list(layerwise_casting_components), + "layerwise_storage_dtype": str(layerwise_storage_dtype or "float8_e4m3fn"), + "layerwise_compute_dtype": str(layerwise_compute_dtype or "bfloat16"), + "channels_last": bool(channels_last), + "channels_last_components": _string_list(channels_last_components), + "regional_compile": bool(regional_compile), + "compile_components": _string_list(compile_components), + "compile_backend": str(compile_backend or "inductor"), + "compile_mode": str(compile_mode or "default"), + "compile_fullgraph": bool(compile_fullgraph), + "compile_dynamic": bool(compile_dynamic), + "denoiser_cache": str(denoiser_cache or "none"), + "cache_threshold": float(cache_threshold), + "cache_options": _json_object(cache_options, "Denoiser cache options"), + } + + +def execution_recipe_summary(recipe: dict[str, Any]) -> dict[str, Any]: + summary = dict(recipe) + quant = summary.pop("quantization_config", None) + mapping = getattr(quant, "quant_mapping", None) + if isinstance(mapping, dict): + summary["quantized_components"] = sorted(mapping) + elif isinstance(quant, dict): + summary["quantized_components"] = sorted(str(key) for key in quant) + else: + summary["quantized_components"] = [] + return summary + + +def loader_runtime_options( + kwargs: dict[str, Any], + *, + default_device: str, + default_offload_mode: str, + direct_device_load: bool = False, +) -> tuple[dict[str, Any], str, str, dict[str, Any]]: + """Resolve a connected recipe without breaking legacy loader controls. + + Complete Diffusers pipelines can be materialized directly on their final + accelerator for a no-offload run. This avoids constructing an entire + CPU-resident copy and then migrating every component with ``pipeline.to``. + Component-by-component loaders must leave ``direct_device_load`` disabled + because they may need to assemble mixed-dtype CPU components first. + """ + from modiff.diffusers_offload import normalize_offload_mode + + recipe = kwargs.get("execution_recipe") or {} + if not isinstance(recipe, dict): + raise TypeError("Execution Recipe must come from a Diffusers Execution Recipe node.") + device = str(recipe.get("device") or kwargs.get("device") or default_device) + recipe_offload = recipe.get("offload_mode") + auto_offload = bool(kwargs.get("auto_offload", True)) + if recipe_offload is not None: + auto_offload = str(recipe_offload) != "none" + offload_mode = normalize_offload_mode( + recipe_offload if recipe_offload is not None else kwargs.get("offload_mode") or default_offload_mode, + auto_offload=auto_offload, + device=device, + ) + load_kwargs = {} + quantization_config = recipe.get("quantization_config") + if quantization_config is not None: + assert_runtime_quantization_full_residency( + model_id=_model_value(kwargs.get("model_id")), + revision=str(kwargs.get("revision") or "").strip() or None, + quantization_config=quantization_config, + device=device, + offload_mode=offload_mode, + device_map=recipe.get("device_map") or "none", + ) + load_kwargs["quantization_config"] = quantization_config + device_map = recipe.get("device_map") or "none" + if direct_device_load and offload_mode == "none" and str(device) in {"cuda", "cuda:0"}: + if device_map == "none": + device_map = "cuda" + if device_map == "cuda": + enable_parallel_weight_loading() + if device_map != "none": + load_kwargs["device_map"] = device_map + max_memory = recipe.get("max_memory") + if isinstance(max_memory, dict) and max_memory: + load_kwargs["max_memory"] = max_memory + return recipe, device, offload_mode, load_kwargs + + +def runtime_residency_reserve_bytes(total_vram: int | None = None) -> int: + """Workspace and OS reserve used before any on-load quantization.""" + gib = 1024**3 + mib = 1024**2 + os_reserve = 600 * mib if os.name == "nt" else 400 * mib + if os.name == "nt" and isinstance(total_vram, int) and total_vram > 15 * gib: + os_reserve += 100 * mib + return int(0.8 * gib) + os_reserve + + +def assert_runtime_quantization_full_residency( + *, + model_id: str, + revision: str | None, + quantization_config: Any, + device: str, + offload_mode: str, + device_map: Any, +) -> dict[str, int]: + """Admit expert on-load quantization only when the source fits entirely on one GPU.""" + if quantization_config is None: + return {"source_weight_bytes": 0, "required_bytes": 0, "free_bytes": 0} + if not model_id: + raise ValueError("Runtime quantization needs a source model so full GPU residency can be checked.") + if not str(device).startswith("cuda"): + raise ValueError("Runtime quantization requires a CUDA/ROCm GPU. Use a pre-quantized artifact on this backend.") + if str(offload_mode or "none") != "none" or device_map not in {None, "none", "cuda"}: + raise ValueError( + "Runtime quantization is blocked while CPU/disk offload or split placement is enabled. " + "Use Create optimized copy instead." + ) + + inventory = inspect_diffusers_components(model_id, revision) + source_bytes = int(inventory.get("total_weight_bytes") or 0) + if source_bytes <= 0: + raise ValueError( + "MoDiff could not prove the source model's unquantized GPU residency from Safetensors metadata. " + "Use the dedicated optimized-artifact workflow instead." + ) + + from modiff.config import CONFIG + from modiff.hardware import get_hardware_snapshot + + hardware = get_hardware_snapshot(CONFIG.paths.get("data"), refresh=True) + requested_index = 0 + try: + requested_index = int(str(device).split(":", 1)[1]) + except (IndexError, ValueError): + pass + gpu = next( + ( + item + for item in hardware.get("devices", []) + if item.get("type") == "cuda" and int(item.get("index") or 0) == requested_index + ), + None, + ) + if not gpu: + raise ValueError(f"Runtime quantization cannot find the requested accelerator {device}.") + free_bytes = int(gpu.get("torch_vram_free") or gpu.get("vram_free") or 0) + total_vram = int(gpu.get("torch_vram_total") or gpu.get("vram_total") or 0) + reserve = runtime_residency_reserve_bytes(total_vram) + required = source_bytes + reserve + if free_bytes <= 0 or required > free_bytes: + gib = 1024**3 + raise ValueError( + f"Runtime quantization needs the full unquantized source plus workspace on {device}: " + f"{required / gib:.1f} GiB required, {free_bytes / gib:.1f} GiB free. " + "Use Create optimized copy on a larger GPU or install a qualified pre-quantized artifact." + ) + return {"source_weight_bytes": source_bytes, "required_bytes": required, "free_bytes": free_bytes} + + +def apply_execution_recipe_to_pipeline(pipeline: Any, recipe: dict[str, Any]) -> dict[str, Any]: + if not recipe: + return {"attention": None, "vae": None} + vae = configure_vae_memory( + pipeline, + slicing=bool(recipe.get("vae_slicing", True)), + tiling=bool(recipe.get("vae_tiling", True)), + ) + layerwise = configure_layerwise_casting( + pipeline, + enabled=bool(recipe.get("layerwise_casting", False)), + components=recipe.get("layerwise_casting_components"), + storage_dtype=recipe.get("layerwise_storage_dtype") or "float8_e4m3fn", + compute_dtype=recipe.get("layerwise_compute_dtype") or "bfloat16", + ) + channels_last = configure_channels_last( + pipeline, + enabled=bool(recipe.get("channels_last", False)), + components=recipe.get("channels_last_components"), + ) + attention = apply_attention_backend( + pipeline, + recipe.get("attention_backend") or "auto", + recipe.get("attention_components"), + ) + cache = configure_denoiser_cache( + pipeline, + strategy=recipe.get("denoiser_cache") or "none", + threshold=float(recipe.get("cache_threshold", 0.05)), + options=recipe.get("cache_options"), + ) + compile_result = configure_regional_compile( + pipeline, + enabled=bool(recipe.get("regional_compile", False)), + components=recipe.get("compile_components"), + backend=recipe.get("compile_backend") or "inductor", + mode=recipe.get("compile_mode") or "default", + fullgraph=bool(recipe.get("compile_fullgraph", False)), + dynamic=bool(recipe.get("compile_dynamic", False)), + ) + return { + "attention": attention, + "vae": vae, + "layerwiseCasting": layerwise, + "channelsLast": channels_last, + "cache": cache, + "compile": compile_result, + } + + +def build_runtime_capabilities( + hardware: dict[str, Any], + *, + torch_module: Any, + package_available=None, +) -> dict[str, Any]: + """Build conservative runtime candidates from live backend/package facts.""" + if package_available is None: + def package_available(name: str) -> bool: + try: + return importlib.util.find_spec(name) is not None + except (ImportError, ModuleNotFoundError, ValueError): + return False + + devices = list(hardware.get("devices") or []) + accelerator = next((item for item in devices if item.get("type") != "cpu"), None) + backend = str((accelerator or {}).get("type") or "cpu") + torch_version = getattr(torch_module, "version", None) + hip_version = getattr(torch_version, "hip", None) + cuda_version = getattr(torch_version, "cuda", None) + vendor = ( + "amd" + if backend == "cuda" and hip_version + else "nvidia" + if backend == "cuda" + else "apple" + if backend == "mps" + else "intel" + if backend == "xpu" + else "cpu" + ) + + capability = None + if backend == "cuda": + try: + capability = tuple(int(item) for item in torch_module.cuda.get_device_capability(0)) + except Exception: + capability = None + + bf16 = False + if backend == "cuda": + probe = getattr(torch_module.cuda, "is_bf16_supported", None) + try: + bf16 = bool(probe()) if callable(probe) else bool(capability and capability >= (8, 0)) + except Exception: + bf16 = False + elif backend == "mps": + try: + probe_tensor = torch_module.empty( + (1,), + dtype=torch_module.bfloat16, + device="mps", + ) + bf16 = probe_tensor is not None + del probe_tensor + except Exception: + bf16 = False + elif backend == "xpu": + probe = getattr(getattr(torch_module, "xpu", None), "is_bf16_supported", None) + try: + bf16 = bool(probe()) if callable(probe) else False + except Exception: + bf16 = False + elif backend == "cpu": + cpu_probe = getattr(getattr(torch_module, "cpu", None), "_is_avx512_bf16_supported", None) + try: + bf16 = bool(cpu_probe()) if callable(cpu_probe) else False + except Exception: + bf16 = False + + flash_attn_available = backend == "cuda" and package_available("flash_attn") + flash_attn_3_available = vendor == "nvidia" and package_available("flash_attn_interface") + hub_kernels_available = vendor == "nvidia" and package_available("kernels") + aiter_available = vendor == "amd" and package_available("aiter") + sage_available = backend == "cuda" and package_available("sageattention") + xformers_available = vendor == "nvidia" and package_available("xformers") + attention = { + "native": {"available": True, "reason": "PyTorch SDPA fallback"}, + "_native_math": {"available": True, "reason": "Portable PyTorch math fallback"}, + "_native_flash": { + "available": backend == "cuda", + "reason": "PyTorch accelerator flash SDPA" if backend == "cuda" else "Requires a CUDA/ROCm torch backend", + }, + "_native_efficient": { + "available": backend == "cuda", + "reason": "PyTorch memory-efficient SDPA" if backend == "cuda" else "Requires a CUDA/ROCm torch backend", + }, + "_native_cudnn": { + "available": vendor == "nvidia", + "reason": "NVIDIA cuDNN attention" if vendor == "nvidia" else "Requires NVIDIA CUDA", + }, + "flex": { + "available": backend == "cuda" and hasattr(getattr(torch_module, "nn", None), "attention"), + "reason": ( + "PyTorch FlexAttention is available" + if backend == "cuda" and hasattr(getattr(torch_module, "nn", None), "attention") + else "Requires a PyTorch accelerator build with FlexAttention" + ), + }, + "flash": { + "available": flash_attn_available, + "reason": ( + "flash-attn package detected" + if flash_attn_available + else "flash-attn package is not installed" + if backend == "cuda" + else "Requires a CUDA/ROCm torch backend" + ), + }, + "flash_varlen": { + "available": flash_attn_available, + "reason": "flash-attn variable-length kernels detected" if flash_attn_available else "Requires flash-attn", + }, + "flash_hub": { + "available": hub_kernels_available, + "reason": "Hugging Face Hub kernels runtime detected" if hub_kernels_available else "Requires the kernels package on NVIDIA CUDA", + }, + "flash_varlen_hub": { + "available": hub_kernels_available, + "reason": "Hugging Face variable-length kernels are available" if hub_kernels_available else "Requires the kernels package on NVIDIA CUDA", + }, + "flash_4_hub": { + "available": hub_kernels_available and bool(capability and capability >= (9, 0)), + "reason": ( + "FlashAttention 4 Hub kernels are available on Hopper/Blackwell" + if hub_kernels_available and capability and capability >= (9, 0) + else "Requires Hub kernels and NVIDIA compute capability 9.0 or newer" + ), + }, + "_flash_3": { + "available": flash_attn_3_available, + "reason": "FlashAttention 3 interface detected" if flash_attn_3_available else "Requires the FlashAttention 3 interface on NVIDIA Hopper", + }, + "_flash_varlen_3": { + "available": flash_attn_3_available, + "reason": "FlashAttention 3 variable-length interface detected" if flash_attn_3_available else "Requires the FlashAttention 3 interface on NVIDIA Hopper", + }, + "_flash_3_hub": { + "available": hub_kernels_available and bool(capability and capability >= (9, 0)), + "reason": "FlashAttention 3 Hub kernels are available" if hub_kernels_available else "Requires Hub kernels on NVIDIA Hopper", + }, + "_flash_3_varlen_hub": { + "available": hub_kernels_available and bool(capability and capability >= (9, 0)), + "reason": "FlashAttention 3 variable-length Hub kernels are available" if hub_kernels_available else "Requires Hub kernels on NVIDIA Hopper", + }, + "aiter": { + "available": aiter_available, + "reason": ( + "ROCm AITER package detected" + if aiter_available + else "AITER package is not installed" + if vendor == "amd" + else "Requires AMD ROCm" + ), + }, + "sage": { + "available": sage_available, + "reason": ( + "SageAttention package detected" + if sage_available + else "SageAttention package is not installed" + if backend == "cuda" + else "Requires a CUDA/ROCm torch backend" + ), + }, + "sage_hub": { + "available": hub_kernels_available, + "reason": "SageAttention Hub kernels are available" if hub_kernels_available else "Requires the kernels package on NVIDIA CUDA", + }, + "sage_varlen": { + "available": sage_available, + "reason": "SageAttention variable-length kernels detected" if sage_available else "Requires SageAttention", + }, + "xformers": { + "available": xformers_available, + "reason": ( + "xFormers package detected" + if xformers_available + else "xFormers package is not installed" + if vendor == "nvidia" + else "MoDiff only enables xFormers on NVIDIA CUDA" + ), + }, + } + + torchao_installed = package_available("torchao") + quanto_installed = package_available("optimum.quanto") or package_available("quanto") + bitsandbytes_installed = package_available("bitsandbytes") + torchao_fp8_device = vendor == "nvidia" and capability is not None and capability >= (8, 9) + blackwell_device = vendor == "nvidia" and capability is not None and capability >= (10, 0) + quantization = { + "none": { + "available": True, + "reason": "No runtime quantization requested", + }, + "bnb_4bit": { + "available": bitsandbytes_installed and vendor == "nvidia", + "reason": "bitsandbytes detected on NVIDIA CUDA" + if vendor == "nvidia" + else "MoDiff has not qualified this backend on the active platform", + }, + "bnb_8bit": { + "available": bitsandbytes_installed and vendor == "nvidia", + "reason": "bitsandbytes detected on NVIDIA CUDA" + if vendor == "nvidia" + else "MoDiff has not qualified this backend on the active platform", + }, + "quanto_float8": { + "available": quanto_installed, + "reason": "Quanto package detected" if quanto_installed else "Quanto package is not installed", + }, + "quanto_int8": { + "available": quanto_installed, + "reason": "Quanto package detected" if quanto_installed else "Quanto package is not installed", + }, + "torchao_int8_weight_only": { + "available": torchao_installed, + "reason": "TorchAO package detected" if torchao_installed else "TorchAO package is not installed", + }, + "torchao_float8": { + "available": torchao_installed and torchao_fp8_device, + "reason": "TorchAO detected on an FP8-capable NVIDIA device" + if torchao_fp8_device + else "Requires TorchAO and a qualified NVIDIA compute capability of at least 8.9", + }, + "torchao_mxfp8": { + "available": torchao_installed and blackwell_device, + "reason": "TorchAO MXFP8 detected on NVIDIA Blackwell" + if blackwell_device + else "Requires TorchAO and NVIDIA compute capability 10.0 or newer", + }, + "torchao_nvfp4": { + "available": torchao_installed and blackwell_device and package_available("mslk"), + "reason": "TorchAO NVFP4 and MSLK detected on NVIDIA Blackwell" + if blackwell_device + else "Requires TorchAO, MSLK, and NVIDIA compute capability 10.0 or newer", + }, + } + + mps_memory = None + if backend == "mps": + runtime = getattr(torch_module, "mps", None) + mps_memory = {} + for name in ("current_allocated_memory", "driver_allocated_memory", "recommended_max_memory"): + probe = getattr(runtime, name, None) + try: + mps_memory[name] = int(probe()) if callable(probe) else None + except Exception as exc: + mps_memory[name] = None + mps_memory[f"{name}_error"] = str(exc) + + system = hardware.get("system") if isinstance(hardware.get("system"), dict) else {} + disk = hardware.get("disk") if isinstance(hardware.get("disk"), dict) else {} + return { + "backend": backend, + "vendor": vendor, + "device": (accelerator or {"device": "cpu:0"}).get("device"), + "device_capability": list(capability) if capability else None, + "torch_version": str(getattr(torch_module, "__version__", "unknown")), + "cuda_version": str(cuda_version) if cuda_version else None, + "hip_version": str(hip_version) if hip_version else None, + "dtypes": {"float32": True, "float16": backend != "cpu", "bfloat16": bf16}, + "attention_backends": attention, + "quantization_backends": quantization, + "compile": { + "available": callable(getattr(torch_module, "compile", None)), + "regional_requires_model_probe": True, + }, + "denoiser_cache": { + name: { + "available": package_available("diffusers"), + "requires_model_probe": True, + "quality_neutral": False, + } + for name in ("first_block", "magcache", "taylorseer", "pab", "fastercache", "text_kv") + }, + "mps_memory": mps_memory, + "memory": { + "accelerator_total_bytes": (accelerator or {}).get("vram_total"), + "accelerator_free_bytes": (accelerator or {}).get("vram_free"), + "system_total_bytes": system.get("ram_total"), + "system_available_bytes": system.get("ram_available"), + "disk_free_bytes": disk.get("free_bytes"), + }, + } + + +QUANTIZATION_NOMINAL_BITS = { + "bnb_4bit": 4, + "bnb_8bit": 8, + "quanto_float8": 8, + "torchao_float8": 8, + "torchao_mxfp8": 8, + "torchao_nvfp4": 4, +} + + +def _ceil_div(value: int, divisor: int) -> int: + return (value + divisor - 1) // divisor + + +def estimate_pipeline_memory( + inventory: Any, + *, + width: int, + height: int, + frames: int = 1, + batch_size: int = 1, + dtype_bytes: int = 2, + latent_channels: int = 16, + spatial_compression: int = 8, + temporal_compression: int = 4, + quantization_summary: Any = None, + offload_mode: str = "none", +) -> dict[str, Any]: + """Return weight and latent floors without presenting a guessed peak VRAM value.""" + inventory = _json_object(inventory, "Component inventory") + quantization = _json_object(quantization_summary, "Quantization summary") + dimensions = { + "width": max(1, int(width)), + "height": max(1, int(height)), + "frames": max(1, int(frames)), + "batch_size": max(1, int(batch_size)), + "dtype_bytes": max(1, int(dtype_bytes)), + "latent_channels": max(1, int(latent_channels)), + "spatial_compression": max(1, int(spatial_compression)), + "temporal_compression": max(1, int(temporal_compression)), + } + latent_width = _ceil_div(dimensions["width"], dimensions["spatial_compression"]) + latent_height = _ceil_div(dimensions["height"], dimensions["spatial_compression"]) + latent_frames = _ceil_div(dimensions["frames"], dimensions["temporal_compression"]) + latent_elements = ( + dimensions["batch_size"] * dimensions["latent_channels"] * latent_frames * latent_height * latent_width + ) + latent_bytes = latent_elements * dimensions["dtype_bytes"] + + component_rows = [] + unknown_dtypes = set() + idealized_total = 0 + for item in inventory.get("components") or []: + if not isinstance(item, dict): + continue + component = str(item.get("component") or "unknown") + parameters = max(0, int(item.get("parameter_count") or 0)) + original_bytes = max(0, int(item.get("weight_bytes") or 0)) + for dtype in dict(item.get("dtype_counts") or {}): + if dtype not in SAFETENSORS_DTYPE_BYTES: + unknown_dtypes.add(str(dtype)) + policy = quantization.get(component) if isinstance(quantization.get(component), dict) else {} + backend = str(policy.get("backend") or "none") + nominal_bits = QUANTIZATION_NOMINAL_BITS.get(backend) + idealized_bytes = _ceil_div(parameters * nominal_bits, 8) if nominal_bits else original_bytes + exclusions = _string_list(policy.get("excluded_modules")) + component_rows.append( + { + "component": component, + "parameter_count": parameters, + "unquantized_weight_floor_bytes": original_bytes, + "quantization_backend": backend, + "nominal_bits": nominal_bits, + "idealized_weight_floor_bytes": idealized_bytes, + "has_preserved_modules": bool(exclusions), + } + ) + idealized_total += idealized_bytes + + unquantized_total = max(0, int(inventory.get("total_weight_bytes") or 0)) + largest_component = max(component_rows, key=lambda item: item["idealized_weight_floor_bytes"], default=None) + normalized_offload = str(offload_mode or "none") + if normalized_offload == "none": + residency_proxy = idealized_total + residency_basis = "all component weight floors" + else: + residency_proxy = int((largest_component or {}).get("idealized_weight_floor_bytes") or 0) + residency_basis = "largest component weight floor; runtime hook and layer granularity can differ" + + warnings = [ + "Peak accelerator memory is not estimated: attention, intermediate activations, allocator state, and backend workspaces require a measured run.", + "Quantized weight floors exclude packing metadata, scales, preserved modules, and backend overhead.", + ] + if unknown_dtypes: + warnings.append( + f"Unknown Safetensors dtypes were excluded from byte totals: {', '.join(sorted(unknown_dtypes))}." + ) + if not component_rows: + warnings.append("No Safetensors component metadata was available for this model.") + + return { + "schema_version": 1, + "confidence": { + "unquantized_weight_floor": "high" if component_rows and not unknown_dtypes else "limited", + "idealized_quantized_weight_floor": "low" if quantization else "high", + "latent_tensor_floor": "high for the explicit compression and channel assumptions", + "peak_accelerator_memory": "unavailable until measured", + }, + "shape": { + **dimensions, + "latent_width": latent_width, + "latent_height": latent_height, + "latent_frames": latent_frames, + "latent_elements": latent_elements, + "latent_tensor_bytes": latent_bytes, + }, + "weights": { + "unquantized_weight_floor_bytes": unquantized_total, + "idealized_weight_floor_bytes": idealized_total, + "accelerator_resident_weight_proxy_bytes": residency_proxy, + "accelerator_residency_basis": residency_basis, + "components": component_rows, + }, + "offload_mode": normalized_offload, + "peak_accelerator_memory_bytes": None, + "warnings": warnings, + } + + +def plan_execution_recipes( + capabilities: Any, + inventory: Any, + *, + preference: str = "balanced", +) -> dict[str, Any]: + """Rank conservative recipes using facts available before a proof run. + + Weight residency is the only admission signal available here. Candidates + therefore remain explicitly unproven until the executor records a real + workload measurement for the same model, shape, and runtime fingerprint. + """ + capabilities = _json_object(capabilities, "Hardware capabilities") + inventory = _json_object(inventory, "Component inventory") + requested = str(preference or "balanced") + if requested not in {"quality", "balanced", "low_memory"}: + raise ValueError(f"Unsupported recipe preference {requested!r}.") + + memory = capabilities.get("memory") if isinstance(capabilities.get("memory"), dict) else {} + free_accelerator = memory.get("accelerator_free_bytes") + free_accelerator = int(free_accelerator) if isinstance(free_accelerator, int) and free_accelerator > 0 else None + backend = str(capabilities.get("backend") or "cpu") + device = str(capabilities.get("device") or "cpu:0") + dtypes = capabilities.get("dtypes") if isinstance(capabilities.get("dtypes"), dict) else {} + dtype = "bfloat16" if dtypes.get("bfloat16") else "float16" if dtypes.get("float16") else "float32" + inventory_components = { + str(item.get("component")) + for item in inventory.get("components") or [] + if isinstance(item, dict) and item.get("component") + } + quant_components = [ + name + for name in ("transformer", "transformer_2", "text_encoder", "text_encoder_2") + if name in inventory_components + ] + weight_floor = max(0, int(inventory.get("total_weight_bytes") or 0)) + + # Runtime recipes never manufacture quantized weights. A selected artifact + # may already be quantized, but the execution recipe only manages its + # residency and attention policy. + balanced_quant = "none" + low_quant = "none" + + def idealized_weight(quant_backend: str) -> int: + bits = QUANTIZATION_NOMINAL_BITS.get(quant_backend) + return _ceil_div(weight_floor * bits, 16) if bits else weight_floor + + def fits(weight_bytes: int, fraction: float) -> bool | None: + return ( + None if free_accelerator is None or weight_bytes <= 0 else weight_bytes <= int(free_accelerator * fraction) + ) + + quality_fits = fits(weight_floor, 0.75) + balanced_fits = fits(idealized_weight(balanced_quant), 0.65) + specs = { + "quality": { + "quantization_backend": "none", + "offload_mode": "none" if quality_fits is True else "model_cpu", + "tradeoff": "Preserves model precision; may require CPU movement when the weight floor lacks accelerator headroom.", + }, + "balanced": { + "quantization_backend": balanced_quant, + "offload_mode": "none" if balanced_fits is True else "group_cpu", + "tradeoff": "Balances runtime movement without changing model weights or enabling quality-changing denoiser caches.", + }, + "low_memory": { + "quantization_backend": low_quant, + "offload_mode": "sequential_cpu" if backend != "cpu" else "none", + "tradeoff": "Minimizes accelerator residency and accepts substantially longer generation time; use a pre-built artifact when further compression is required.", + }, + } + order = [requested, *[name for name in ("quality", "balanced", "low_memory") if name != requested]] + candidates = [] + for rank, name in enumerate(order, start=1): + spec = specs[name] + quant_backend = spec["quantization_backend"] + resident_floor = idealized_weight(quant_backend) + if spec["offload_mode"] != "none": + component_floors = [ + max(0, int(item.get("weight_bytes") or 0)) + for item in inventory.get("components") or [] + if isinstance(item, dict) + ] + resident_floor = max(component_floors, default=resident_floor) + candidate_fits = fits(resident_floor, 0.65) + candidates.append( + { + "rank": rank, + "profile": name, + "preference_match": name == requested, + "device": device, + "dtype": dtype, + "quantization_backend": quant_backend, + "quantized_components": quant_components if quant_backend != "none" else [], + "offload_mode": spec["offload_mode"], + "attention_backend": "auto", + "vae_slicing": True, + "vae_tiling": True, + "regional_compile": False, + "denoiser_cache": "none", + "weight_residency_floor_bytes": resident_floor, + "weight_floor_fits_known_free_memory": candidate_fits, + "proof_status": "required", + "tradeoff": spec["tradeoff"], + } + ) + warnings = [ + "These recipes are ranked from hardware and model metadata, not a successful generation.", + "Activation, attention, allocator, compile, and backend workspace memory must be established by a measured proof run.", + ] + warnings.append( + "Execution recipes keep original precision; use a qualified pre-built artifact when compression is required." + ) + return { + "schema_version": 1, + "requested_preference": requested, + "backend": backend, + "device": device, + "free_accelerator_bytes": free_accelerator, + "unquantized_weight_floor_bytes": weight_floor, + "candidates": candidates, + "warnings": warnings, + } + + +class PipelineQuantizationConfigV2(NodeBase): + """Create a component-selective Diffusers quantization configuration.""" + + label = "Pipeline Quantization Config V2" + category = "Diffusers Runtime" + resizable = True + params = { + "backend": { + "label": "Backend", + "type": "string", + "options": [ + "none", + "bnb_4bit", + "bnb_8bit", + "quanto_float8", + "quanto_int8", + "torchao_float8", + "torchao_int8_weight_only", + ], + "default": "none", + }, + "components": { + "label": "Components", + "type": "string", + "display": "select", + "options": QUANT_COMPONENTS, + "fieldOptions": {"multiple": True}, + "default": ["transformer"], + }, + "dtype": { + "label": "Compute DType", + "type": "string", + "options": ["float32", "float16", "bfloat16"], + "default": "bfloat16", + }, + "excluded_modules": { + "label": "Preserve Modules", + "display": "textarea", + "type": "text", + "default": "", + "description": "Comma-separated module paths or patterns to keep in their original precision.", + }, + "component_overrides": { + "label": "Per-component Overrides (JSON)", + "display": "textarea", + "type": "text", + "default": "{}", + }, + "quantization_config": {"label": "Quant Config", "display": "output", "type": "quantization_config"}, + "summary": {"label": "Summary", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + config, summary = build_quantization_config_v2( + backend=kwargs.get("backend") or "none", + components=kwargs.get("components"), + dtype=str_to_dtype(kwargs.get("dtype") or "bfloat16"), + excluded_modules=kwargs.get("excluded_modules"), + component_overrides=kwargs.get("component_overrides"), + ) + return { + # Connected output sockets must carry a concrete value. A disabled + # marker keeps the quantization flow visible and executable while + # build_execution_recipe normalizes it back to no quantization. + "quantization_config": config if config is not None else dict(NO_QUANTIZATION_CONFIG), + "summary": json.dumps(summary, sort_keys=True), + } + + +class DiffusersComponentInventory(NodeBase): + """Inspect component weight floors from local or Hub Safetensors headers.""" + + label = "Diffusers Component Inventory" + category = "Diffusers Runtime" + resizable = True + params = { + "model_id": { + "label": "Model", + "display": "modelselect", + "type": "string", + "value": {"source": "hub", "value": ""}, + "fieldOptions": {"noValidation": True, "sources": ["hub", "local"]}, + }, + "revision": {"label": "Revision", "type": "string", "default": ""}, + "inventory": {"label": "Inventory", "display": "output", "type": "string"}, + "source_bytes": {"label": "Source Bytes", "display": "output", "type": "int"}, + "weight_bytes": {"label": "Weight Floor Bytes", "display": "output", "type": "int"}, + "parameter_count": {"label": "Parameters", "display": "output", "type": "int"}, + "largest_shard_bytes": {"label": "Largest Shard Bytes", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + model_id = _model_value(kwargs.get("model_id")) + if not model_id: + raise ValueError("Diffusers Component Inventory needs a model.") + revision = str(kwargs.get("revision") or "").strip() or None + self.progress(-1, phase="inventory", message="Reading model component metadata") + inventory = inspect_diffusers_components(model_id, revision) + largest = inventory.get("largest_shard") or {} + return { + "inventory": json.dumps(inventory, sort_keys=True), + "source_bytes": int(inventory.get("total_source_bytes") or 0), + "weight_bytes": int(inventory.get("total_weight_bytes") or 0), + "parameter_count": int(inventory.get("total_parameter_count") or 0), + "largest_shard_bytes": int(largest.get("bytes") or 0), + } + + +class LoadPrequantizedDiffusersComponent(NodeBase): + """Load a Diffusers-native or GGUF denoiser component without quantizing at runtime.""" + + label = "Load Pre-quantized Diffusers Component" + category = "Diffusers Runtime" + resizable = True + params = { + "artifact": { + "label": "Artifact", + "display": "modelselect", + "type": "string", + "value": {"source": "hub", "value": ""}, + "fieldOptions": {"noValidation": True, "sources": ["hub", "local"]}, + }, + "filename": {"label": "GGUF Filename", "type": "string", "default": ""}, + "revision": {"label": "Revision", "type": "string", "default": ""}, + "component_class": { + "label": "Component Architecture", + "type": "string", + "options": [ + "FluxTransformer2DModel", + "QwenImageTransformer2DModel", + "WanTransformer3DModel", + "LTXVideoTransformer3DModel", + ], + "default": "FluxTransformer2DModel", + }, + "config_model": { + "label": "Base Config", + "type": "string", + "default": "", + "description": "Optional base repository used to validate the component architecture.", + }, + "config_revision": { + "label": "Base Config Revision", + "type": "string", + "default": "", + "description": "Optional immutable revision for the base configuration repository.", + }, + "subfolder": {"label": "Config Subfolder", "type": "string", "default": "transformer"}, + "compute_dtype": { + "label": "Compute DType", + "type": "string", + "options": ["float16", "bfloat16", "float32"], + "default": "bfloat16", + }, + "component": {"label": "Component", "display": "output", "type": "any"}, + "resolved_artifact": {"label": "Resolved Artifact", "display": "output", "type": "string"}, + "quantization": {"label": "Quantization", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + import diffusers + + artifact_selection = kwargs.get("artifact") + artifact = _model_value(artifact_selection) + filename = str(kwargs.get("filename") or "").strip() + artifact_source = artifact_selection.get("source") if isinstance(artifact_selection, dict) else None + revision = resolve_model_revision( + artifact, + kwargs.get("revision"), + source=artifact_source, + ) + class_name = str(kwargs.get("component_class") or "FluxTransformer2DModel") + allowed = set(self.default_params["component_class"]["options"]) + if class_name not in allowed: + raise ValueError(f"Unsupported component architecture {class_name!r}.") + component_class = getattr(diffusers, class_name, None) + if component_class is None or not callable(getattr(component_class, "from_single_file", None)): + raise RuntimeError(f"Installed Diffusers does not expose GGUF loading for {class_name}.") + if not artifact: + raise ValueError("Choose a pre-quantized artifact.") + + source = Path(artifact).expanduser() + if source.is_file(): + resolved = source + else: + if not filename.lower().endswith(".gguf"): + raise ValueError("Hub GGUF artifacts require an explicit .gguf filename.") + from huggingface_hub import hf_hub_download + from modiff.config import CONFIG + + resolved = Path( + hf_hub_download( + repo_id=artifact, + filename=filename, + revision=revision, + token=CONFIG.hf.get("token"), + cache_dir=CONFIG.hf.get("cache_dir"), + ) + ) + if resolved.suffix.lower() != ".gguf": + raise ValueError(f"Expected a GGUF artifact, got {resolved.name!r}.") + + from diffusers import GGUFQuantizationConfig + + dtype = str_to_dtype(kwargs.get("compute_dtype") or "bfloat16") + load_kwargs: dict[str, Any] = { + "quantization_config": GGUFQuantizationConfig(compute_dtype=dtype), + "torch_dtype": dtype, + } + config_model = str(kwargs.get("config_model") or "").strip() + subfolder = str(kwargs.get("subfolder") or "transformer").strip() + if config_model: + load_kwargs["config"] = config_model + config_revision = resolve_model_revision(config_model, kwargs.get("config_revision")) + if config_revision: + load_kwargs["config_revision"] = config_revision + if subfolder: + load_kwargs["subfolder"] = subfolder + self.progress(-1, phase="loading", message=f"Loading {class_name} GGUF artifact") + try: + component = component_class.from_single_file(str(resolved), **load_kwargs) + except Exception as exc: + raise RuntimeError( + f"{resolved.name} does not match {class_name} or its selected base config: {exc}" + ) from exc + return { + "component": component, + "resolved_artifact": ( + f"{artifact}@{revision or ('local' if source.is_file() else 'unversioned')}:" + f"{filename or resolved.name}" + ), + "quantization": "GGUF", + } + + +class DiffusersExecutionRecipe(NodeBase): + """Combine load-time and runtime choices into one reusable pipeline recipe.""" + + label = "Diffusers Execution Recipe" + category = "Diffusers Runtime" + resizable = True + params = { + "quantization_config": {"label": "Quant Config", "display": "input", "type": "quantization_config"}, + "device_map": { + "label": "Device Placement", + "type": "string", + "options": ["none", "cuda", "auto", "balanced", "balanced_low_0", "cpu", "manual"], + "default": "none", + }, + "max_memory": { + "label": "Max Memory (JSON)", + "display": "textarea", + "type": "text", + "default": "{}", + "description": 'Optional device limits, for example {"0": "16GiB", "cpu": "48GiB"}.', + }, + "device_map_overrides": { + "label": "Manual Device Map (JSON)", + "display": "textarea", + "type": "text", + "default": "{}", + "description": 'Used only with manual placement, for example {"transformer": 0, "text_encoder_2": "cpu"}.', + }, + "offload_mode": { + "label": "Runtime Offload", + "type": "string", + "options": ["none", "model_cpu", "sequential_cpu", "group_cpu", "group_disk"], + "default": "none", + }, + "device": {"label": "Execution Device", "type": "string", "default": "cuda:0"}, + "attention_backend": { + "label": "Attention Backend", + "type": "string", + "options": ATTENTION_BACKENDS, + "default": "auto", + }, + "attention_components": {"label": "Attention Components", "type": "string", "default": ""}, + "vae_slicing": {"label": "VAE Slicing", "type": "bool", "default": True}, + "vae_tiling": {"label": "VAE Tiling", "type": "bool", "default": True}, + "layerwise_casting": {"label": "Layerwise Casting", "type": "bool", "default": False}, + "layerwise_casting_components": { + "label": "Layerwise Casting Components", + "type": "string", + "default": "transformer", + }, + "layerwise_storage_dtype": { + "label": "Layerwise Storage DType", + "type": "string", + "options": ["float8_e4m3fn", "float8_e5m2"], + "default": "float8_e4m3fn", + }, + "layerwise_compute_dtype": { + "label": "Layerwise Compute DType", + "type": "string", + "options": ["bfloat16", "float16", "float32"], + "default": "bfloat16", + }, + "channels_last": {"label": "Channels Last", "type": "bool", "default": False}, + "channels_last_components": { + "label": "Channels Last Components", + "type": "string", + "default": "unet,vae", + }, + "regional_compile": {"label": "Regional Compile", "type": "bool", "default": False}, + "compile_components": {"label": "Compile Components", "type": "string", "default": "transformer"}, + "compile_backend": {"label": "Compile Backend", "type": "string", "default": "inductor"}, + "compile_mode": { + "label": "Compile Mode", + "type": "string", + "options": ["default", "reduce-overhead", "max-autotune"], + "default": "default", + }, + "compile_fullgraph": {"label": "Compile Full Graph", "type": "bool", "default": False}, + "compile_dynamic": {"label": "Compile Dynamic Shapes", "type": "bool", "default": False}, + "denoiser_cache": { + "label": "Denoiser Cache", + "type": "string", + "options": ["none", "first_block", "magcache", "taylorseer", "pab", "fastercache", "text_kv"], + "default": "none", + }, + "cache_threshold": {"label": "Cache Threshold", "type": "float", "default": 0.05, "min": 0.0}, + "cache_options": { + "label": "Cache Options (JSON)", + "display": "textarea", + "type": "text", + "default": "{}", + }, + "execution_recipe": {"label": "Execution Recipe", "display": "output", "type": "diffusers_execution_recipe"}, + "summary": {"label": "Summary", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + recipe = build_execution_recipe( + quantization_config=kwargs.get("quantization_config"), + device_map=kwargs.get("device_map") or "none", + device_map_overrides=kwargs.get("device_map_overrides"), + max_memory=kwargs.get("max_memory"), + offload_mode=kwargs.get("offload_mode") or "none", + device=kwargs.get("device") or "cuda:0", + attention_backend=kwargs.get("attention_backend") or "auto", + attention_components=kwargs.get("attention_components"), + vae_slicing=bool(kwargs.get("vae_slicing", True)), + vae_tiling=bool(kwargs.get("vae_tiling", True)), + layerwise_casting=bool(kwargs.get("layerwise_casting", False)), + layerwise_casting_components=kwargs.get("layerwise_casting_components"), + layerwise_storage_dtype=kwargs.get("layerwise_storage_dtype") or "float8_e4m3fn", + layerwise_compute_dtype=kwargs.get("layerwise_compute_dtype") or "bfloat16", + channels_last=bool(kwargs.get("channels_last", False)), + channels_last_components=kwargs.get("channels_last_components"), + regional_compile=bool(kwargs.get("regional_compile", False)), + compile_components=kwargs.get("compile_components"), + compile_backend=kwargs.get("compile_backend") or "inductor", + compile_mode=kwargs.get("compile_mode") or "default", + compile_fullgraph=bool(kwargs.get("compile_fullgraph", False)), + compile_dynamic=bool(kwargs.get("compile_dynamic", False)), + denoiser_cache=kwargs.get("denoiser_cache") or "none", + cache_threshold=float(kwargs.get("cache_threshold", 0.05)), + cache_options=kwargs.get("cache_options"), + ) + return { + "execution_recipe": recipe, + "summary": json.dumps(execution_recipe_summary(recipe), sort_keys=True), + } + + +class ApplyPipelineRuntimeConfig(NodeBase): + """Apply attention and VAE memory settings to any compatible pipeline.""" + + label = "Apply Pipeline Runtime Config" + category = "Diffusers Runtime" + resizable = True + params = { + "pipeline": { + "label": "Pipeline", + "display": "input", + "type": [ + "image_diffusion_pipeline", + "video_diffusion_pipeline", + "audio_diffusion_pipeline", + "modular_pipeline", + "any", + ], + }, + "attention_backend": { + "label": "Attention Backend", + "type": "string", + "options": ATTENTION_BACKENDS, + "default": "auto", + }, + "attention_components": { + "label": "Attention Components", + "type": "string", + "default": "", + "description": "Optional comma-separated component names. Empty applies to compatible denoisers.", + }, + "vae_slicing": {"label": "VAE Slicing", "type": "bool", "default": True}, + "vae_tiling": {"label": "VAE Tiling", "type": "bool", "default": True}, + "layerwise_casting": {"label": "Layerwise Casting", "type": "bool", "default": False}, + "layerwise_casting_components": { + "label": "Layerwise Casting Components", + "type": "string", + "default": "transformer", + }, + "layerwise_storage_dtype": { + "label": "Layerwise Storage DType", + "type": "string", + "options": ["float8_e4m3fn", "float8_e5m2"], + "default": "float8_e4m3fn", + }, + "layerwise_compute_dtype": { + "label": "Layerwise Compute DType", + "type": "string", + "options": ["bfloat16", "float16", "float32"], + "default": "bfloat16", + }, + "channels_last": {"label": "Channels Last", "type": "bool", "default": False}, + "channels_last_components": { + "label": "Channels Last Components", + "type": "string", + "default": "unet,vae", + }, + "regional_compile": {"label": "Regional Compile", "type": "bool", "default": False}, + "compile_components": {"label": "Compile Components", "type": "string", "default": "transformer"}, + "compile_backend": {"label": "Compile Backend", "type": "string", "default": "inductor"}, + "compile_mode": { + "label": "Compile Mode", + "type": "string", + "options": ["default", "reduce-overhead", "max-autotune"], + "default": "default", + }, + "denoiser_cache": { + "label": "Denoiser Cache", + "type": "string", + "options": ["none", "first_block", "magcache", "taylorseer", "pab", "fastercache", "text_kv"], + "default": "none", + }, + "cache_threshold": {"label": "Cache Threshold", "type": "float", "default": 0.05, "min": 0.0}, + "cache_options": { + "label": "Cache Options (JSON)", + "display": "textarea", + "type": "text", + "default": "{}", + }, + "configured_pipeline": {"label": "Pipeline", "display": "output", "type": "any"}, + "summary": {"label": "Summary", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + pipeline = kwargs.get("pipeline") + if pipeline is None: + raise ValueError("Apply Pipeline Runtime Config needs a pipeline input.") + attention = apply_attention_backend( + pipeline, + kwargs.get("attention_backend") or "auto", + kwargs.get("attention_components"), + ) + vae = configure_vae_memory( + pipeline, + slicing=bool(kwargs.get("vae_slicing", True)), + tiling=bool(kwargs.get("vae_tiling", True)), + ) + layerwise = configure_layerwise_casting( + pipeline, + enabled=bool(kwargs.get("layerwise_casting", False)), + components=kwargs.get("layerwise_casting_components"), + storage_dtype=kwargs.get("layerwise_storage_dtype") or "float8_e4m3fn", + compute_dtype=kwargs.get("layerwise_compute_dtype") or "bfloat16", + ) + channels_last = configure_channels_last( + pipeline, + enabled=bool(kwargs.get("channels_last", False)), + components=kwargs.get("channels_last_components"), + ) + cache = configure_denoiser_cache( + pipeline, + strategy=kwargs.get("denoiser_cache") or "none", + threshold=float(kwargs.get("cache_threshold", 0.05)), + options=kwargs.get("cache_options"), + ) + compile_result = configure_regional_compile( + pipeline, + enabled=bool(kwargs.get("regional_compile", False)), + components=kwargs.get("compile_components"), + backend=kwargs.get("compile_backend") or "inductor", + mode=kwargs.get("compile_mode") or "default", + ) + return { + "configured_pipeline": pipeline, + "summary": json.dumps( + { + "attention": attention, + "vae": vae, + "layerwiseCasting": layerwise, + "channelsLast": channels_last, + "cache": cache, + "compile": compile_result, + }, + sort_keys=True, + ), + } + + +class PipelineMemoryEstimate(NodeBase): + """Expose metadata-derived memory floors for a requested image/video shape.""" + + label = "Pipeline Memory Estimate" + category = "Diffusers Runtime" + resizable = True + params = { + "inventory": {"label": "Component Inventory", "display": "input", "type": "string"}, + "quantization_summary": { + "label": "Quantization Summary", + "display": "input", + "type": "string", + "default": "{}", + }, + "width": {"label": "Width", "type": "int", "default": 1024, "min": 1}, + "height": {"label": "Height", "type": "int", "default": 1024, "min": 1}, + "frames": {"label": "Frames", "type": "int", "default": 1, "min": 1}, + "batch_size": {"label": "Batch", "type": "int", "default": 1, "min": 1}, + "dtype_bytes": {"label": "Latent DType Bytes", "type": "int", "default": 2, "min": 1, "max": 8}, + "latent_channels": {"label": "Latent Channels", "type": "int", "default": 16, "min": 1}, + "spatial_compression": {"label": "Spatial Compression", "type": "int", "default": 8, "min": 1}, + "temporal_compression": {"label": "Temporal Compression", "type": "int", "default": 4, "min": 1}, + "offload_mode": { + "label": "Runtime Offload", + "type": "string", + "options": ["none", "model_cpu", "sequential_cpu", "group_cpu", "group_disk"], + "default": "none", + }, + "estimate": {"label": "Estimate", "display": "output", "type": "string"}, + "weight_floor_bytes": {"label": "Weight Floor", "display": "output", "type": "int"}, + "resident_weight_proxy_bytes": { + "label": "Resident Weight Proxy", + "display": "output", + "type": "int", + }, + "latent_tensor_bytes": {"label": "Latent Tensor Floor", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + estimate = estimate_pipeline_memory( + kwargs.get("inventory"), + width=kwargs.get("width") or 1024, + height=kwargs.get("height") or 1024, + frames=kwargs.get("frames") or 1, + batch_size=kwargs.get("batch_size") or 1, + dtype_bytes=kwargs.get("dtype_bytes") or 2, + latent_channels=kwargs.get("latent_channels") or 16, + spatial_compression=kwargs.get("spatial_compression") or 8, + temporal_compression=kwargs.get("temporal_compression") or 4, + quantization_summary=kwargs.get("quantization_summary"), + offload_mode=kwargs.get("offload_mode") or "none", + ) + weights = estimate["weights"] + return { + "estimate": json.dumps(estimate, sort_keys=True), + "weight_floor_bytes": weights["idealized_weight_floor_bytes"], + "resident_weight_proxy_bytes": weights["accelerator_resident_weight_proxy_bytes"], + "latent_tensor_bytes": estimate["shape"]["latent_tensor_bytes"], + } + + +class ExecutionRecipePlanner(NodeBase): + """Rank quality, balanced, and low-memory recipes for the active machine.""" + + label = "Execution Recipe Planner" + category = "Diffusers Runtime" + resizable = True + params = { + "capabilities": {"label": "Hardware Capabilities", "display": "input", "type": "string"}, + "inventory": {"label": "Component Inventory", "display": "input", "type": "string"}, + "preference": { + "label": "Preference", + "type": "string", + "options": ["quality", "balanced", "low_memory"], + "default": "balanced", + }, + "execution_recipe": { + "label": "Recommended Recipe", + "display": "output", + "type": "diffusers_execution_recipe", + }, + "recommendations": {"label": "Recommendations", "display": "output", "type": "string"}, + "selected_profile": {"label": "Selected Profile", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + plan = plan_execution_recipes( + kwargs.get("capabilities"), + kwargs.get("inventory"), + preference=kwargs.get("preference") or "balanced", + ) + selected = plan["candidates"][0] + quantization_config = None + quant_backend = selected["quantization_backend"] + if quant_backend != "none": + quantization_config, _summary = build_quantization_config_v2( + backend=quant_backend, + components=selected["quantized_components"], + dtype=str_to_dtype(selected["dtype"]), + ) + recipe = build_execution_recipe( + quantization_config=quantization_config, + offload_mode=selected["offload_mode"], + device=selected["device"], + attention_backend=selected["attention_backend"], + vae_slicing=selected["vae_slicing"], + vae_tiling=selected["vae_tiling"], + regional_compile=False, + denoiser_cache="none", + ) + recipe["planner_profile"] = selected["profile"] + recipe["proof_status"] = "required" + return { + "execution_recipe": recipe, + "recommendations": json.dumps(plan, sort_keys=True), + "selected_profile": selected["profile"], + } + + +class ReleasePipelineMemory(NodeBase): + """Move a pipeline out of accelerator memory and clear optional runtime state.""" + + label = "Release Pipeline Memory" + category = "Diffusers Runtime" + params = { + "pipeline": { + "label": "Pipeline", + "display": "input", + "type": [ + "image_diffusion_pipeline", + "video_diffusion_pipeline", + "audio_diffusion_pipeline", + "modular_pipeline", + "any", + ], + }, + "move_to_cpu": {"label": "Move Pipeline to CPU", "type": "bool", "default": True}, + "disable_cache": {"label": "Disable Denoiser Cache", "type": "bool", "default": True}, + "reset_compile_cache": {"label": "Reset Compile Cache", "type": "bool", "default": False}, + "report": {"label": "Release Report", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + pipeline = kwargs.get("pipeline") + if pipeline is None: + raise ValueError("Release Pipeline Memory needs a pipeline input.") + report = release_pipeline_memory( + pipeline, + move_to_cpu=bool(kwargs.get("move_to_cpu", True)), + disable_cache=bool(kwargs.get("disable_cache", True)), + reset_compile_cache=bool(kwargs.get("reset_compile_cache", False)), + ) + return {"report": json.dumps(report, sort_keys=True)} + + +class HardwareCapabilityProbe(NodeBase): + """Report runtime choices supported by the active hardware and packages.""" + + label = "Hardware Capability Probe" + category = "Diffusers Runtime" + resizable = True + params = { + "capabilities": {"label": "Capabilities", "display": "output", "type": "string"}, + "backend": {"label": "Backend", "display": "output", "type": "string"}, + "device": {"label": "Device", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + import torch + from modiff.config import CONFIG + from modiff.hardware import get_hardware_snapshot + + hardware = get_hardware_snapshot(CONFIG.paths.get("data"), refresh=True) + capabilities = build_runtime_capabilities(hardware, torch_module=torch) + return { + "capabilities": json.dumps(capabilities, sort_keys=True), + "backend": capabilities["backend"], + "device": capabilities["device"], + } diff --git a/modules/DiffusersVideo/__init__.py b/modules/DiffusersVideo/__init__.py new file mode 100644 index 0000000..221beda --- /dev/null +++ b/modules/DiffusersVideo/__init__.py @@ -0,0 +1,33 @@ +from .main import * # noqa: F403 + + +def _registry_entry(node_class, *, hidden=False): + return { + "type": "custom", + "label": node_class.label, + "category": node_class.category, + "description": node_class.__doc__ or "", + "resizable": getattr(node_class, "resizable", False), + "skipParamsCheck": getattr(node_class, "skipParamsCheck", False), + "style": getattr(node_class, "style", {}), + "params": node_class.params, + "hidden": hidden, + } + + +MODULE_MAP = { + node_class.__name__: _registry_entry(node_class) + for node_class in ( + LoadPipeline, # noqa: F405 + Generate, # noqa: F405 + GenerateVideoAudio, # noqa: F405 + BuildShotJobs, # noqa: F405 + GenerateShotJob, # noqa: F405 + GenerateSequence, # noqa: F405 + PlanLongVideo, # noqa: F405 + ) +} + +# Keep the old action executable for imported graphs, but do not advertise a +# model-specific node in the node library. +MODULE_MAP["GenerateLTX2"] = _registry_entry(GenerateLTX2, hidden=True) # noqa: F405 diff --git a/modules/DiffusersVideo/main.py b/modules/DiffusersVideo/main.py new file mode 100644 index 0000000..0ca6453 --- /dev/null +++ b/modules/DiffusersVideo/main.py @@ -0,0 +1,1843 @@ +"""Model-neutral Diffusers video facade nodes. + +Node identity describes the media contract; registered adapters own pipeline +loading, input validation, and family-specific argument translation. Legacy +Wan node keys remain registered separately for persisted workflows. +""" + +from dataclasses import dataclass +from functools import wraps +import json +import logging +from typing import Any + +from modiff.config import CONFIG +from modiff.NodeBase import NodeBase +from modiff.diffusers_offload import OFFLOAD_MODE_MODEL_CPU, apply_pipeline_offload +from modiff.model_artifact_catalog import require_catalog_revision, resolve_model_revision +from modules.DiffusersVideo.wan_vace import ( + WanVACEGenerate, + WanVACELoadPipeline, + callback_tensor_inputs, + ensure_reference_images, + ensure_single_prompt, + ensure_video_list, + none_if_blank, + normalize_num_frames, + parse_json_object, + repo_value, +) +from utils.huggingface import local_files_only +from utils.torch_utils import DEFAULT_DEVICE, str_to_dtype + +logger = logging.getLogger("modiff") + +# LTX's distilled 13B release is trained for this non-uniform eight-evaluation +# trajectory. Diffusers otherwise derives a generic linear-quadratic schedule, +# which is appropriate for the dev checkpoint but not the distilled artifact. +LTX_DISTILLED_TIMESTEPS = [1000, 900, 700, 500, 300, 200, 100, 40] +FRAMEPACK_BASE_REPO = "hunyuanvideo-community/HunyuanVideo" +FRAMEPACK_VISION_REPO = "lllyasviel/flux_redux_bfl" + + +def _value_or_default(mapping: dict[str, Any], key: str, default: Any): + value = mapping.get(key) + return default if value is None else value + + +@dataclass(frozen=True) +class VideoPipelineAdapter: + id: str + pipeline_class: str + diffusers_class: str + default_repo: str + modes: tuple[str, ...] + output_media: tuple[str, ...] = ("video",) + supports_mask: bool = False + max_prompt_tokens: int | None = None + default_audio_sample_rate: int | None = None + + +VIDEO_PIPELINE_ADAPTERS = { + "WanVACEPipeline": VideoPipelineAdapter( + id="wan-vace", + pipeline_class="WanVACEPipeline", + diffusers_class="WanVACEPipeline", + default_repo="Wan-AI/Wan2.1-VACE-1.3B-diffusers", + modes=( + "text_to_video", + "image_to_video", + "video_to_video", + "video_inpaint", + "video_outpaint", + "reference_to_video", + "control_to_video", + "video_color_edit", + ), + supports_mask=True, + ), + "WanVideoToVideoPipeline": VideoPipelineAdapter( + id="wan-video-to-video", + pipeline_class="WanVideoToVideoPipeline", + diffusers_class="WanVideoToVideoPipeline", + default_repo="Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + modes=("video_to_video", "video_color_edit"), + ), + "WanPipeline": VideoPipelineAdapter( + id="wan-text-to-video", + pipeline_class="WanPipeline", + diffusers_class="WanPipeline", + default_repo="Wan-AI/Wan2.1-T2V-1.3B-Diffusers", + modes=("text_to_video",), + ), + "Wan22Pipeline": VideoPipelineAdapter( + id="wan-2.2-text-to-video", + pipeline_class="Wan22Pipeline", + diffusers_class="WanPipeline", + default_repo="Wan-AI/Wan2.2-T2V-A14B-Diffusers", + modes=("text_to_video",), + ), + "WanTI2VPipeline": VideoPipelineAdapter( + id="wan-2.2-ti2v-5b", + pipeline_class="WanTI2VPipeline", + diffusers_class="WanPipeline", + default_repo="Wan-AI/Wan2.2-TI2V-5B-Diffusers", + modes=("text_to_video",), + max_prompt_tokens=512, + ), + "WanImageToVideoPipeline": VideoPipelineAdapter( + id="wan-image-to-video", + pipeline_class="WanImageToVideoPipeline", + diffusers_class="WanImageToVideoPipeline", + default_repo="Wan-AI/Wan2.2-I2V-A14B-Diffusers", + modes=("image_to_video",), + max_prompt_tokens=512, + ), + "WanAnimatePipeline": VideoPipelineAdapter( + id="wan-animate", + pipeline_class="WanAnimatePipeline", + diffusers_class="WanAnimatePipeline", + default_repo="Wan-AI/Wan2.2-Animate-14B-Diffusers", + modes=("character_animate", "character_replace"), + ), + "LTXConditionPipeline": VideoPipelineAdapter( + id="ltx-video", + pipeline_class="LTXConditionPipeline", + diffusers_class="LTXConditionPipeline", + default_repo="Lightricks/LTX-Video-0.9.8-13B-distilled", + modes=("text_to_video", "image_to_video", "video_to_video", "reference_to_video"), + max_prompt_tokens=128, + ), + "LTXI2VLongMultiPromptPipeline": VideoPipelineAdapter( + id="ltx-video-long-i2v", + pipeline_class="LTXI2VLongMultiPromptPipeline", + diffusers_class="LTXI2VLongMultiPromptPipeline", + default_repo="Lightricks/LTX-Video-0.9.8-13B-distilled", + modes=("image_to_video",), + max_prompt_tokens=128, + ), + "LTX2ConditionPipeline": VideoPipelineAdapter( + id="ltx-2", + pipeline_class="LTX2ConditionPipeline", + diffusers_class="LTX2ConditionPipeline", + default_repo="Lightricks/LTX-2", + modes=("text_to_video", "image_to_video", "video_to_video", "reference_to_video"), + output_media=("video", "audio"), + max_prompt_tokens=1024, + default_audio_sample_rate=24000, + ), + "HunyuanVideoFramepackPipeline": VideoPipelineAdapter( + id="framepack", + pipeline_class="HunyuanVideoFramepackPipeline", + diffusers_class="HunyuanVideoFramepackPipeline", + default_repo="lllyasviel/FramePackI2V_HY", + modes=("image_to_video",), + max_prompt_tokens=256, + ), +} + + +def get_video_pipeline_adapter(name: Any) -> VideoPipelineAdapter: + key = str(name or "WanVACEPipeline") + adapter = VIDEO_PIPELINE_ADAPTERS.get(key) + if adapter is None: + supported = ", ".join(sorted(VIDEO_PIPELINE_ADAPTERS)) + raise ValueError(f"Unsupported Diffusers video pipeline class {key}. Supported classes: {supported}.") + return adapter + + +def _resolve_adapter_model_selection(adapter: VideoPipelineAdapter, value: Any): + """Replace only the inherited Wan VACE default for non-VACE adapters. + + ``LoadPipeline`` intentionally inherits the mature Wan loader contract, so + its model selector also inherits Wan's persisted default value. A graph + that changes only ``pipeline_class`` must resolve to that adapter's model; + an explicitly selected local or Hub artifact must remain untouched. + """ + + selected = repo_value(value) + inherited_vace_default = VIDEO_PIPELINE_ADAPTERS["WanVACEPipeline"].default_repo + if not selected or (adapter.pipeline_class != "WanVACEPipeline" and selected == inherited_vace_default): + return {"source": "hub", "value": adapter.default_repo} + return value + + +def _resolve_loader_revision(model_selection: Any, model_id: str, revision: Any) -> str | None: + source = model_selection.get("source") if isinstance(model_selection, dict) else None + return resolve_model_revision(model_id, none_if_blank(revision), source=source) + + +def _pipeline_adapter(pipeline: Any) -> VideoPipelineAdapter: + adapter_name = getattr(pipeline, "_modiff_video_pipeline_class", None) + if adapter_name: + return get_video_pipeline_adapter(adapter_name) + # Compatibility for pipelines loaded before adapter tagging existed. + return get_video_pipeline_adapter("WanVACEPipeline") + + +def _normalize_ltx_frames(value: int) -> int: + if value < 1: + return 1 + remainder = (value - 1) % 8 + return value if remainder == 0 else value + (8 - remainder) + + +def _validate_ltx_dimensions(width: int, height: int): + if width % 32 or height % 32: + raise ValueError(f"LTX Video width and height must be divisible by 32; received {width}x{height}.") + + +def _validate_prompt_token_limit(pipeline: Any, prompt: str | None, label: str, limit: int | None): + if not prompt or not limit: + return + tokenizer = getattr(pipeline, "tokenizer", None) + if not callable(tokenizer): + return + encoded = tokenizer(prompt, add_special_tokens=True, truncation=False) + token_ids = encoded.get("input_ids") if hasattr(encoded, "get") else None + if token_ids and isinstance(token_ids[0], list): + token_ids = token_ids[0] + token_count = len(token_ids or []) + if token_count > limit: + raise ValueError( + f"LTX {label} uses {token_count} tokens, but this artifact supports at most {limit}. " + "Shorten the text so motion and preservation constraints are not truncated." + ) + + +def _install_ltx_dynamic_shift(pipeline: Any, width: int, height: int, num_frames: int): + """Inject the official resolution-dependent scheduler shift for LTX Condition. + + Diffusers' LTXPipeline calculates and passes ``mu`` itself. Some released + LTXConditionPipeline versions use the same dynamic FlowMatch scheduler but + omit that argument. Wrap the scheduler call for this invocation so both + pipeline variants use the model's official sequence-length shift without + disabling dynamic scheduling. + """ + + scheduler = getattr(pipeline, "scheduler", None) + config = getattr(scheduler, "config", None) + if scheduler is None or not config or not bool(config.get("use_dynamic_shifting", False)): + return None + original = getattr(scheduler, "set_timesteps", None) + if not callable(original): + return None + + temporal_ratio = int(getattr(pipeline, "vae_temporal_compression_ratio", 8)) + spatial_ratio = int(getattr(pipeline, "vae_spatial_compression_ratio", 32)) + latent_frames = (num_frames - 1) // temporal_ratio + 1 + sequence_length = latent_frames * (height // spatial_ratio) * (width // spatial_ratio) + base_sequence_length = int(config.get("base_image_seq_len", 256)) + max_sequence_length = int(config.get("max_image_seq_len", 4096)) + base_shift = float(config.get("base_shift", 0.5)) + max_shift = float(config.get("max_shift", 1.15)) + slope = (max_shift - base_shift) / (max_sequence_length - base_sequence_length) + mu = sequence_length * slope + (base_shift - slope * base_sequence_length) + + @wraps(original) + def set_timesteps_with_mu(*args, **kwargs): + kwargs.setdefault("mu", mu) + return original(*args, **kwargs) + + scheduler.set_timesteps = set_timesteps_with_mu + return scheduler, original + + +class LoadPipeline(WanVACELoadPipeline): + """Load a registered Diffusers video pipeline through a stable facade.""" + + label = "Load Diffusers Video Pipeline" + category = "Diffusers Video" + params = { + **WanVACELoadPipeline.params, + "pipeline": {"label": "Pipeline", "display": "output", "type": "video_diffusion_pipeline"}, + "pipeline_class": { + "label": "Pipeline Class", + "type": "string", + "options": list(VIDEO_PIPELINE_ADAPTERS), + "default": "WanVACEPipeline", + }, + "resolved_artifact": {"label": "Resolved Artifact", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + adapter = get_video_pipeline_adapter(kwargs.get("pipeline_class")) + values = dict(kwargs) + values["model_id"] = _resolve_adapter_model_selection(adapter, values.get("model_id")) + if adapter.pipeline_class == "WanVACEPipeline": + result = super().execute(**values) + pipeline = result["pipeline"] + elif adapter.pipeline_class == "WanVideoToVideoPipeline": + pipeline = self._load_wan_video_to_video(adapter, values) + result = {"pipeline": pipeline} + elif adapter.pipeline_class in {"WanPipeline", "Wan22Pipeline", "WanTI2VPipeline"}: + pipeline = self._load_wan_text_to_video(adapter, values) + result = {"pipeline": pipeline} + elif adapter.pipeline_class == "WanImageToVideoPipeline": + pipeline = self._load_wan_image_to_video(adapter, values) + result = {"pipeline": pipeline} + elif adapter.pipeline_class == "LTXConditionPipeline": + pipeline = self._load_ltx(adapter, values) + result = {"pipeline": pipeline} + elif adapter.pipeline_class == "LTXI2VLongMultiPromptPipeline": + pipeline = self._load_ltx_long(adapter, values) + result = {"pipeline": pipeline} + elif adapter.pipeline_class == "LTX2ConditionPipeline": + pipeline = self._load_ltx2(adapter, values) + result = {"pipeline": pipeline} + elif adapter.pipeline_class == "WanAnimatePipeline": + pipeline = self._load_wan_animate(adapter, values) + result = {"pipeline": pipeline} + else: + pipeline = self._load_framepack(adapter, values) + result = {"pipeline": pipeline} + setattr(pipeline, "_modiff_video_pipeline_class", adapter.pipeline_class) + setattr(pipeline, "_modiff_video_repo", repo_value(values.get("model_id")) or adapter.default_repo) + return {**result, "resolved_artifact": values.get("model_id")} + + def _load_ltx(self, adapter: VideoPipelineAdapter, kwargs: dict[str, Any]): + from diffusers import LTXConditionPipeline + from modules.DiffusersRuntime.main import apply_execution_recipe_to_pipeline, loader_runtime_options + + model_selection = kwargs.get("model_id") + model_id = repo_value(model_selection) or adapter.default_repo + dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) + revision = _resolve_loader_revision(model_selection, model_id, kwargs.get("revision")) + recipe, device, offload_mode, recipe_load_kwargs = loader_runtime_options( + kwargs, + default_device=DEFAULT_DEVICE, + default_offload_mode=OFFLOAD_MODE_MODEL_CPU, + direct_device_load=True, + ) + load_kwargs = { + "torch_dtype": dtype, + "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), + "revision": revision, + "local_files_only": local_files_only(model_id), + **recipe_load_kwargs, + } + if CONFIG.hf.get("cache_dir"): + load_kwargs["cache_dir"] = CONFIG.hf["cache_dir"] + + logger.info("Loading %s pipeline: %s", adapter.diffusers_class, model_id) + self.progress(-1, phase="loading", message=f"Loading {adapter.diffusers_class}") + pipeline = LTXConditionPipeline.from_pretrained(model_id, **load_kwargs) + apply_execution_recipe_to_pipeline(pipeline, recipe) + self.progress(-1, phase="component_placement", message=f"Applying {offload_mode} offload") + apply_pipeline_offload(pipeline, mode=offload_mode, device=device, node_id=self.node_id, scope=adapter.id) + self.mm_add(pipeline, priority=2) + return pipeline + + def _load_ltx_long(self, adapter: VideoPipelineAdapter, kwargs: dict[str, Any]): + from diffusers import LTXEulerAncestralRFScheduler, LTXI2VLongMultiPromptPipeline + from modules.DiffusersRuntime.main import apply_execution_recipe_to_pipeline, loader_runtime_options + + model_selection = kwargs.get("model_id") + model_id = repo_value(model_selection) or adapter.default_repo + dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) + recipe, device, offload_mode, recipe_load_kwargs = loader_runtime_options( + kwargs, + default_device=DEFAULT_DEVICE, + default_offload_mode=OFFLOAD_MODE_MODEL_CPU, + direct_device_load=True, + ) + load_kwargs = { + "torch_dtype": dtype, + "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), + "revision": _resolve_loader_revision(model_selection, model_id, kwargs.get("revision")), + "local_files_only": local_files_only(model_id), + **recipe_load_kwargs, + } + if CONFIG.hf.get("cache_dir"): + load_kwargs["cache_dir"] = CONFIG.hf["cache_dir"] + self.progress(-1, phase="loading", message="Loading LTX long image-to-video pipeline") + pipeline = LTXI2VLongMultiPromptPipeline.from_pretrained(model_id, **load_kwargs) + pipeline.scheduler = LTXEulerAncestralRFScheduler.from_config(pipeline.scheduler.config) + apply_execution_recipe_to_pipeline(pipeline, recipe) + apply_pipeline_offload(pipeline, mode=offload_mode, device=device, node_id=self.node_id, scope=adapter.id) + self.mm_add(pipeline, priority=2) + return pipeline + + def _load_ltx2(self, adapter: VideoPipelineAdapter, kwargs: dict[str, Any]): + from diffusers import LTX2ConditionPipeline + from modules.DiffusersRuntime.main import apply_execution_recipe_to_pipeline, loader_runtime_options + + model_selection = kwargs.get("model_id") + model_id = repo_value(model_selection) or adapter.default_repo + dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) + recipe, device, offload_mode, recipe_load_kwargs = loader_runtime_options( + kwargs, + default_device=DEFAULT_DEVICE, + default_offload_mode="sequential_cpu", + direct_device_load=True, + ) + load_kwargs = { + "torch_dtype": dtype, + "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), + "revision": _resolve_loader_revision(model_selection, model_id, kwargs.get("revision")), + "local_files_only": local_files_only(model_id), + **recipe_load_kwargs, + } + if CONFIG.hf.get("cache_dir"): + load_kwargs["cache_dir"] = CONFIG.hf["cache_dir"] + self.progress(-1, phase="loading", message="Loading LTX-2 video and audio pipeline") + pipeline = LTX2ConditionPipeline.from_pretrained(model_id, **load_kwargs) + apply_execution_recipe_to_pipeline(pipeline, recipe) + apply_pipeline_offload(pipeline, mode=offload_mode, device=device, node_id=self.node_id, scope=adapter.id) + self.mm_add(pipeline, priority=2) + return pipeline + + def _load_wan_animate(self, adapter: VideoPipelineAdapter, kwargs: dict[str, Any]): + import torch + from diffusers import AutoencoderKLWan, WanAnimatePipeline + from modules.DiffusersRuntime.main import apply_execution_recipe_to_pipeline, loader_runtime_options + + model_selection = kwargs.get("model_id") + model_id = repo_value(model_selection) or adapter.default_repo + dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) + recipe, device, offload_mode, recipe_load_kwargs = loader_runtime_options( + kwargs, + default_device=DEFAULT_DEVICE, + default_offload_mode="sequential_cpu", + ) + common = { + "revision": _resolve_loader_revision(model_selection, model_id, kwargs.get("revision")), + "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), + "local_files_only": local_files_only(model_id), + } + if CONFIG.hf.get("cache_dir"): + common["cache_dir"] = CONFIG.hf["cache_dir"] + self.progress(-1, phase="loading", message="Loading Wan Animate pipeline") + vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32, **common) + pipeline = WanAnimatePipeline.from_pretrained( + model_id, + vae=vae, + torch_dtype=dtype, + **common, + **recipe_load_kwargs, + ) + apply_execution_recipe_to_pipeline(pipeline, recipe) + apply_pipeline_offload(pipeline, mode=offload_mode, device=device, node_id=self.node_id, scope=adapter.id) + self.mm_add(pipeline, priority=2) + return pipeline + + def _load_framepack(self, adapter: VideoPipelineAdapter, kwargs: dict[str, Any]): + from diffusers import HunyuanVideoFramepackPipeline, HunyuanVideoFramepackTransformer3DModel + from modules.DiffusersRuntime.main import apply_execution_recipe_to_pipeline, loader_runtime_options + from transformers import SiglipImageProcessor, SiglipVisionModel + + model_selection = kwargs.get("model_id") + model_id = repo_value(model_selection) or adapter.default_repo + dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) + revision = _resolve_loader_revision(model_selection, model_id, kwargs.get("revision")) + recipe, device, offload_mode, recipe_load_kwargs = loader_runtime_options( + kwargs, + default_device=DEFAULT_DEVICE, + default_offload_mode="sequential_cpu", + ) + common_kwargs = { + "torch_dtype": dtype, + "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), + } + if CONFIG.hf.get("cache_dir"): + common_kwargs["cache_dir"] = CONFIG.hf["cache_dir"] + + # The FramePack repository contains only the packed transformer. The + # official Diffusers pipeline composes it with HunyuanVideo's base + # components and the FLUX Redux SigLIP processor/encoder. + transformer_kwargs = { + **common_kwargs, + "revision": revision, + "local_files_only": local_files_only(model_id), + } + pipeline_quantization = recipe_load_kwargs.get("quantization_config") + quant_mapping = getattr(pipeline_quantization, "quant_mapping", None) + if isinstance(quant_mapping, dict) and quant_mapping.get("transformer") is not None: + transformer_kwargs["quantization_config"] = quant_mapping["transformer"] + + logger.info("Loading %s transformer: %s", adapter.diffusers_class, model_id) + self.progress(-1, phase="loading", message=f"Loading {adapter.diffusers_class}") + transformer = HunyuanVideoFramepackTransformer3DModel.from_pretrained(model_id, **transformer_kwargs) + feature_extractor = SiglipImageProcessor.from_pretrained( + FRAMEPACK_VISION_REPO, + subfolder="feature_extractor", + revision=require_catalog_revision(FRAMEPACK_VISION_REPO), + local_files_only=local_files_only(FRAMEPACK_VISION_REPO), + **({"cache_dir": common_kwargs["cache_dir"]} if "cache_dir" in common_kwargs else {}), + ) + image_encoder = SiglipVisionModel.from_pretrained( + FRAMEPACK_VISION_REPO, + subfolder="image_encoder", + revision=require_catalog_revision(FRAMEPACK_VISION_REPO), + torch_dtype=dtype, + low_cpu_mem_usage=common_kwargs["low_cpu_mem_usage"], + local_files_only=local_files_only(FRAMEPACK_VISION_REPO), + **({"cache_dir": common_kwargs["cache_dir"]} if "cache_dir" in common_kwargs else {}), + ) + pipeline = HunyuanVideoFramepackPipeline.from_pretrained( + FRAMEPACK_BASE_REPO, + transformer=transformer, + feature_extractor=feature_extractor, + image_encoder=image_encoder, + revision=require_catalog_revision(FRAMEPACK_BASE_REPO), + local_files_only=local_files_only(FRAMEPACK_BASE_REPO), + **common_kwargs, + **recipe_load_kwargs, + ) + apply_execution_recipe_to_pipeline(pipeline, recipe) + self.progress(-1, phase="component_placement", message=f"Applying {offload_mode} offload") + apply_pipeline_offload(pipeline, mode=offload_mode, device=device, node_id=self.node_id, scope=adapter.id) + self.mm_add(pipeline, priority=2) + return pipeline + + def _load_wan_video_to_video(self, adapter: VideoPipelineAdapter, kwargs: dict[str, Any]): + import torch + from diffusers import AutoencoderKLWan, WanVideoToVideoPipeline + from diffusers.schedulers.scheduling_unipc_multistep import UniPCMultistepScheduler + from modules.DiffusersRuntime.main import apply_execution_recipe_to_pipeline, loader_runtime_options + + model_selection = kwargs.get("model_id") + model_id = repo_value(model_selection) or adapter.default_repo + dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) + revision = _resolve_loader_revision(model_selection, model_id, kwargs.get("revision")) + recipe, device, offload_mode, recipe_load_kwargs = loader_runtime_options( + kwargs, + default_device=DEFAULT_DEVICE, + default_offload_mode=OFFLOAD_MODE_MODEL_CPU, + ) + load_kwargs = { + "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), + "revision": revision, + "local_files_only": local_files_only(model_id), + **recipe_load_kwargs, + } + if CONFIG.hf.get("cache_dir"): + load_kwargs["cache_dir"] = CONFIG.hf["cache_dir"] + + logger.info("Loading %s pipeline: %s", adapter.diffusers_class, model_id) + self.progress(-1, phase="loading", message=f"Loading {adapter.diffusers_class}") + vae = AutoencoderKLWan.from_pretrained( + model_id, + subfolder="vae", + torch_dtype=torch.float32, + **{key: value for key, value in load_kwargs.items() if key not in recipe_load_kwargs}, + ) + pipeline = WanVideoToVideoPipeline.from_pretrained( + model_id, + vae=vae, + torch_dtype=dtype, + **load_kwargs, + ) + if adapter.pipeline_class != "WanTI2VPipeline": + pipeline.scheduler = UniPCMultistepScheduler.from_config(pipeline.scheduler.config, flow_shift=3.0) + apply_execution_recipe_to_pipeline(pipeline, recipe) + self.progress(-1, phase="component_placement", message=f"Applying {offload_mode} offload") + apply_pipeline_offload(pipeline, mode=offload_mode, device=device, node_id=self.node_id, scope=adapter.id) + self.mm_add(pipeline, priority=2) + return pipeline + + def _load_wan_image_to_video(self, adapter: VideoPipelineAdapter, kwargs: dict[str, Any]): + import torch + from diffusers import AutoencoderKLWan, WanImageToVideoPipeline + from modules.DiffusersRuntime.main import apply_execution_recipe_to_pipeline, loader_runtime_options + + model_selection = kwargs.get("model_id") + model_id = repo_value(model_selection) or adapter.default_repo + dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) + revision = _resolve_loader_revision(model_selection, model_id, kwargs.get("revision")) + recipe, device, offload_mode, recipe_load_kwargs = loader_runtime_options( + kwargs, + default_device=DEFAULT_DEVICE, + default_offload_mode=OFFLOAD_MODE_MODEL_CPU, + ) + common = { + "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), + "revision": revision, + "local_files_only": local_files_only(model_id), + } + if CONFIG.hf.get("cache_dir"): + common["cache_dir"] = CONFIG.hf["cache_dir"] + + logger.info("Loading %s pipeline: %s", adapter.diffusers_class, model_id) + self.progress(-1, phase="loading", message=f"Loading {adapter.diffusers_class}") + # Wan's VAE is numerically sensitive and the official recipe keeps it + # in FP32. The two denoisers and text encoder retain the requested load + # dtype or per-component quantization from the execution recipe. + vae = AutoencoderKLWan.from_pretrained( + model_id, + subfolder="vae", + torch_dtype=torch.float32, + **common, + ) + pipeline = WanImageToVideoPipeline.from_pretrained( + model_id, + vae=vae, + torch_dtype=dtype, + **common, + **recipe_load_kwargs, + ) + apply_execution_recipe_to_pipeline(pipeline, recipe) + self.progress(-1, phase="component_placement", message=f"Applying {offload_mode} offload") + apply_pipeline_offload(pipeline, mode=offload_mode, device=device, node_id=self.node_id, scope=adapter.id) + self.mm_add(pipeline, priority=2) + return pipeline + + def _load_wan_text_to_video(self, adapter: VideoPipelineAdapter, kwargs: dict[str, Any]): + import torch + from diffusers import AutoencoderKLWan, WanPipeline + from diffusers.schedulers.scheduling_unipc_multistep import UniPCMultistepScheduler + from modules.DiffusersRuntime.main import apply_execution_recipe_to_pipeline, loader_runtime_options + + model_selection = kwargs.get("model_id") + model_id = repo_value(model_selection) or adapter.default_repo + dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) + revision = _resolve_loader_revision(model_selection, model_id, kwargs.get("revision")) + recipe, device, offload_mode, recipe_load_kwargs = loader_runtime_options( + kwargs, + default_device=DEFAULT_DEVICE, + default_offload_mode=OFFLOAD_MODE_MODEL_CPU, + ) + load_kwargs = { + "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), + "revision": revision, + "local_files_only": local_files_only(model_id), + **recipe_load_kwargs, + } + if CONFIG.hf.get("cache_dir"): + load_kwargs["cache_dir"] = CONFIG.hf["cache_dir"] + + logger.info("Loading %s pipeline: %s", adapter.diffusers_class, model_id) + self.progress(-1, phase="loading", message=f"Loading {adapter.diffusers_class}") + vae = AutoencoderKLWan.from_pretrained( + model_id, + subfolder="vae", + torch_dtype=torch.float32, + **{key: value for key, value in load_kwargs.items() if key not in recipe_load_kwargs}, + ) + pipeline = WanPipeline.from_pretrained(model_id, vae=vae, torch_dtype=dtype, **load_kwargs) + # Wan's upstream 1.3B quality recipe recommends shift 8-12 and guidance + # 6. Use the conservative lower end for the base 1.3B text-to-video + # adapter. Wan 2.2 checkpoints carry their own scheduler contracts and + # must not be overwritten with a Wan 2.1 value. + if adapter.pipeline_class == "WanPipeline": + pipeline.scheduler = UniPCMultistepScheduler.from_config(pipeline.scheduler.config, flow_shift=8.0) + apply_execution_recipe_to_pipeline(pipeline, recipe) + self.progress(-1, phase="component_placement", message=f"Applying {offload_mode} offload") + apply_pipeline_offload(pipeline, mode=offload_mode, device=device, node_id=self.node_id, scope=adapter.id) + self.mm_add(pipeline, priority=2) + return pipeline + + +class Generate(WanVACEGenerate): + """Generate or condition video through the selected family adapter.""" + + label = "Diffusers Video Generate" + category = "Diffusers Video" + params = { + **WanVACEGenerate.params, + "seed": { + "label": "Seed", + "type": "int", + "display": "random", + "default": 0, + "min": 0, + "max": 9007199254740991, + }, + "pipeline": {"label": "Pipeline", "display": "input", "type": "video_diffusion_pipeline", "required": True}, + "mode": { + "label": "Mode", + "type": "string", + "options": sorted({mode for adapter in VIDEO_PIPELINE_ADAPTERS.values() for mode in adapter.modes}), + "default": "text_to_video", + }, + "frame_rate": {"label": "Frame rate", "type": "int", "default": 25, "min": 1, "max": 60}, + "strength": {"label": "Condition strength", "type": "float", "default": 1.0, "min": 0, "max": 1, "step": 0.05}, + "denoise_strength": { + "label": "Denoise strength", + "type": "float", + "default": 1.0, + "min": 0, + "max": 1, + "step": 0.05, + }, + "last_image": {"label": "Optional Last Image", "display": "input", "type": "image", "required": False}, + "framepack_sampling": { + "label": "FramePack Sampling", + "type": "string", + "options": ["inverted_anti_drifting", "vanilla"], + "default": "inverted_anti_drifting", + }, + "latent_window_size": {"label": "FramePack Window", "type": "int", "default": 9, "min": 1, "max": 32}, + "true_cfg_scale": {"label": "True CFG", "type": "float", "default": 1.0, "min": 0, "max": 20}, + "secondary_guidance_scale": { + "label": "Low-noise Guidance", + "type": "float", + "default": 3.5, + "min": 0, + "max": 20, + "step": 0.1, + }, + "scheduler_flow_shift": { + "label": "Flow Shift", + "type": "float", + "default": 0, + "min": 0, + "max": 32, + "step": 0.1, + }, + "pose_video": {"label": "Pose Video", "display": "input", "type": ["video", "str"], "required": False}, + "face_video": {"label": "Face Video", "display": "input", "type": ["video", "str"], "required": False}, + "background_video": {"label": "Background Video", "display": "input", "type": ["video", "str"], "required": False}, + "segment_frame_length": {"label": "Segment Frames", "type": "int", "default": 77, "min": 5, "max": 241}, + "previous_conditioning_frames": {"label": "Previous Frames", "type": "int", "default": 1, "min": 1, "max": 16}, + "motion_encode_batch_size": {"label": "Motion Batch", "type": "int", "default": 1, "min": 1, "max": 32}, + "temporal_tile_size": {"label": "Temporal Window", "type": "int", "default": 80, "min": 17, "max": 257}, + "temporal_overlap": {"label": "Temporal Overlap", "type": "int", "default": 24, "min": 1, "max": 128}, + "temporal_overlap_condition_strength": { + "label": "Overlap Preservation", + "type": "float", + "default": 0.5, + "min": 0, + "max": 1, + "step": 0.05, + }, + "adain_factor": { + "label": "Long Color Consistency", + "type": "float", + "default": 0.25, + "min": 0, + "max": 1, + "step": 0.05, + }, + "prompt_segments_json": { + "label": "Timed Prompt Segments", + "display": "textarea", + "type": "text", + "default": "", + }, + } + + def execute(self, **kwargs): + _adapter, result = self._execute_with_adapter(**kwargs) + result.pop("_audio", None) + return result + + def _execute_with_adapter(self, **kwargs): + pipeline = kwargs.get("pipeline") + if pipeline is None: + raise ValueError("A Diffusers video pipeline is required.") + adapter = _pipeline_adapter(pipeline) + mode = str(kwargs.get("mode") or "text_to_video") + if mode not in adapter.modes: + raise ValueError(f"{adapter.pipeline_class} does not support video mode {mode}.") + return adapter, self._execute_adapter(pipeline, adapter, mode, kwargs) + + def _execute_adapter( + self, + pipeline: Any, + adapter: VideoPipelineAdapter, + mode: str, + kwargs: dict[str, Any], + ): + if adapter.pipeline_class == "WanVACEPipeline": + return super().execute(**kwargs) + if adapter.pipeline_class == "WanVideoToVideoPipeline": + return self._execute_wan_video_to_video(pipeline, adapter, mode, kwargs) + if adapter.pipeline_class in {"WanPipeline", "Wan22Pipeline", "WanTI2VPipeline"}: + return self._execute_wan_text_to_video(pipeline, adapter, mode, kwargs) + if adapter.pipeline_class == "WanImageToVideoPipeline": + return self._execute_wan_image_to_video(pipeline, adapter, mode, kwargs) + if adapter.pipeline_class == "LTXConditionPipeline": + return self._execute_ltx(pipeline, adapter, mode, kwargs) + if adapter.pipeline_class == "LTXI2VLongMultiPromptPipeline": + return self._execute_ltx_long(pipeline, adapter, mode, kwargs) + if adapter.pipeline_class == "LTX2ConditionPipeline": + return self._execute_ltx2(pipeline, adapter, mode, kwargs) + if adapter.pipeline_class == "WanAnimatePipeline": + return self._execute_wan_animate(pipeline, adapter, mode, kwargs) + return self._execute_framepack(pipeline, adapter, mode, kwargs) + + def _execute_wan_animate(self, pipeline: Any, adapter: VideoPipelineAdapter, mode: str, kwargs: dict[str, Any]): + import torch + + references = ensure_reference_images(kwargs.get("reference_images")) + if not references or len(references) != 1: + raise ValueError("Wan Animate needs exactly one character reference image.") + pose_video = ensure_video_list(kwargs.get("pose_video"), "pose video") + face_video = ensure_video_list(kwargs.get("face_video"), "face video") + if not pose_video or not face_video: + raise ValueError("Wan Animate needs preprocessed pose and face videos.") + if len(pose_video) != len(face_video): + raise ValueError("Wan Animate pose and face videos must contain the same number of frames.") + call_mode = "replace" if mode == "character_replace" else "animate" + background = ensure_video_list(kwargs.get("background_video"), "background video") + mask = ensure_video_list(kwargs.get("mask"), "mask video") + if call_mode == "replace" and (not background or not mask): + raise ValueError("Wan character replacement needs background and mask videos.") + if call_mode == "animate" and (background is not None or mask is not None): + raise ValueError("Wan character animation does not accept background or mask videos.") + device = getattr(pipeline, "_execution_device", None) or "cpu" + generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed") or 0)) + height = int(kwargs.get("height") or 720) + width = int(kwargs.get("width") or 1280) + self._active_pipeline = pipeline + try: + result = pipeline( + image=references[0], + pose_video=pose_video, + face_video=face_video, + background_video=background, + mask_video=mask, + prompt=ensure_single_prompt(none_if_blank(kwargs.get("prompt")), "prompt"), + negative_prompt=ensure_single_prompt(none_if_blank(kwargs.get("negative_prompt")), "negative prompt"), + height=height, + width=width, + segment_frame_length=int(kwargs.get("segment_frame_length") or 77), + num_inference_steps=int(kwargs.get("num_inference_steps") or 20), + mode=call_mode, + prev_segment_conditioning_frames=int(kwargs.get("previous_conditioning_frames") or 1), + motion_encode_batch_size=int(kwargs.get("motion_encode_batch_size") or 1), + guidance_scale=float(_value_or_default(kwargs, "guidance_scale", 1)), + generator=generator, + output_type=kwargs.get("output_type") or "pil", + return_dict=True, + attention_kwargs=parse_json_object(kwargs.get("attention_kwargs_json"), "attention kwargs"), + callback_on_step_end=self.pipe_callback, + callback_on_step_end_tensor_inputs=callback_tensor_inputs( + kwargs.get("callback_on_step_end_tensor_inputs") + ), + max_sequence_length=min(int(kwargs.get("max_sequence_length") or 512), 512), + ) + finally: + self._active_pipeline = None + frames = getattr(result, "frames", result) + if isinstance(frames, list) and len(frames) == 1 and isinstance(frames[0], list): + frames = frames[0] + return { + "video_out": frames, + "width_out": width, + "height_out": height, + "frames_out": len(frames) if isinstance(frames, list) else len(pose_video), + } + + def _execute_framepack(self, pipeline: Any, adapter: VideoPipelineAdapter, mode: str, kwargs: dict[str, Any]): + import torch + + if mode != "image_to_video": + raise ValueError("FramePack supports image_to_video generation only.") + if ensure_video_list(kwargs.get("video"), "video") is not None: + raise ValueError("FramePack does not accept a source video; provide one opening reference image.") + if ensure_video_list(kwargs.get("mask"), "mask") is not None: + raise ValueError("FramePack does not accept a mask.") + references = ensure_reference_images(kwargs.get("reference_images")) + if not references or len(references) != 1: + raise ValueError("FramePack needs exactly one opening reference image.") + sampling = str(kwargs.get("framepack_sampling") or "inverted_anti_drifting") + last_image = kwargs.get("last_image") + if last_image is not None and sampling != "inverted_anti_drifting": + raise ValueError("FramePack last-image guidance requires inverted_anti_drifting sampling.") + width = int(kwargs.get("width") or 1280) + height = int(kwargs.get("height") or 720) + if width % 16 or height % 16: + raise ValueError(f"FramePack width and height must be divisible by 16; received {width}x{height}.") + num_frames = max(1, int(kwargs.get("num_frames") or 129)) + prompt = ensure_single_prompt(none_if_blank(kwargs.get("prompt")), "prompt") + negative_prompt = ensure_single_prompt(none_if_blank(kwargs.get("negative_prompt")), "negative prompt") + _validate_prompt_token_limit(pipeline, prompt, "prompt", adapter.max_prompt_tokens) + device = getattr(pipeline, "_execution_device", None) or "cpu" + generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed") or 0)) + call_kwargs = { + "image": references[0], + "last_image": last_image, + "prompt": prompt, + "negative_prompt": negative_prompt, + "height": height, + "width": width, + "num_frames": num_frames, + "latent_window_size": int(kwargs.get("latent_window_size") or 9), + "num_inference_steps": int(kwargs.get("num_inference_steps") or 30), + "true_cfg_scale": float(_value_or_default(kwargs, "true_cfg_scale", 1.0)), + "guidance_scale": float(_value_or_default(kwargs, "guidance_scale", 6.0)), + "num_videos_per_prompt": 1, + "generator": generator, + "output_type": kwargs.get("output_type") or "pil", + "return_dict": True, + "attention_kwargs": parse_json_object(kwargs.get("attention_kwargs_json"), "attention kwargs"), + "callback_on_step_end": self.pipe_callback, + "callback_on_step_end_tensor_inputs": callback_tensor_inputs( + kwargs.get("callback_on_step_end_tensor_inputs") + ), + "max_sequence_length": min(int(kwargs.get("max_sequence_length") or 256), 256), + "sampling_type": sampling, + } + self._active_pipeline = pipeline + try: + result = pipeline(**call_kwargs) + finally: + self._active_pipeline = None + frames = getattr(result, "frames", result) + if isinstance(frames, list) and len(frames) == 1 and isinstance(frames[0], list): + frames = frames[0] + return { + "video_out": frames, + "width_out": width, + "height_out": height, + "frames_out": len(frames) if isinstance(frames, list) else num_frames, + } + + def _execute_wan_text_to_video( + self, + pipeline: Any, + adapter: VideoPipelineAdapter, + mode: str, + kwargs: dict[str, Any], + ): + import torch + + if mode != "text_to_video": + raise ValueError(f"{adapter.pipeline_class} does not support video mode {mode}.") + if ensure_video_list(kwargs.get("video"), "video") is not None: + raise ValueError("Wan text_to_video does not accept a source video.") + if ensure_video_list(kwargs.get("mask"), "mask") is not None: + raise ValueError("Wan text_to_video does not accept a mask.") + if ensure_reference_images(kwargs.get("reference_images")): + raise ValueError("Wan text_to_video does not accept reference images.") + + prompt = ensure_single_prompt(none_if_blank(kwargs.get("prompt")), "prompt") + negative_prompt = ensure_single_prompt(none_if_blank(kwargs.get("negative_prompt")), "negative prompt") + _validate_prompt_token_limit(pipeline, prompt, "prompt", adapter.max_prompt_tokens) + _validate_prompt_token_limit(pipeline, negative_prompt, "negative prompt", adapter.max_prompt_tokens) + is_ti2v = adapter.pipeline_class == "WanTI2VPipeline" + width = int(kwargs.get("width") or (1280 if is_ti2v else 832)) + height = int(kwargs.get("height") or (704 if is_ti2v else 480)) + num_frames = normalize_num_frames(int(kwargs.get("num_frames") or (121 if is_ti2v else 81)), pipeline) + device = getattr(pipeline, "_execution_device", None) or "cpu" + generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed", 0))) + default_guidance = 6.0 if adapter.pipeline_class == "WanPipeline" else 5.0 + call_kwargs = { + "prompt": prompt, + "negative_prompt": negative_prompt, + "height": height, + "width": width, + "num_frames": num_frames, + "num_inference_steps": int(kwargs.get("num_inference_steps") or 50), + "guidance_scale": float(kwargs.get("guidance_scale", default_guidance)), + "num_videos_per_prompt": 1, + "generator": generator, + "latents": none_if_blank(kwargs.get("latents")), + "prompt_embeds": none_if_blank(kwargs.get("prompt_embeds")), + "negative_prompt_embeds": none_if_blank(kwargs.get("negative_prompt_embeds")), + "output_type": kwargs.get("output_type", "pil"), + "return_dict": True, + "attention_kwargs": parse_json_object(kwargs.get("attention_kwargs_json"), "attention kwargs"), + "callback_on_step_end": self.pipe_callback, + "callback_on_step_end_tensor_inputs": callback_tensor_inputs( + kwargs.get("callback_on_step_end_tensor_inputs") + ), + "max_sequence_length": int(kwargs.get("max_sequence_length", 512)), + } + requested_flow_shift = float(kwargs.get("scheduler_flow_shift") or 0) + original_scheduler = None + if requested_flow_shift > 0: + from diffusers import UniPCMultistepScheduler + + original_scheduler = pipeline.scheduler + pipeline.scheduler = UniPCMultistepScheduler.from_config( + original_scheduler.config, flow_shift=requested_flow_shift + ) + self._active_pipeline = pipeline + try: + result = pipeline(**call_kwargs) + finally: + self._active_pipeline = None + if original_scheduler is not None: + pipeline.scheduler = original_scheduler + frames = getattr(result, "frames", result) + if isinstance(frames, list) and len(frames) == 1 and isinstance(frames[0], list): + frames = frames[0] + return { + "video_out": frames, + "width_out": width, + "height_out": height, + "frames_out": len(frames) if isinstance(frames, list) else num_frames, + } + + def _execute_wan_video_to_video( + self, + pipeline: Any, + adapter: VideoPipelineAdapter, + mode: str, + kwargs: dict[str, Any], + ): + import torch + + prompt = ensure_single_prompt(none_if_blank(kwargs.get("prompt")), "prompt") + negative_prompt = ensure_single_prompt(none_if_blank(kwargs.get("negative_prompt")), "negative prompt") + video = ensure_video_list(kwargs.get("video"), "video") + if video is None: + raise ValueError(f"Wan {mode} requires a source video.") + if ensure_video_list(kwargs.get("mask"), "mask") is not None: + raise ValueError(f"{adapter.pipeline_class} does not accept a mask; use Wan VACE video inpaint instead.") + if ensure_reference_images(kwargs.get("reference_images")): + raise ValueError(f"{adapter.pipeline_class} does not accept reference images.") + + strength = float(kwargs.get("strength", 0.8)) + if not 0 < strength <= 1: + raise ValueError(f"Wan {mode} strength must be greater than 0 and at most 1; received {strength}.") + width = int(kwargs.get("width", 832)) + height = int(kwargs.get("height", 480)) + device = getattr(pipeline, "_execution_device", None) or "cpu" + generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed", 0))) + call_kwargs = { + "video": video, + "prompt": prompt, + "negative_prompt": negative_prompt, + "height": height, + "width": width, + "num_inference_steps": int(kwargs.get("num_inference_steps", 30)), + "guidance_scale": float(kwargs.get("guidance_scale", 5.0)), + "strength": strength, + "num_videos_per_prompt": 1, + "generator": generator, + "latents": none_if_blank(kwargs.get("latents")), + "prompt_embeds": none_if_blank(kwargs.get("prompt_embeds")), + "negative_prompt_embeds": none_if_blank(kwargs.get("negative_prompt_embeds")), + "output_type": kwargs.get("output_type", "pil"), + "return_dict": True, + "attention_kwargs": parse_json_object(kwargs.get("attention_kwargs_json"), "attention kwargs"), + "callback_on_step_end": self.pipe_callback, + "callback_on_step_end_tensor_inputs": callback_tensor_inputs( + kwargs.get("callback_on_step_end_tensor_inputs") + ), + "max_sequence_length": int(kwargs.get("max_sequence_length", 512)), + } + self._active_pipeline = pipeline + try: + result = pipeline(**call_kwargs) + finally: + self._active_pipeline = None + frames = getattr(result, "frames", result) + if isinstance(frames, list) and len(frames) == 1 and isinstance(frames[0], list): + frames = frames[0] + return { + "video_out": frames, + "width_out": width, + "height_out": height, + "frames_out": len(frames) if isinstance(frames, list) else len(video), + } + + def _execute_wan_image_to_video( + self, + pipeline: Any, + adapter: VideoPipelineAdapter, + mode: str, + kwargs: dict[str, Any], + ): + import torch + + if mode not in {"image_to_video", "reference_to_video"}: + raise ValueError(f"{adapter.pipeline_class} does not support video mode {mode}.") + if ensure_video_list(kwargs.get("video"), "video") is not None: + raise ValueError("Wan image-to-video does not accept a source video.") + if ensure_video_list(kwargs.get("mask"), "mask") is not None: + raise ValueError("Wan image-to-video does not accept a mask; use Wan VACE for masked video editing.") + references = ensure_reference_images(kwargs.get("reference_images")) + if not references or len(references) != 1: + raise ValueError("Wan image-to-video needs exactly one opening reference image.") + + prompt = ensure_single_prompt(none_if_blank(kwargs.get("prompt")), "prompt") + negative_prompt = ensure_single_prompt(none_if_blank(kwargs.get("negative_prompt")), "negative prompt") + _validate_prompt_token_limit(pipeline, prompt, "prompt", adapter.max_prompt_tokens) + _validate_prompt_token_limit(pipeline, negative_prompt, "negative prompt", adapter.max_prompt_tokens) + width = int(kwargs.get("width") or 832) + height = int(kwargs.get("height") or 480) + vae_scale = int(getattr(pipeline, "vae_scale_factor_spatial", 8) or 8) + transformer = getattr(pipeline, "transformer", None) + patch_size = getattr(getattr(transformer, "config", None), "patch_size", (1, 2, 2)) + spatial_patch = int(patch_size[1] if isinstance(patch_size, (list, tuple)) else patch_size) + spatial_multiple = vae_scale * spatial_patch + if width % spatial_multiple or height % spatial_multiple: + raise ValueError( + f"Wan image-to-video dimensions must be divisible by {spatial_multiple}; received {width}x{height}." + ) + num_frames = normalize_num_frames(int(kwargs.get("num_frames") or 81), pipeline) + if num_frames < 81: + raise ValueError("Quality-first Wan shots need at least 81 frames (about five seconds at 16 fps).") + device = getattr(pipeline, "_execution_device", None) or "cpu" + generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed") or 0)) + call_kwargs = { + "image": references[0], + "last_image": kwargs.get("last_image"), + "prompt": prompt, + "negative_prompt": negative_prompt, + "height": height, + "width": width, + "num_frames": num_frames, + "num_inference_steps": int(kwargs.get("num_inference_steps") or 40), + "guidance_scale": float(_value_or_default(kwargs, "guidance_scale", 3.5)), + "guidance_scale_2": float(_value_or_default(kwargs, "secondary_guidance_scale", 3.5)), + "num_videos_per_prompt": 1, + "generator": generator, + "latents": none_if_blank(kwargs.get("latents")), + "prompt_embeds": none_if_blank(kwargs.get("prompt_embeds")), + "negative_prompt_embeds": none_if_blank(kwargs.get("negative_prompt_embeds")), + "output_type": kwargs.get("output_type") or "pil", + "return_dict": True, + "attention_kwargs": parse_json_object(kwargs.get("attention_kwargs_json"), "attention kwargs"), + "callback_on_step_end": self.pipe_callback, + "callback_on_step_end_tensor_inputs": callback_tensor_inputs( + kwargs.get("callback_on_step_end_tensor_inputs") + ), + "max_sequence_length": min(int(kwargs.get("max_sequence_length") or 512), 512), + } + self._active_pipeline = pipeline + try: + result = pipeline(**call_kwargs) + finally: + self._active_pipeline = None + frames = getattr(result, "frames", result) + if isinstance(frames, list) and len(frames) == 1 and isinstance(frames[0], list): + frames = frames[0] + return { + "video_out": frames, + "width_out": width, + "height_out": height, + "frames_out": len(frames) if isinstance(frames, list) else num_frames, + } + + def _execute_ltx(self, pipeline: Any, adapter: VideoPipelineAdapter, mode: str, kwargs: dict[str, Any]): + import torch + from diffusers.pipelines.ltx.pipeline_ltx_condition import LTXVideoCondition + + prompt = ensure_single_prompt(none_if_blank(kwargs.get("prompt")), "prompt") + negative_prompt = ensure_single_prompt(none_if_blank(kwargs.get("negative_prompt")), "negative prompt") + video = ensure_video_list(kwargs.get("video"), "video") + reference_images = ensure_reference_images(kwargs.get("reference_images")) + mask = ensure_video_list(kwargs.get("mask"), "mask") + if mask is not None: + raise ValueError( + "LTX Video does not support the generic mask input; use a registered control adapter when available." + ) + if mode == "text_to_video" and (video is not None or reference_images is not None): + raise ValueError("LTX text_to_video does not accept image or video conditioning inputs.") + if mode in {"image_to_video", "reference_to_video"} and not reference_images: + raise ValueError(f"LTX {mode} requires at least one reference image.") + if mode == "video_to_video" and video is None: + raise ValueError("LTX video_to_video requires a source video.") + + _validate_prompt_token_limit(pipeline, prompt, "prompt", adapter.max_prompt_tokens) + _validate_prompt_token_limit(pipeline, negative_prompt, "negative prompt", adapter.max_prompt_tokens) + + width = int(kwargs.get("width", 704)) + height = int(kwargs.get("height", 480)) + _validate_ltx_dimensions(width, height) + num_frames = _normalize_ltx_frames(int(kwargs.get("num_frames", 97))) + device = getattr(pipeline, "_execution_device", None) or "cpu" + generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed", 0))) + call_kwargs = { + "prompt": prompt, + "negative_prompt": negative_prompt, + "height": height, + "width": width, + "num_frames": num_frames, + "frame_rate": int(kwargs.get("frame_rate", 25)), + "num_inference_steps": int(kwargs.get("num_inference_steps", 40)), + "guidance_scale": float(kwargs.get("guidance_scale", 3.0)), + "num_videos_per_prompt": 1, + "generator": generator, + "latents": none_if_blank(kwargs.get("latents")), + "prompt_embeds": none_if_blank(kwargs.get("prompt_embeds")), + "negative_prompt_embeds": none_if_blank(kwargs.get("negative_prompt_embeds")), + "output_type": kwargs.get("output_type", "pil"), + "return_dict": True, + "attention_kwargs": parse_json_object(kwargs.get("attention_kwargs_json"), "attention kwargs"), + "callback_on_step_end": self.pipe_callback, + "callback_on_step_end_tensor_inputs": callback_tensor_inputs( + kwargs.get("callback_on_step_end_tensor_inputs") + ), + "max_sequence_length": min( + int(kwargs.get("max_sequence_length", adapter.max_prompt_tokens or 256)), + adapter.max_prompt_tokens or 256, + ), + } + model_repo = str(getattr(pipeline, "_modiff_video_repo", "")).lower() + inference_steps = int(call_kwargs["num_inference_steps"]) + if "distilled" in model_repo: + if inference_steps != 8: + raise ValueError( + "The LTX distilled artifact requires exactly 8 inference steps; " + f"received {inference_steps}. Select an LTX dev artifact for longer schedules." + ) + if float(call_kwargs["guidance_scale"]) != 1.0: + raise ValueError("The LTX distilled artifact requires guidance scale 1 (CFG is not used).") + call_kwargs["negative_prompt"] = None + call_kwargs["timesteps"] = list(LTX_DISTILLED_TIMESTEPS) + if mode in {"image_to_video", "reference_to_video"}: + condition_strength = float(kwargs.get("strength", 1.0)) + if len(reference_images) == 1: + # The qualified 0.9.8 distilled checkpoint is stable when an + # opening still is encoded as a one-frame video condition. + # Its direct image-condition path can decode black frames for + # partial guide strengths, so retain the validated contract. + call_kwargs["conditions"] = [ + LTXVideoCondition(video=[reference_images[0]], frame_index=0, strength=condition_strength) + ] + else: + call_kwargs["conditions"] = [ + LTXVideoCondition( + image=image, + frame_index=round(index * (num_frames - 1) / (len(reference_images) - 1)), + strength=condition_strength, + ) + for index, image in enumerate(reference_images) + ] + elif mode == "video_to_video": + call_kwargs["conditions"] = [ + LTXVideoCondition( + video=video, + frame_index=0, + strength=float(kwargs.get("strength", 1.0)), + ) + ] + call_kwargs["denoise_strength"] = float(kwargs.get("denoise_strength", 1.0)) + + scheduler_patch = _install_ltx_dynamic_shift(pipeline, width, height, num_frames) + self._active_pipeline = pipeline + try: + result = pipeline(**call_kwargs) + finally: + if scheduler_patch is not None: + scheduler, original_set_timesteps = scheduler_patch + scheduler.set_timesteps = original_set_timesteps + self._active_pipeline = None + frames = getattr(result, "frames", result) + if isinstance(frames, list) and len(frames) == 1 and isinstance(frames[0], list): + frames = frames[0] + return { + "video_out": frames, + "width_out": width, + "height_out": height, + "frames_out": len(frames) if isinstance(frames, list) else num_frames, + } + + def _execute_ltx_long(self, pipeline: Any, adapter: VideoPipelineAdapter, mode: str, kwargs: dict[str, Any]): + if mode != "image_to_video": + raise ValueError("LTX long sliding-window generation requires image_to_video mode.") + if ( + ensure_video_list(kwargs.get("video"), "video") is not None + or ensure_video_list(kwargs.get("mask"), "mask") is not None + ): + raise ValueError("LTX long image-to-video does not accept source video or mask inputs.") + references = ensure_reference_images(kwargs.get("reference_images")) + if not references or len(references) != 1: + raise ValueError("LTX long image-to-video needs exactly one opening reference image.") + prompt = ensure_single_prompt(none_if_blank(kwargs.get("prompt")), "prompt") + _validate_prompt_token_limit(pipeline, prompt, "prompt", adapter.max_prompt_tokens) + width = int(kwargs.get("width") or 1216) + height = int(kwargs.get("height") or 704) + _validate_ltx_dimensions(width, height) + num_frames = _normalize_ltx_frames(int(kwargs.get("num_frames") or 753)) + steps = int(kwargs.get("num_inference_steps") or 8) + guidance = float(_value_or_default(kwargs, "guidance_scale", 1)) + model_repo = str(getattr(pipeline, "_modiff_video_repo", "")).lower() + if "distilled" in model_repo and (steps != 8 or guidance != 1): + raise ValueError("The LTX distilled long-video pipeline requires 8 steps and guidance scale 1.") + tile_size = int(kwargs.get("temporal_tile_size") or 80) + overlap = int(kwargs.get("temporal_overlap") or 24) + if overlap >= tile_size: + raise ValueError("LTX long temporal overlap must be smaller than its temporal window.") + segments_text = str(kwargs.get("prompt_segments_json") or "").strip() + prompt_segments = None + if segments_text: + try: + prompt_segments = json.loads(segments_text) + except json.JSONDecodeError as exc: + raise ValueError(f"Timed prompt segments must be valid JSON: {exc.msg}.") from exc + if not isinstance(prompt_segments, list) or any(not isinstance(item, dict) for item in prompt_segments): + raise ValueError("Timed prompt segments must be a JSON list of objects.") + self._active_pipeline = pipeline + try: + result = pipeline( + prompt=prompt, + negative_prompt=None if guidance == 1 else none_if_blank(kwargs.get("negative_prompt")), + prompt_segments=prompt_segments, + cond_image=references[0], + cond_strength=float(kwargs.get("strength") if kwargs.get("strength") is not None else 0.5), + height=height, + width=width, + num_frames=num_frames, + frame_rate=float(kwargs.get("frame_rate") or 25), + guidance_scale=guidance, + num_inference_steps=steps, + seed=int(kwargs.get("seed") or 0), + temporal_tile_size=tile_size, + temporal_overlap=overlap, + temporal_overlap_cond_strength=float( + kwargs.get("temporal_overlap_condition_strength") + if kwargs.get("temporal_overlap_condition_strength") is not None + else 0.5 + ), + adain_factor=float(kwargs.get("adain_factor") if kwargs.get("adain_factor") is not None else 0.25), + decode_timestep=0.05, + decode_noise_scale=0.025, + output_type=kwargs.get("output_type") or "pil", + return_dict=True, + attention_kwargs=parse_json_object(kwargs.get("attention_kwargs_json"), "attention kwargs"), + callback_on_step_end=self.pipe_callback, + # The long pipeline keeps its global unpacked tensor in a local + # named ``latents`` while denoising ``latents_packed``. Asking + # for the generic "latents" callback input makes Diffusers + # replace the packed sampler state with that 5D global tensor + # on the next step. Progress/cancellation needs no tensor copy. + callback_on_step_end_tensor_inputs=[], + max_sequence_length=min(int(kwargs.get("max_sequence_length") or 128), 128), + ) + finally: + self._active_pipeline = None + frames = getattr(result, "frames", result) + if isinstance(frames, list) and len(frames) == 1 and isinstance(frames[0], list): + frames = frames[0] + return { + "video_out": frames, + "width_out": width, + "height_out": height, + "frames_out": len(frames) if isinstance(frames, list) else num_frames, + } + + def _execute_ltx2(self, pipeline: Any, adapter: VideoPipelineAdapter, mode: str, kwargs: dict[str, Any]): + import torch + from diffusers.pipelines.ltx2.pipeline_ltx2_condition import LTX2VideoCondition + + prompt = ensure_single_prompt(none_if_blank(kwargs.get("prompt")), "prompt") + negative_prompt = ensure_single_prompt(none_if_blank(kwargs.get("negative_prompt")), "negative prompt") + video = ensure_video_list(kwargs.get("video"), "video") + references = ensure_reference_images(kwargs.get("reference_images")) + if mode == "text_to_video" and (video is not None or references is not None): + raise ValueError("LTX-2 text_to_video does not accept image or video conditions.") + if mode in {"image_to_video", "reference_to_video"} and not references: + raise ValueError(f"LTX-2 {mode} requires at least one reference image.") + if mode == "video_to_video" and not video: + raise ValueError("LTX-2 video_to_video requires a source video.") + width = int(kwargs.get("width") or 768) + height = int(kwargs.get("height") or 512) + _validate_ltx_dimensions(width, height) + num_frames = _normalize_ltx_frames(int(kwargs.get("num_frames") or 121)) + strength = float(_value_or_default(kwargs, "strength", 1)) + conditions = None + if references: + conditions = [ + LTX2VideoCondition( + frames=image, + index=round(index * (num_frames - 1) / max(1, len(references) - 1)), + strength=strength, + ) + for index, image in enumerate(references) + ] + elif video: + conditions = [LTX2VideoCondition(frames=video, index=0, strength=strength)] + device = getattr(pipeline, "_execution_device", None) or "cpu" + generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed") or 0)) + self._active_pipeline = pipeline + try: + result = pipeline( + conditions=conditions, + prompt=prompt, + negative_prompt=negative_prompt, + height=height, + width=width, + num_frames=num_frames, + frame_rate=float(kwargs.get("frame_rate") or 24), + num_inference_steps=int(kwargs.get("num_inference_steps") or 40), + guidance_scale=float(_value_or_default(kwargs, "guidance_scale", 4)), + generator=generator, + output_type=kwargs.get("output_type") or "pil", + return_dict=True, + attention_kwargs=parse_json_object(kwargs.get("attention_kwargs_json"), "attention kwargs"), + callback_on_step_end=self.pipe_callback, + callback_on_step_end_tensor_inputs=callback_tensor_inputs( + kwargs.get("callback_on_step_end_tensor_inputs") + ), + max_sequence_length=min(int(kwargs.get("max_sequence_length") or 1024), 1024), + ) + finally: + self._active_pipeline = None + frames = getattr(result, "frames", result) + if isinstance(frames, list) and len(frames) == 1 and isinstance(frames[0], list): + frames = frames[0] + return { + "video_out": frames, + "width_out": width, + "height_out": height, + "frames_out": len(frames) if isinstance(frames, list) else num_frames, + "_audio": getattr(result, "audios", None), + } + + +class GenerateVideoAudio(NodeBase): + """Generate synchronized video and audio from any capable Diffusers video pipeline.""" + + label = "Diffusers Video + Audio Generate" + category = "Diffusers Video" + resizable = True + params = { + **Generate.params, + "audio": {"label": "Audio", "display": "output", "type": "audio"}, + "sample_rate_out": {"label": "Sample Rate", "display": "output", "type": "int"}, + "duration_seconds": {"label": "Audio Duration", "display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + from modules.DiffusersAudio.main import output_to_audio_object + + pipeline = kwargs.get("pipeline") + if pipeline is None: + raise ValueError("A Diffusers video pipeline is required.") + adapter = _pipeline_adapter(pipeline) + if "audio" not in adapter.output_media: + raise ValueError( + f"{adapter.pipeline_class} does not produce synchronized audio. " + "Use Diffusers Video Generate for video-only pipelines." + ) + worker = Generate(self.node_id) + worker._sid = self._sid + worker.pipe_callback = self.pipe_callback + executed_adapter, result = worker._execute_with_adapter(**kwargs) + raw_audio = result.pop("_audio", None) + if raw_audio is None: + raise RuntimeError("The selected video pipeline did not return an audio stream.") + vocoder = getattr(pipeline, "vocoder", None) + vocoder_config = getattr(vocoder, "config", None) + configured_rate = vocoder_config.get("output_sampling_rate") if hasattr(vocoder_config, "get") else None + sample_rate = int(configured_rate or executed_adapter.default_audio_sample_rate or 48000) + audio = output_to_audio_object(raw_audio, sample_rate=sample_rate) + return { + **result, + "audio": audio, + "sample_rate_out": sample_rate, + "duration_seconds": float(audio["duration_seconds"]), + } + + +class GenerateLTX2(GenerateVideoAudio): + """Deprecated action alias for saved LTX-2 video-and-audio workflows.""" + + +class BuildShotJobs(NodeBase): + """Pair planned shots with keyframes and quality settings for a visual collection loop.""" + + label = "Build Quality Video Shot Jobs" + category = "Diffusers Video" + resizable = True + params = { + "shots": {"label": "Shot Plan", "display": "input", "type": "collection"}, + "opening_images": {"label": "Opening Keyframes", "display": "input", "type": "image"}, + "ending_images": {"label": "Optional Ending Keyframes", "display": "input", "type": "image", "required": False}, + "mode": { + "label": "Shot Mode", + "type": "string", + "options": ["image_to_video", "text_to_video"], + "default": "image_to_video", + }, + "reference_policy": { + "label": "Keyframe Pairing", + "type": "string", + "options": ["one_per_shot", "reuse_first", "none"], + "default": "one_per_shot", + }, + "negative_prompt": { + "label": "Shared Negative Prompt", + "display": "textarea", + "type": "text", + "default": "over-saturated, overexposed, static, blurred details, subtitles, illustration, painting, frozen frame, gray cast, worst quality, low quality, JPEG artifacts, ugly, deformed, disfigured, malformed limbs, fused fingers, extra limbs, inconsistent anatomy, flicker, temporal jitter, warped geometry, duplicate subject, abrupt camera jump", + }, + "base_seed": {"label": "Base Seed", "type": "int", "default": 42017}, + "fps": {"label": "FPS", "type": "int", "default": 16, "min": 1, "max": 60}, + "minimum_seconds": {"label": "Minimum Shot Length", "type": "float", "default": 5, "min": 5}, + "width": {"label": "Width", "type": "int", "default": 832, "min": 16}, + "height": {"label": "Height", "type": "int", "default": 480, "min": 16}, + "steps": {"label": "Steps", "type": "int", "default": 40, "min": 1, "max": 100}, + "guidance_scale": {"label": "High-noise Guidance", "type": "float", "default": 3.5, "min": 0}, + "secondary_guidance_scale": { + "label": "Low-noise Guidance", + "type": "float", + "default": 3.5, + "min": 0, + }, + "conditioning_strength": { + "label": "Opening Keyframe Strength", + "type": "float", + "default": 0.9, + "min": 0, + "max": 1, + "step": 0.05, + "description": "Lower values allow more motion; higher values preserve the opening frame more exactly.", + }, + "jobs": {"label": "Shot Jobs", "display": "output", "type": "collection"}, + "count": {"label": "Shot Count", "display": "output", "type": "int"}, + "planned_duration_seconds": {"label": "Planned Duration", "display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + shots = kwargs.get("shots") + if not isinstance(shots, (list, tuple)) or not shots: + raise ValueError("Build Quality Video Shot Jobs needs a non-empty shot collection.") + openings = kwargs.get("opening_images") + openings = ( + list(openings) if isinstance(openings, (list, tuple)) else [openings] if openings is not None else [] + ) + endings = kwargs.get("ending_images") + endings = list(endings) if isinstance(endings, (list, tuple)) else [endings] if endings is not None else [] + mode = str(kwargs.get("mode") or "image_to_video") + if mode not in {"image_to_video", "text_to_video"}: + raise ValueError(f"Unsupported quality shot mode {mode!r}.") + if mode == "image_to_video" and not openings: + raise ValueError("Build Quality Video Shot Jobs needs opening keyframes.") + policy = "none" if mode == "text_to_video" else str(kwargs.get("reference_policy") or "one_per_shot") + if policy == "one_per_shot" and len(openings) != len(shots): + raise ValueError( + f"One-per-shot keyframe pairing needs {len(shots)} opening images; received {len(openings)}." + ) + if policy not in {"one_per_shot", "reuse_first", "none"}: + raise ValueError(f"Unsupported keyframe pairing policy {policy!r}.") + if endings and len(endings) not in {1, len(shots)}: + raise ValueError("Ending keyframes must contain one shared image or one image per shot.") + + fps = max(1, int(kwargs.get("fps") or 16)) + minimum = max(5.0, float(kwargs.get("minimum_seconds") or 5)) + base_seed = int(kwargs.get("base_seed") or 0) + jobs = [] + planned_duration = 0.0 + for index, shot in enumerate(shots): + if not isinstance(shot, dict) or not str(shot.get("prompt") or "").strip(): + raise ValueError(f"Shot {index + 1} needs a non-empty prompt record.") + duration = float(shot.get("duration_seconds") or 0) + if duration < minimum: + raise ValueError( + f"Shot {index + 1} is {duration:g} seconds; quality video shots must be at least {minimum:g} seconds." + ) + target_frames = max(1, round(duration * fps)) + num_frames = 1 + ((target_frames - 1 + 3) // 4) * 4 + opening = openings[index] if policy == "one_per_shot" else openings[0] if policy == "reuse_first" else None + ending = endings[index] if len(endings) == len(shots) else endings[0] if endings else None + jobs.append( + { + **shot, + "index": index, + "mode": mode, + "opening_image": opening, + "ending_image": ending, + "negative_prompt": str(kwargs.get("negative_prompt") or "").strip(), + "seed": int(shot.get("seed", base_seed + index)), + "fps": fps, + "num_frames": num_frames, + "width": int(kwargs.get("width") or 832), + "height": int(kwargs.get("height") or 480), + "steps": int(kwargs.get("steps") or 40), + "guidance_scale": float(_value_or_default(kwargs, "guidance_scale", 3.5)), + "secondary_guidance_scale": float( + _value_or_default(kwargs, "secondary_guidance_scale", 3.5) + ), + "conditioning_strength": float( + _value_or_default( + shot, + "conditioning_strength", + _value_or_default(kwargs, "conditioning_strength", 0.9), + ) + ), + } + ) + planned_duration += num_frames / fps + return {"jobs": jobs, "count": len(jobs), "planned_duration_seconds": planned_duration} + + +class GenerateShotJob(NodeBase): + """Execute one normalized shot job through any compatible Diffusers video pipeline.""" + + label = "Generate Video Shot Job" + category = "Diffusers Video" + params = { + "pipeline": { + "label": "Pipeline", + "display": "input", + "type": "video_diffusion_pipeline", + "required": True, + }, + "job": {"label": "Shot Job", "display": "input", "type": "any", "required": True}, + "video_out": {"label": "Video", "display": "output", "type": "video"}, + "width_out": {"label": "Width", "display": "output", "type": "int"}, + "height_out": {"label": "Height", "display": "output", "type": "int"}, + "frames_out": {"label": "Frames", "display": "output", "type": "int"}, + "fps_out": {"label": "FPS", "display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + pipeline = kwargs.get("pipeline") + job = kwargs.get("job") + if pipeline is None: + raise ValueError("Generate Video Shot Job needs a Diffusers video pipeline.") + if not isinstance(job, dict): + raise TypeError("Generate Video Shot Job needs a shot job record.") + prompt = str(job.get("prompt") or "").strip() + opening = job.get("opening_image") + mode = str(job.get("mode") or "image_to_video") + if not prompt or (mode == "image_to_video" and opening is None): + raise ValueError("A video shot job needs a prompt and image-to-video jobs also need opening_image.") + + worker = Generate(self.node_id) + worker._sid = self._sid + worker.pipe_callback = self.pipe_callback + result = worker.execute( + pipeline=pipeline, + mode=mode, + reference_images=[opening] if opening is not None else None, + last_image=job.get("ending_image"), + prompt=prompt, + negative_prompt=str(job.get("negative_prompt") or ""), + width=int(job.get("width") or 832), + height=int(job.get("height") or 480), + num_frames=int(job.get("num_frames") or 81), + frame_rate=float(job.get("fps") or 16), + num_inference_steps=int(job.get("steps") or 40), + guidance_scale=float(_value_or_default(job, "guidance_scale", 3.5)), + secondary_guidance_scale=float(_value_or_default(job, "secondary_guidance_scale", 3.5)), + strength=float(_value_or_default(job, "conditioning_strength", 0.9)), + seed=int(job.get("seed") or 0), + output_type=str(job.get("output_type") or "pil"), + ) + result["width_out"] = int(result.get("width_out") or job.get("width") or 832) + result["height_out"] = int(result.get("height_out") or job.get("height") or 480) + result["fps_out"] = float(job.get("fps") or 16) + return result + + +class GenerateSequence(NodeBase): + """Generate a storyboard sequence through any compatible text-to-video adapter.""" + + label = "Diffusers Video Generate Sequence" + category = "Diffusers Video" + params = { + **Generate.params, + "prompts_json": { + "label": "Shot prompts (JSON)", + "display": "textarea", + "type": "text", + "default": "[]", + "description": "JSON array of two to six prompt strings or {prompt, seed} shot objects.", + }, + "clips": {"label": "Clips", "display": "output", "type": "video_collection"}, + "clip_count": {"label": "Clip count", "display": "output", "type": "int"}, + "total_frames": {"label": "Total frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + import json + + pipeline = kwargs.get("pipeline") + if pipeline is None: + raise ValueError("A Diffusers video pipeline is required.") + adapter = _pipeline_adapter(pipeline) + if "text_to_video" not in adapter.modes: + raise ValueError(f"{adapter.pipeline_class} cannot generate a text-to-video sequence.") + try: + prompts = json.loads(str(kwargs.get("prompts_json") or "[]")) + except json.JSONDecodeError as exc: + raise ValueError(f"Shot prompts must be valid JSON: {exc.msg}.") from exc + + def normalize_shot(item, index): + if isinstance(item, str) and item.strip(): + return {"prompt": item.strip(), "seed": None} + if isinstance(item, dict) and isinstance(item.get("prompt"), str) and item["prompt"].strip(): + seed = item.get("seed") + if seed is not None and (isinstance(seed, bool) or not isinstance(seed, int)): + raise ValueError(f"Shot {index + 1} seed must be an integer when supplied.") + return {"prompt": item["prompt"].strip(), "seed": seed} + raise ValueError(f"Shot {index + 1} must be a non-empty prompt string or a prompt object.") + + if not isinstance(prompts, list) or not 2 <= len(prompts) <= 6: + raise ValueError("Shot prompts must be a JSON array containing two to six shots.") + shots = [normalize_shot(item, index) for index, item in enumerate(prompts)] + + base_seed = int(kwargs.get("seed", 0)) + clips = [] + total_frames = 0 + width_out = 0 + height_out = 0 + shot_generator = Generate(self.node_id) + shot_generator._sid = self._sid + for index, shot in enumerate(shots): + self.progress( + int(index / len(shots) * 100), + phase="sequence", + message=f"Generating shot {index + 1} of {len(shots)}", + ) + + # Preserve interruption, timeout and Diffusers callback behavior, + # then remap the current shot's denoising percentage onto the full + # sequence. Without this wrapper every new shot made progress jump + # back to zero, which was especially misleading for 30-second jobs. + def sequence_pipe_callback(pipe, step_index, timestep, callback_kwargs, *, shot_index=index): + result = self.pipe_callback(pipe, step_index, timestep, callback_kwargs) + shot_steps = max(1, int(pipe._num_timesteps)) + completed_in_shot = step_index + 1 + total_steps = len(shots) * shot_steps + completed_steps = shot_index * shot_steps + completed_in_shot + self.progress( + int(completed_steps / total_steps * 100), + phase="denoising", + message=( + f"Shot {shot_index + 1}/{len(shots)}: " + f"denoising {completed_in_shot}/{shot_steps}" + ), + current_step=completed_steps, + total_steps=total_steps, + ) + return result + + shot_generator.pipe_callback = sequence_pipe_callback + values = dict( + kwargs, + mode="text_to_video", + prompt=shot["prompt"], + seed=shot["seed"] if shot["seed"] is not None else base_seed + index, + ) + result = shot_generator.execute(**values) + clip = result["video_out"] + clips.append(clip) + total_frames += int(result.get("frames_out") or len(clip)) + width_out = int(result.get("width_out") or width_out) + height_out = int(result.get("height_out") or height_out) + self.progress(100, phase="sequence", message=f"Generated {len(shots)} of {len(shots)} shots") + return { + "video_out": [frame for clip in clips for frame in clip], + "width_out": width_out, + "height_out": height_out, + "frames_out": total_frames, + "clips": clips, + "clip_count": len(clips), + "total_frames": total_frames, + } + + +class PlanLongVideo(NodeBase): + """Create loop-ready jobs for a continuous take or an authored multi-shot fallback.""" + + label = "Plan Long Video" + category = "Diffusers Video" + resizable = True + params = { + "prompt": {"label": "Prompt", "display": "textarea", "type": "text", "default": ""}, + "target_seconds": {"label": "Approximate Duration", "type": "float", "default": 30, "min": 1, "max": 600}, + "fps": {"label": "FPS", "type": "int", "default": 16, "min": 1, "max": 60}, + "strategy": { + "label": "Strategy", + "type": "string", + "options": ["framepack_continuous", "ltx_continuation", "wan_continuation", "multi_shot"], + "default": "ltx_continuation", + }, + "chunk_seconds": {"label": "Chunk Duration", "type": "float", "default": 5, "min": 1, "max": 30}, + "overlap_seconds": {"label": "Boundary Overlap", "type": "float", "default": 0.25, "min": 0, "max": 5}, + "shot_prompts": { + "label": "Optional Shot Prompts (JSON)", + "display": "textarea", + "type": "text", + "default": "[]", + }, + "seed": {"label": "Seed", "type": "int", "default": 0}, + "jobs": {"label": "Generation Jobs", "display": "output", "type": "collection"}, + "job_count": {"label": "Jobs", "display": "output", "type": "int"}, + "planned_frames": {"label": "Planned Frames", "display": "output", "type": "int"}, + "planned_seconds": {"label": "Planned Duration", "display": "output", "type": "float"}, + } + + @staticmethod + def _legal_frames(strategy, value): + value = max(1, int(value)) + if strategy == "ltx_continuation": + return _normalize_ltx_frames(value) + if strategy == "wan_continuation": + remainder = (value - 1) % 4 + return value if remainder == 0 else value + 4 - remainder + return value + + def execute(self, **kwargs): + import json + from math import ceil + + prompt = str(kwargs.get("prompt") or "").strip() + if not prompt: + raise ValueError("Plan Long Video needs a prompt.") + strategy = str(kwargs.get("strategy") or "ltx_continuation") + fps = max(1, int(kwargs.get("fps") or 16)) + target_frames = max(1, round(float(kwargs.get("target_seconds") or 30) * fps)) + seed = int(kwargs.get("seed") or 0) + raw_shots = kwargs.get("shot_prompts") or "[]" + try: + shot_prompts = json.loads(raw_shots) if isinstance(raw_shots, str) else raw_shots + except json.JSONDecodeError as exc: + raise ValueError(f"Shot prompts must be valid JSON: {exc.msg}.") from exc + if not isinstance(shot_prompts, list) or any( + not isinstance(item, str) or not item.strip() for item in shot_prompts + ): + raise ValueError("Shot prompts must be a JSON array of non-empty strings.") + + if strategy == "framepack_continuous": + chunk_frames = target_frames + count = 1 + else: + chunk_frames = self._legal_frames(strategy, round(float(kwargs.get("chunk_seconds") or 5) * fps)) + overlap = min( + max(0, round(float(kwargs.get("overlap_seconds") or 0) * fps)), + max(0, chunk_frames - 1), + ) + stride = max(1, chunk_frames - overlap) + count = max(1, ceil(max(0, target_frames - overlap) / stride)) + jobs = [] + for index in range(count): + authored = shot_prompts[index % len(shot_prompts)].strip() if shot_prompts else prompt + continuity = index > 0 and strategy in {"ltx_continuation", "wan_continuation"} + jobs.append( + { + "index": index, + "prompt": authored, + "seed": seed + index, + "num_frames": chunk_frames, + "mode": "image_to_video" if strategy != "multi_shot" else "text_to_video", + "uses_previous_last_frame": continuity, + "strategy": strategy, + } + ) + overlap_frames = ( + 0 + if count == 1 + else min( + max(0, round(float(kwargs.get("overlap_seconds") or 0) * fps)), + max(0, chunk_frames - 1), + ) + ) + planned_frames = chunk_frames * count - overlap_frames * max(0, count - 1) + return { + "jobs": jobs, + "job_count": len(jobs), + "planned_frames": planned_frames, + "planned_seconds": planned_frames / fps, + } diff --git a/modules/WanVACE/main.py b/modules/DiffusersVideo/wan_vace.py similarity index 56% rename from modules/WanVACE/main.py rename to modules/DiffusersVideo/wan_vace.py index 9b28340..2bccaae 100644 --- a/modules/WanVACE/main.py +++ b/modules/DiffusersVideo/wan_vace.py @@ -7,14 +7,16 @@ from modiff.diffusers_offload import ( OFFLOAD_MODE_MODEL_CPU, apply_pipeline_offload, - normalize_offload_mode, offload_mode_param, ) +from modiff.model_artifact_catalog import resolve_model_revision from utils.huggingface import local_files_only from utils.torch_utils import DEFAULT_DEVICE, DEVICE_LIST, str_to_dtype logger = logging.getLogger("modiff") +WAN_VACE_NATIVE_CHUNK_FRAMES = 81 + WAN_VACE_DEFAULT_REPO = "Wan-AI/Wan2.1-VACE-1.3B-diffusers" DEVICE_OPTIONS = list(DEVICE_LIST.keys()) @@ -85,6 +87,93 @@ def normalize_num_frames(num_frames: int, pipeline: Any) -> int: return num_frames if remainder == 0 else num_frames + (vae_scale - remainder) +def _black_frame_like(frame: Any): + """Return a black conditioning mask without changing its container type.""" + + try: + import torch + + if isinstance(frame, torch.Tensor): + return torch.zeros_like(frame) + except ImportError: # pragma: no cover - torch is a runtime dependency + pass + + try: + import numpy as np + + if isinstance(frame, np.ndarray): + return np.zeros_like(frame) + except ImportError: # pragma: no cover - numpy is a runtime dependency + pass + + if hasattr(frame, "mode") and hasattr(frame, "size"): + from PIL import Image + + return Image.new(frame.mode, frame.size, 0) + raise TypeError(f"Wan VACE cannot create a continuity mask for {type(frame).__name__} frames.") + + +def _neutralize_masked_region(frame: Any, mask: Any): + """Replace generated VACE regions with the contract's neutral gray value. + + VACE treats white mask pixels as regions to generate and black pixels as + source pixels to preserve. The corresponding source pixels must be + neutral gray; leaving the original image under a white mask can make the + pipeline preserve the old object instead of honoring the edit. + """ + + try: + import torch + + if isinstance(frame, torch.Tensor) and isinstance(mask, torch.Tensor): + mask_values = mask + threshold = 0.5 if mask_values.is_floating_point() and float(mask_values.max()) <= 1 else 127 + generate = mask_values > threshold + while generate.ndim < frame.ndim: + generate = generate.unsqueeze(-1) + if generate.shape != frame.shape: + generate = torch.broadcast_to(generate, frame.shape) + if frame.is_floating_point(): + minimum = float(frame.min()) + maximum = float(frame.max()) + gray = 0.5 if minimum >= 0 and maximum <= 1 else (0.0 if minimum >= -1 and maximum <= 1 else 127.0) + else: + gray = 127 + return torch.where(generate, torch.as_tensor(gray, dtype=frame.dtype, device=frame.device), frame) + except ImportError: # pragma: no cover - torch is a runtime dependency + pass + + try: + import numpy as np + + if isinstance(frame, np.ndarray) and isinstance(mask, np.ndarray): + mask_values = mask[..., 0] if mask.ndim == frame.ndim else mask + threshold = 0.5 if np.issubdtype(mask_values.dtype, np.floating) and float(mask_values.max()) <= 1 else 127 + generate = mask_values > threshold + while generate.ndim < frame.ndim: + generate = np.expand_dims(generate, axis=-1) + if np.issubdtype(frame.dtype, np.floating): + minimum = float(frame.min()) + maximum = float(frame.max()) + gray = 0.5 if minimum >= 0 and maximum <= 1 else (0.0 if minimum >= -1 and maximum <= 1 else 127.0) + else: + gray = 127 + return np.where(generate, np.asarray(gray, dtype=frame.dtype), frame) + except ImportError: # pragma: no cover - numpy is a runtime dependency + pass + + if hasattr(frame, "mode") and hasattr(frame, "size") and hasattr(mask, "convert"): + from PIL import Image + + bands = frame.getbands() + neutral_color = 127 if len(bands) == 1 else tuple(255 if band == "A" else 127 for band in bands) + neutral = Image.new(frame.mode, frame.size, neutral_color) + return Image.composite(neutral, frame, mask.convert("L")) + raise TypeError( + f"Wan VACE cannot neutralize {type(frame).__name__} frames with {type(mask).__name__} masks." + ) + + def validate_dimensions(width: int, height: int, pipeline: Any): spatial_scale = int(getattr(pipeline, "vae_scale_factor_spatial", 8) or 8) transformer = getattr(pipeline, "transformer", None) @@ -100,8 +189,8 @@ def validate_dimensions(width: int, height: int, pipeline: Any): ) -class LoadPipeline(NodeBase): - """Load a Wan VACE Diffusers video pipeline.""" +class WanVACELoadPipeline(NodeBase): + """Internal loader adapter for Hugging Face Diffusers' Wan VACE pipeline.""" label = "Load Wan VACE" category = "Video AI" @@ -130,19 +219,34 @@ class LoadPipeline(NodeBase): }, "auto_offload": {"label": "Auto offload", "type": "bool", "default": True}, "offload_mode": offload_mode_param(), + "execution_recipe": { + "label": "Execution Recipe", + "display": "input", + "type": "diffusers_execution_recipe", + }, "low_cpu_mem_usage": {"label": "Low CPU memory", "type": "bool", "default": True}, } def execute(self, **kwargs): import torch from diffusers import AutoencoderKLWan, WanVACEPipeline + from modules.DiffusersRuntime.main import apply_execution_recipe_to_pipeline, loader_runtime_options - model_id = repo_value(kwargs.get("model_id")) or WAN_VACE_DEFAULT_REPO + model_selection = kwargs.get("model_id") + selected_model_id = repo_value(model_selection) + model_id = selected_model_id or WAN_VACE_DEFAULT_REPO + model_source = model_selection.get("source") if selected_model_id and isinstance(model_selection, dict) else "hub" dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) - revision = none_if_blank(kwargs.get("revision")) - device = kwargs.get("device") or DEFAULT_DEVICE - auto_offload = bool(kwargs.get("auto_offload", True)) - offload_mode = normalize_offload_mode(kwargs.get("offload_mode") or OFFLOAD_MODE_MODEL_CPU, auto_offload=auto_offload) + revision = resolve_model_revision( + model_id, + none_if_blank(kwargs.get("revision")), + source=model_source, + ) + recipe, device, offload_mode, recipe_load_kwargs = loader_runtime_options( + kwargs, + default_device=DEFAULT_DEVICE, + default_offload_mode=OFFLOAD_MODE_MODEL_CPU, + ) logger.info("Loading Wan VACE pipeline: %s", model_id) self.progress(-1, phase="loading", message="Loading Wan VACE pipeline") @@ -166,10 +270,12 @@ def execute(self, **kwargs): "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), "vae": vae, **component_load_kwargs, + **recipe_load_kwargs, } pipeline = WanVACEPipeline.from_pretrained(model_id, **load_kwargs) + apply_execution_recipe_to_pipeline(pipeline, recipe) - self.progress(-1, phase="loading", message=f"Applying {offload_mode} offload") + self.progress(-1, phase="component_placement", message=f"Applying {offload_mode} offload") apply_pipeline_offload( pipeline, mode=offload_mode, @@ -182,19 +288,19 @@ def execute(self, **kwargs): return {"pipeline": pipeline} -class Generate(NodeBase): - """Generate or edit video with the Wan VACE pipeline.""" +class WanVACEGenerate(NodeBase): + """Internal generation adapter for Hugging Face Diffusers' Wan VACE pipeline.""" label = "Wan VACE Generate" category = "Video AI" resizable = True params = { - "pipeline": {"label": "Pipeline", "display": "input", "type": "wan_vace_pipeline"}, + "pipeline": {"label": "Pipeline", "display": "input", "type": "wan_vace_pipeline", "required": True}, "prompt": {"label": "Prompt", "display": "textarea", "type": "text", "default": ""}, "negative_prompt": {"label": "Negative Prompt", "display": "textarea", "type": "text", "default": ""}, - "video": {"label": "Source/control video", "display": "input", "type": "video"}, - "mask": {"label": "Mask video", "display": "input", "type": "video"}, - "reference_images": {"label": "Reference images", "display": "input", "type": "image"}, + "video": {"label": "Source/control video", "display": "input", "type": "video", "required": False}, + "mask": {"label": "Mask video", "display": "input", "type": "video", "required": False}, + "reference_images": {"label": "Reference images", "display": "input", "type": "image", "required": False}, "conditioning_scale": {"label": "Conditioning Scale", "display": "slider", "type": "float", "min": 0, "max": 2, "step": 0.05, "default": 1.0}, "width": {"label": "Width", "type": "int", "default": 832, "min": 16, "max": 2048, "step": 16}, "height": {"label": "Height", "type": "int", "default": 480, "min": 16, "max": 2048, "step": 16}, @@ -205,9 +311,9 @@ class Generate(NodeBase): "use_guidance_scale_2": {"label": "Use guidance 2", "type": "bool", "default": False}, "num_videos_per_prompt": {"label": "Videos per prompt", "type": "int", "default": 1, "min": 1, "max": 1}, "seed": {"label": "Seed", "type": "int", "display": "random", "default": 0, "min": 0, "max": 4294967295}, - "latents": {"label": "Latents", "display": "input", "type": "tensor"}, - "prompt_embeds": {"label": "Prompt embeds", "display": "input", "type": "tensor"}, - "negative_prompt_embeds": {"label": "Negative prompt embeds", "display": "input", "type": "tensor"}, + "latents": {"label": "Latents", "display": "input", "type": "tensor", "required": False}, + "prompt_embeds": {"label": "Prompt embeds", "display": "input", "type": "tensor", "required": False}, + "negative_prompt_embeds": {"label": "Negative prompt embeds", "display": "input", "type": "tensor", "required": False}, "output_type": {"label": "Output type", "type": "string", "options": ["pil", "np", "pt"], "default": "pil"}, "attention_kwargs_json": {"label": "Attention kwargs JSON", "display": "textarea", "type": "text", "default": ""}, "callback_on_step_end_tensor_inputs": {"label": "Callback tensors", "type": "string", "default": "latents"}, @@ -235,6 +341,8 @@ def execute(self, **kwargs): raise ValueError("Wan VACE mask input requires a source/control video input.") if video is not None and mask is not None and len(video) != len(mask): raise ValueError(f"Wan VACE video/mask frame count mismatch: {len(video)} vs {len(mask)}.") + if video is not None and mask is not None: + video = [_neutralize_masked_region(frame, mask_frame) for frame, mask_frame in zip(video, mask)] num_videos_per_prompt = int(kwargs.get("num_videos_per_prompt", 1)) if num_videos_per_prompt != 1: @@ -246,7 +354,7 @@ def execute(self, **kwargs): validate_dimensions(width, height, pipeline) device = getattr(pipeline, "_execution_device", None) or "cpu" - generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed", 0))) + seed = int(kwargs.get("seed", 0)) call_kwargs = { "prompt": prompt, @@ -261,7 +369,7 @@ def execute(self, **kwargs): "num_inference_steps": int(kwargs.get("num_inference_steps", 30)), "guidance_scale": float(kwargs.get("guidance_scale", 5.0)), "num_videos_per_prompt": num_videos_per_prompt, - "generator": generator, + "generator": None, "latents": none_if_blank(kwargs.get("latents")), "prompt_embeds": none_if_blank(kwargs.get("prompt_embeds")), "negative_prompt_embeds": none_if_blank(kwargs.get("negative_prompt_embeds")), @@ -278,14 +386,67 @@ def execute(self, **kwargs): raise ValueError("guidance_scale_2 is only valid for Wan VACE pipelines with boundary_ratio support.") call_kwargs["guidance_scale_2"] = float(kwargs.get("guidance_scale_2", 0.0)) - self._active_pipeline = pipeline - try: - result = pipeline(**call_kwargs) - finally: - self._active_pipeline = None - frames = getattr(result, "frames", result) - if isinstance(frames, list) and len(frames) == 1 and isinstance(frames[0], list): - frames = frames[0] + def run_chunk(chunk_kwargs, chunk_seed): + values = dict(chunk_kwargs) + values["generator"] = torch.Generator(device=device).manual_seed(chunk_seed) + self._active_pipeline = pipeline + try: + result = pipeline(**values) + finally: + self._active_pipeline = None + frames = getattr(result, "frames", result) + if isinstance(frames, list) and len(frames) == 1 and isinstance(frames[0], list): + frames = frames[0] + return frames + + # The official VACE contract is trained and documented around native + # ~5 second (81 frame) clips. Passing a 161-frame source directly is + # substantially slower and, in live proofs, caused the requested mask + # edit to be ignored. Keep the node contract model-neutral while the + # adapter segments longer source-conditioned videos into overlapping + # native clips. For masked edits, the prior generated final frame is a + # black-masked continuity anchor for the next clip. + if video is not None and num_frames > WAN_VACE_NATIVE_CHUNK_FRAMES: + if call_kwargs["latents"] is not None: + raise ValueError("Custom Wan VACE latents cannot be reused across a segmented long-video run.") + frames = [] + start = 0 + chunk_index = 0 + total_chunks = (num_frames - 2) // (WAN_VACE_NATIVE_CHUNK_FRAMES - 1) + 1 + while start < num_frames: + end = min(start + WAN_VACE_NATIVE_CHUNK_FRAMES, num_frames) + chunk_video = list(video[start:end]) + chunk_mask = list(mask[start:end]) if mask is not None else None + if frames and chunk_mask is not None: + chunk_video[0] = frames[-1] + chunk_mask[0] = _black_frame_like(chunk_mask[0]) + values = dict(call_kwargs) + values["video"] = chunk_video + values["mask"] = chunk_mask + values["num_frames"] = len(chunk_video) + self.progress( + chunk_index / total_chunks, + phase="sequence", + message=f"Generating native VACE segment {chunk_index + 1}", + ) + # A segmented edit is one logical generation. Keep the same + # locked seed across native chunks so material/subject identity + # cannot reset at the continuation boundary; the generated + # overlap frame supplies temporal position. + chunk_frames = run_chunk(values, seed) + if not isinstance(chunk_frames, list) or len(chunk_frames) != len(chunk_video): + raise RuntimeError( + f"Wan VACE segment {chunk_index + 1} returned " + f"{len(chunk_frames) if isinstance(chunk_frames, list) else 'an unknown number of'} frames; " + f"expected {len(chunk_video)}." + ) + frames.extend(chunk_frames if not frames else chunk_frames[1:]) + if end >= num_frames: + break + start = end - 1 + chunk_index += 1 + else: + frames = run_chunk(call_kwargs, seed) return { "video_out": frames, diff --git a/modules/Experiments/FLUXKontext.py b/modules/Experiments/FLUXKontext.py deleted file mode 100644 index f80d440..0000000 --- a/modules/Experiments/FLUXKontext.py +++ /dev/null @@ -1,516 +0,0 @@ -import os -import logging -logger = logging.getLogger('modiff') -from modiff.NodeBase import NodeBase -from utils.torch_utils import str_to_dtype, DEVICE_LIST, DEFAULT_DEVICE, IS_CUDA -from utils.huggingface import get_model_class -from modiff.config import CONFIG -from diffusers import FluxTransformer2DModel, AutoencoderKL -from modules.Experiments import QUANT_FIELDS, QUANT_SELECT, PREFERRED_KONTEXT_RESOLUTIONS -from utils.quantization import getQuantizationConfig, quantize -from utils.image import fit as image_fit -from utils.memory_menager import memory_flush -import torch -import importlib -import os -from .flux_layers import FLUX_LAYERS - -HF_TOKEN = CONFIG.hf['token'] -MODELS_DIR = CONFIG.paths['models'] -ONLINE_STATUS = CONFIG.hf['online_status'] - -class FluxTransformerLoader(NodeBase): - def execute(self, **kwargs): - model_id = kwargs.get('model_id', { 'source': 'hub', 'value': 'black-forest-labs/FLUX.1-dev'}) - model_id = model_id.get('value', 'black-forest-labs/FLUX.1-dev') if isinstance(model_id, dict) else model_id - - dtype = str_to_dtype(kwargs['dtype']) - - quantization = kwargs.get('quantization', 'none') - quantization = None if quantization == 'none' else quantization - quantization = 'gguf' if model_id.lower().endswith('.gguf') else quantization - - fuse_qkv = kwargs.get('fuse_qkv', False) - - compile = kwargs.get('compile', False) - compile_mode = kwargs.get('compile_mode', 'default') - compile_fullgraph = kwargs.get('compile_fullgraph', False) - - config = { - 'torch_dtype': dtype, - 'token': HF_TOKEN, - 'subfolder': "transformer" - } - - loaderCallback = FluxTransformer2DModel.from_pretrained - if quantization == 'gguf': - from diffusers import GGUFQuantizationConfig - config['quantization_config'] = GGUFQuantizationConfig(compute_dtype=dtype) - loaderCallback = FluxTransformer2DModel.from_single_file - if 'kontext' in model_id.lower(): - # workaround for an issue with GGUF Kontext models not having the correct in_channels - # https://github.com/huggingface/diffusers/issues/11839 - config['in_channels'] = 64 - elif quantization == 'bnb': - config['quantization_config'] = getQuantizationConfig(quantization, **kwargs) - - transformer = self.graceful_model_loader(loaderCallback, model_id, config) - - if quantization == 'torchao' or quantization == 'quanto': - quant_device = kwargs.get('quant_device', None) - transformer = self.mm_exec(lambda: quantize(transformer, quantization, **kwargs), quant_device, models=[transformer]) - memory_flush() - - # for name, module in transformer.named_modules(): - # with open("transformer_modules.txt", "a") as f: - # f.write(f'"{name}",\n') - - if fuse_qkv and hasattr(transformer, 'fuse_qkv_projections'): - transformer.fuse_qkv_projections() - - if compile: - transformer = torch.compile(transformer, mode=compile_mode, fullgraph=compile_fullgraph) - - self.mm_add(transformer, priority=3) - - return { "transformer": transformer } - -class FluxTextEncoderLoader(NodeBase): - def execute(self, **kwargs): - from transformers import CLIPTextModel, CLIPTokenizer, T5TokenizerFast, T5EncoderModel - - model_id = kwargs.get('model_id', { 'source': 'hub', 'value': 'black-forest-labs/FLUX.1-dev'}) - model_id = model_id.get('value', 'black-forest-labs/FLUX.1-dev') if isinstance(model_id, dict) else model_id - dtype = str_to_dtype(kwargs.get('dtype', 'bfloat16')) - quantization = kwargs.get('quantization', 'none') - quantization = None if quantization == 'none' else quantization - - t5 = kwargs.get('t5', None) - - quant_config = None - if not t5 and quantization == 'bnb': - quant_config = getQuantizationConfig(quantization, **kwargs) - - config = { - "torch_dtype": dtype, - "token": HF_TOKEN, - } - - text_encoder = self.graceful_model_loader(CLIPTextModel, model_id, {**config, "subfolder": "text_encoder"}) - text_encoder_2 = t5 or self.graceful_model_loader(T5EncoderModel, model_id, {**config, "subfolder": "text_encoder_2", "quantization_config": quant_config}) - tokenizer = self.graceful_model_loader(CLIPTokenizer, model_id, {**config, "subfolder": "tokenizer"}) - tokenizer_2 = self.graceful_model_loader(T5TokenizerFast, model_id, {**config, "subfolder": "tokenizer_2"}) - - self.mm_add(text_encoder, priority=1) - if not t5: - self.mm_add(text_encoder_2, priority=1) - - if quantization == 'torchao' or quantization == 'quanto': - quant_device = kwargs.get('quant_device', None) - text_encoder_2 = self.mm_exec(lambda: quantize(text_encoder_2, quantization, **kwargs), quant_device, models=[text_encoder_2]) - memory_flush() - - #print(dict(text_encoder_2.named_parameters()).keys()) - #print(dict(text_encoder_2.named_modules()).keys()) - - return { "encoders": { "text_encoder": text_encoder, "text_encoder_2": text_encoder_2, "tokenizer": tokenizer, "tokenizer_2": tokenizer_2 } } - -class FluxPipelineLoader(NodeBase): - label = "FLUX Pipeline Loader" - category = "loader" - params = { - "pipeline": { "label": "FLUX Pipeline", "display": "output", "type": "pipeline" }, - "model_id": { - "label": "Model", - "display": "modelselect", - "type": "string", - "default": { 'source': 'hub', 'value': 'black-forest-labs/FLUX.1-dev' }, - "fieldOptions": { - "noValidation": True, - "sources": ['hub'], - "filter": { - "hub": { "className": r"^Flux" }, - }, - }, - }, - "dtype": { - "label": "Dtype", - "type": "string", - "default": "bfloat16", - "options": ['auto', 'float32', 'float16', 'bfloat16'], - }, - "transformer": { "label": "Transformer", "display": "input", "type": "FluxTransformer2DModel" }, - "encoders": { "label": "Encoders", "display": "input", "type": "FluxTextEncoders" }, - } - - def execute(self, **kwargs): - model_id = kwargs.get('model_id', { 'source': 'hub', 'value': 'black-forest-labs/FLUX.1-dev'}) - source = model_id.get('source', 'hub') - model_id = model_id.get('value', 'black-forest-labs/FLUX.1-dev') if isinstance(model_id, dict) else model_id - dtype = str_to_dtype(kwargs['dtype']) - transformer = kwargs.get('transformer', None) - encoders = kwargs.get('encoders', None) - - config = { - 'torch_dtype': dtype, - 'token': HF_TOKEN, - } - - if transformer: - config['transformer'] = transformer - if encoders: - config['text_encoder'] = encoders['text_encoder'] - config['text_encoder_2'] = encoders['text_encoder_2'] - config['tokenizer'] = encoders['tokenizer'] - config['tokenizer_2'] = encoders['tokenizer_2'] - - fluxClass = get_model_class(model_id) - - # the repository is not cached, we infer the class name from the model_id - if not fluxClass: - if 'kontext' in model_id.lower(): - fluxClass = 'FluxKontextPipeline' - else: - fluxClass = 'FluxPipeline' - - try: - diffusers_mod = importlib.import_module("diffusers") - FluxPipeline = getattr(diffusers_mod, fluxClass) - except Exception: - logger.error(f"Error loading Pipeline class {fluxClass} for model {model_id}.") - return None - - pipeline = self.graceful_model_loader(FluxPipeline, model_id, config) - - self.mm_add(pipeline.vae, priority=2) - - if not encoders: - self.mm_add(pipeline.text_encoder, priority=1) - self.mm_add(pipeline.text_encoder_2, priority=1) - - if not transformer: - self.mm_add(pipeline.transformer, priority=3) - - return { "pipeline": pipeline } - -class FluxTextEncoder(NodeBase): - label = "FLUX Text Encoder" - category = "embedding" - resizable = True - params = { - "pipeline": { "label": "FLUX Pipeline", "display": "input", "type": ["pipeline", "FluxTextEncoders"] }, - "embeds": { "label": "Embeddings", "display": "output", "type": "embedding" }, - "prompt": { "label": "Prompt", "type": "text" }, - #"negative_prompt": { "label": "Negative Prompt", "type": "text" }, - "device": { "label": "Device", "type": "string", "default": DEFAULT_DEVICE, "options": DEVICE_LIST }, - } - - def execute(self, **kwargs): - pipeline = kwargs.get('pipeline', None) - prompt = kwargs.get('prompt', '') - device = kwargs.get('device', DEFAULT_DEVICE) - - try: - pipelineClass = pipeline.__class__.__name__ or 'FluxPipeline' - diffusers_mod = importlib.import_module("diffusers") - FluxPipeline = getattr(diffusers_mod, pipelineClass) - except Exception as e: - logger.error(f"Error loading pipeline class {pipelineClass}: {e}") - return None - - work_pipe = FluxPipeline.from_pretrained( - pipeline.config._name_or_path, - text_encoder=pipeline.text_encoder, - text_encoder_2=pipeline.text_encoder_2, - tokenizer=pipeline.tokenizer, - tokenizer_2=pipeline.tokenizer_2, - transformer=None, - vae=None, - local_files_only=True, - ) - - pooled_prompt_embeds = self.mm_exec( - lambda: work_pipe._get_clip_prompt_embeds(prompt=prompt, device=device, num_images_per_prompt=1), - device, - models=[work_pipe.text_encoder], - ) - - prompt_embeds = self.mm_exec( - lambda: work_pipe._get_t5_prompt_embeds(prompt=prompt, device=device, num_images_per_prompt=1), - device, - models=[work_pipe.text_encoder_2], - ) - - ( - prompt_embeds, - pooled_prompt_embeds, - _, - ) = self.mm_exec( - lambda: work_pipe.encode_prompt(None, None, prompt_embeds=prompt_embeds, pooled_prompt_embeds=pooled_prompt_embeds, device=device), - device, - models=[work_pipe.text_encoder, work_pipe.text_encoder_2], - ) - del work_pipe - - return { "embeds": (prompt_embeds, pooled_prompt_embeds) } - -class FluxKontextSampler(NodeBase): - label = "FLUX Kontext Sampler" - category = "sampler" - params = { - "pipeline": { "label": "FLUX Pipeline", "display": "input", "type": "pipeline" }, - "embeds": { "label": "Embeddings", "display": "input", "type": "embedding" }, - "image": { "label": "Image", "display": "input", "type": "image" }, - "latents": { "label": "Latents", "display": "output", "type": "latent" }, - "seed": { "label": "Seed", "type": "int", "display": "random", "default": 0, "min": 0, "max": 4294967295 }, - "resolution": { - "label": "Resolution", - "type": "string", - "default": "1024x1024", - "options": [f"{w}x{h}" for w, h in PREFERRED_KONTEXT_RESOLUTIONS], - }, - "steps": { "label": "Steps", "type": "int", "default": 25, "min": 1, "max": 100, "step": 1 }, - "cfg": { "label": "Guidance Scale", "type": "float", "default": 2.5, "min": 0.0, "max": 15.0, "step": 0.1 }, - "device": { "label": "Device", "type": "string", "default": DEFAULT_DEVICE, "options": DEVICE_LIST }, - - } - def __init__(self, node_id=None): - super().__init__(node_id) - self.curr_image = None - self.curr_image_latents = None - - def execute(self, **kwargs): - pipeline = kwargs.get('pipeline', None) - prompt_embeds, pooled_prompt_embeds = kwargs.get('embeds', (None, None)) - cfg = kwargs['cfg'] - image = kwargs['image'] - resolution = kwargs.get('resolution', '1024x1024') - width, height = [int(x) for x in resolution.split('x')] - device = kwargs.get('device', DEFAULT_DEVICE) - seed = kwargs.get('seed', 0) - steps = kwargs.get('steps', 25) - image = image[0] if isinstance(image, list) else image - - generator = torch.Generator(device=device).manual_seed(seed) - - try: - pipelineClass = pipeline.__class__.__name__ or 'FluxKontextPipeline' - diffusers_mod = importlib.import_module("diffusers") - FluxPipeline = getattr(diffusers_mod, pipelineClass) - except Exception as e: - logger.error(f"Error loading pipeline class {pipelineClass}: {e}") - return None - - if self.curr_image is image: - image_latents = self.curr_image_latents - else: - encode_pipe = FluxPipeline.from_pretrained( - pipeline.config._name_or_path, - text_encoder=None, - text_encoder_2=None, - tokenizer=None, - tokenizer_2=None, - transformer=None, - vae=pipeline.vae, - local_files_only=True, - ) - - def encode_image(pipe, image, device, generator): - dtype = pipe.vae.dtype - image = image.convert('RGB') - w, h = image.size - aspect_ratio = w / h - _, ref_w, ref_h = min((abs(aspect_ratio - w / h), w, h) for w, h in PREFERRED_KONTEXT_RESOLUTIONS) - image = image_fit(image, ref_w, ref_h, resample='LANCZOS') - image = pipe.image_processor.preprocess(image) - image = image.to(device, dtype=dtype) - image_latents = pipe._encode_vae_image(image, generator) - image_latents = image_latents.to('cpu').detach().clone() - del pipe, image - return image_latents - - image_latents = self.mm_exec( - lambda: encode_image(encode_pipe, image, device, generator), - device, - models=[encode_pipe.vae], - ) - - self.curr_image = image - self.curr_image_latents = image_latents - del encode_pipe - memory_flush() - - dummy_vae = AutoencoderKL( - in_channels=3, - out_channels=3, - down_block_types=['DownEncoderBlock2D', 'DownEncoderBlock2D', 'DownEncoderBlock2D', 'DownEncoderBlock2D'], - up_block_types=['UpDecoderBlock2D', 'UpDecoderBlock2D', 'UpDecoderBlock2D', 'UpDecoderBlock2D'], - block_out_channels=[128, 256, 512, 512], - layers_per_block=2, - latent_channels=16, - ) - - sampling_config = { - 'image': image_latents, - 'generator': generator, - 'prompt_embeds': prompt_embeds, - 'pooled_prompt_embeds': pooled_prompt_embeds, - 'width': width, - 'height': height, - 'guidance_scale': cfg, - 'num_inference_steps': steps, - 'output_type': "latent", - 'callback_on_step_end': self.pipe_callback, - } - - sampling_pipeline = FluxPipeline.from_pretrained( - pipeline.config._name_or_path, - transformer=pipeline.transformer, - text_encoder=None, - text_encoder_2=None, - tokenizer=None, - tokenizer_2=None, - local_files_only=True, - vae=dummy_vae, - ) - - def sampling(pipe, config, device): - pipe.vae.to(device) - config['image'] = config['image'].to(device, dtype=pipe.transformer.dtype) - config['prompt_embeds'] = config['prompt_embeds'].to(device, dtype=pipe.transformer.dtype) - config['pooled_prompt_embeds'] = config['pooled_prompt_embeds'].to(device, dtype=pipe.transformer.dtype) - - latents = pipe(**config).images - config['image'] = config['image'].to('cpu') - config['prompt_embeds'] = config['prompt_embeds'].to('cpu') - config['pooled_prompt_embeds'] = config['pooled_prompt_embeds'].to('cpu') - del pipe, config - return latents.to('cpu').detach().clone() - - latents = self.mm_exec( - lambda: sampling(sampling_pipeline, sampling_config, device), - device, - models=[sampling_pipeline.transformer], - ) - del sampling_pipeline, dummy_vae, image_latents, prompt_embeds, pooled_prompt_embeds - - return { "latents": (latents, (height, width)) } - - -class NunchakuFluxTransformerLoader(NodeBase): - label = "Nunchaku FLUX Transformer Loader" - category = "loader" - params = { - "model_id": { - "label": "Model", - "display": "modelselect", - "type": "string", - "options": ["nunchaku-tech/nunchaku-flux.1-dev", "nunchaku-tech/nunchaku-flux.1-kontext-dev", "nunchaku-tech/nunchaku-flux.1-krea-dev"], - "default": { 'source': 'hub', 'value': 'nunchaku-tech/nunchaku-flux.1-dev' }, - "fieldOptions": { - "noValidation": True, - "sources": ['hub', 'local'], - "filter": { - "hub": { "id": r"nunchaku.*-flux\.1" }, - "local": { "id": r"svdq-.*-flux\.1" } - }, - } - }, - "fp16_attn": { - "label": "Enable FP16 Attention", - "type": "bool", - "default": IS_CUDA, - "description": "Use Nunchaku's FP16 attention implementation for better performance. Only available on NVIDIA devices from series 30xx and above.", - }, - "dtype": { - "label": "Dtype", - "type": "string", - "default": "bfloat16", - "options": ['auto', 'float32', 'float16', 'bfloat16'], - }, - "transformer": { "label": "Transformer", "display": "output", "type": "FluxTransformer2DModel" } - } - - def execute(self, **kwargs): - from nunchaku import NunchakuFluxTransformer2dModel - from nunchaku.utils import get_precision - - model_id = kwargs.get('model_id', { 'source': 'hub', 'value': 'nunchaku-tech/nunchaku-flux.1-dev'}) - source = model_id.get('source', 'hub') - model_id = model_id.get('value', 'nunchaku-tech/nunchaku-flux.1-dev') - dtype = str_to_dtype(kwargs.get('dtype', 'bfloat16')) - fp16_attn = kwargs.get('fp16_attn', False) - - if source == 'local': - if not os.path.isabs(model_id): - model_id = os.path.join(MODELS_DIR, model_id) - if not os.path.exists(model_id): - raise FileNotFoundError(f"Local model {model_id} not found.") - else: - if not model_id.endswith('.safetensors'): - filename = model_id.split('/')[-1].replace('nunchaku-', f"svdq-{get_precision()}_r32-") + '.safetensors' - model_id = f"{model_id}/{filename}" - - transformer = self.graceful_model_loader(NunchakuFluxTransformer2dModel, model_id, { "torch_dtype": dtype, "token": HF_TOKEN }) - - if fp16_attn and hasattr(transformer, 'set_attention_impl'): - transformer.set_attention_impl("nunchaku-fp16") - - self.mm_add(transformer, priority=3) - - return { "transformer": transformer } - -class NunchakuT5EncoderLoader(NodeBase): - label = "Nunchaku T5 Encoder Loader" - category = "loader" - params = { - "model_id": { - "label": "Model", - "display": "modelselect", - "type": "string", - "default": { 'source': 'hub', 'value': 'nunchaku-tech/nunchaku-t5' }, - "fieldOptions": { - "noValidation": True, - "sources": ['hub', 'local'], - "filter": { - "hub": { "id": r"nunchaku-t5" }, - "local": { "id": r"awq-.*-t5xxl" } - }, - }, - }, - "dtype": { - "label": "Dtype", - "type": "string", - "default": "bfloat16", - "options": ['auto', 'float32', 'float16', 'bfloat16'], - }, - "t5": { - "label": "T5 Encoder", - "display": "output", - "type": "T5EncoderModel", - }, - } - - def execute(self, **kwargs): - from nunchaku import NunchakuT5EncoderModel - - model_id = kwargs.get('model_id', { 'source': 'hub', 'value': 'nunchaku-tech/nunchaku-t5'}) - source = model_id.get('source', 'hub') - model_id = model_id.get('value', 'nunchaku-tech/nunchaku-t5') - dtype = str_to_dtype(kwargs.get('dtype', 'bfloat16')) - - if source == 'local': - if not os.path.isabs(model_id): - model_id = os.path.join(MODELS_DIR, model_id) - if not os.path.exists(model_id): - raise FileNotFoundError(f"Local model {model_id} not found.") - else: - if not model_id.endswith('.safetensors'): - filename = 'awq-int4-flux.1-t5xxl.safetensors' - model_id = f"{model_id}/{filename}" - - t5 = self.graceful_model_loader(NunchakuT5EncoderModel, model_id, { "torch_dtype": dtype, "token": HF_TOKEN }) - - self.mm_add(t5, priority=2) - - return { "t5": t5 } diff --git a/modules/Experiments/StableDiffusion3.py b/modules/Experiments/StableDiffusion3.py deleted file mode 100644 index 16a1828..0000000 --- a/modules/Experiments/StableDiffusion3.py +++ /dev/null @@ -1,476 +0,0 @@ -import torch -from PIL import Image -from modiff.NodeBase import NodeBase -from utils.torch_utils import str_to_dtype, DEVICE_LIST, DEFAULT_DEVICE -from utils.memory_menager import memory_flush -from modiff.config import CONFIG -from utils.huggingface import local_files_only -from diffusers import StableDiffusion3Pipeline, SD3Transformer2DModel, AutoencoderKL -from transformers import CLIPTextModelWithProjection, CLIPTokenizer, T5EncoderModel, T5TokenizerFast -from .utils import get_clip_prompt_embeds, get_t5_prompt_embeds, upcast_vae, sd3_latents_to_rgb -from utils.quantization import getQuantizationConfig, quantize - -HF_TOKEN = CONFIG.hf['token'] - -class SD3PipelineLoader(NodeBase): - '''description": "Load a Stable Diffusion 3 pipeline''' - label = "SD3 Pipeline Loader" - category = "loader" - style = { "minWidth": 360 } - resizable = True - params = { - "pipeline": { "label": "SD3 Pipeline", "display": "output", "type": ["pipeline", "StableDiffusion3Pipeline"] }, - "model_id": { - "label": "Model", - "display": "modelselect", - "type": "string", - "default": { 'source': 'hub', 'value': "stabilityai/stable-diffusion-3.5-large" }, - "fieldOptions": { - "noValidation": True, - "sources": ['hub', 'local'], - "filter": { - "hub": { "className": ["StableDiffusion3Pipeline"] }, - "local": { "id": r"SD3\.5" }, - }, - }, - }, - "dtype": { - "label": "Dtype", - "type": "string", - "default": "bfloat16", - "options": ['auto', 'float32', 'float16', 'bfloat16'], - }, - "load_t5": { "label": "Load T5 Encoder", "type": "boolean", "default": True }, - "text_encoders": { "label": "Text Encoders", "display": "input", "type": "SD3TextEncoders", "onChange": { True: [], False: ['load_t5']} }, - "transformer": { "label": "Transformer", "display": "input", "type": "SD3Transformer2DModel" }, - } - - def execute(self, **kwargs): - dtype = str_to_dtype(kwargs['dtype']) - model_id = kwargs.get('model_id', { 'source': 'hub', 'value': 'stabilityai/stable-diffusion-3.5-large'}) - source = model_id.get('source', 'hub') - model_id = model_id.get('value', 'stabilityai/stable-diffusion-3.5-large') if isinstance(model_id, dict) else model_id - - transformer = kwargs.get('transformer', None) - text_encoders = kwargs.get('text_encoders', None) - load_t5 = kwargs.get('load_t5', True) - config = {} - - if transformer: - config['transformer'] = transformer - if not load_t5: - config['text_encoder_3'] = None - config['tokenizer_3'] = None - - if text_encoders: - config['text_encoder'] = text_encoders['text_encoder'] - config['text_encoder_2'] = text_encoders['text_encoder_2'] - config['text_encoder_3'] = text_encoders['text_encoder_3'] - config['tokenizer'] = text_encoders['tokenizer'] - config['tokenizer_2'] = text_encoders['tokenizer_2'] - config['tokenizer_3'] = text_encoders['tokenizer_3'] - - pipeline = StableDiffusion3Pipeline.from_pretrained( - model_id, - **config, - torch_dtype=dtype, - token=HF_TOKEN, - local_files_only=local_files_only(model_id), - ) - - if dtype == torch.float16 and pipeline.vae.config.force_upcast: - pipeline.vae = upcast_vae(pipeline.vae) - - self.mm_add(pipeline.transformer, priority=3) - self.mm_add(pipeline.vae, priority=2) - - #print(dict(pipeline.transformer.named_modules()).keys()) - - if not text_encoders: - self.mm_add(pipeline.text_encoder, priority=1) - self.mm_add(pipeline.text_encoder_2, priority=1) - if load_t5: - self.mm_add(pipeline.text_encoder_3, priority=1) - - return { "pipeline": pipeline } - -class SD3TransformerLoader(NodeBase): - def execute(self, **kwargs): - model_id = kwargs.get('model_id', { 'source': 'hub', 'value': 'stabilityai/stable-diffusion-3.5-large'}) - model_id = model_id.get('value', 'stabilityai/stable-diffusion-3.5-large') if isinstance(model_id, dict) else model_id - - dtype = str_to_dtype(kwargs['dtype']) - - quantization = kwargs.get('quantization', 'none') - quantization = None if quantization == 'none' else quantization - quantization = 'gguf' if model_id.lower().endswith('.gguf') else quantization - - fuse_qkv = kwargs.get('fuse_qkv', False) - - compile = kwargs.get('compile', False) - compile_mode = kwargs.get('compile_mode', 'default') - compile_fullgraph = kwargs.get('compile_fullgraph', False) - - config = { - 'torch_dtype': dtype, - 'token': HF_TOKEN, - 'subfolder': "transformer" - } - - loaderCallback = SD3Transformer2DModel.from_pretrained - if quantization == 'gguf': - from diffusers import GGUFQuantizationConfig - config['quantization_config'] = GGUFQuantizationConfig(compute_dtype=dtype) - loaderCallback = SD3Transformer2DModel.from_single_file - elif quantization == 'bnb': - config['quantization_config'] = getQuantizationConfig(quantization, **kwargs) - - transformer = self.graceful_model_loader(loaderCallback, model_id, config) - - if quantization == 'torchao' or quantization == 'quanto': - quant_device = kwargs.get('quant_device', None) - transformer = self.mm_exec(lambda: quantize(transformer, quantization, **kwargs), quant_device, models=[transformer]) - memory_flush() - - # for name, module in transformer.named_modules(): - # with open("transformer_modules.txt", "a") as f: - # f.write(f'"{name}",\n') - - if fuse_qkv and hasattr(transformer, 'fuse_qkv_projections'): - transformer.fuse_qkv_projections() - - if compile: - transformer = torch.compile(transformer, mode=compile_mode, fullgraph=compile_fullgraph) - - self.mm_add(transformer, priority=3) - - return { "transformer": transformer } - -class SD3TextEncodersLoader(NodeBase): - def execute(self, **kwargs): - model_id = kwargs.get('model_id', { 'source': 'hub', 'value': 'stabilityai/stable-diffusion-3.5-large'}) - source = model_id.get('source', 'hub') - model_id = model_id.get('value', 'stabilityai/stable-diffusion-3.5-large') if isinstance(model_id, dict) else model_id - dtype = str_to_dtype(kwargs['dtype']) - load_t5 = kwargs.get('load_t5', True) - - quantization = kwargs.get('quantization', 'none') - quantization = None if quantization == 'none' else quantization - - quant_config = None - if load_t5 and quantization == 'bnb': - quant_config = getQuantizationConfig(quantization, **kwargs) - - config = { - "dtype": dtype, - "token": HF_TOKEN, - } - - text_encoder = self.graceful_model_loader(CLIPTextModelWithProjection, model_id, {**config, "subfolder": "text_encoder"}) - tokenizer = self.graceful_model_loader(CLIPTokenizer, model_id, {**config, "subfolder": "tokenizer"}) - text_encoder_2 = self.graceful_model_loader(CLIPTextModelWithProjection, model_id, {**config, "subfolder": "text_encoder_2"}) - tokenizer_2 = self.graceful_model_loader(CLIPTokenizer, model_id, {**config, "subfolder": "tokenizer_2"}) - - self.mm_add(text_encoder, priority=1) - self.mm_add(text_encoder_2, priority=1) - - t5_encoder = None - t5_tokenizer = None - - if load_t5: - t5_encoder = self.graceful_model_loader(T5EncoderModel, model_id, {**config, "subfolder": "text_encoder_3", "quantization_config": quant_config}) - t5_tokenizer = self.graceful_model_loader(T5TokenizerFast, model_id, {**config, "subfolder": "tokenizer_3"}) - self.mm_add(t5_encoder, priority=0) - - #print(dict(t5_encoder.named_parameters()).keys()) - #print(dict(text_encoder_2.named_modules()).keys()) - - if quantization == 'torchao' or quantization == 'quanto': - quant_device = kwargs.get('quant_device', None) - t5_encoder = self.mm_exec(lambda: quantize(t5_encoder, quantization, **kwargs), quant_device, exclude=[t5_encoder]) - memory_flush() - - return { - "encoders": { - "text_encoder": text_encoder, - "text_encoder_2": text_encoder_2, - "text_encoder_3": t5_encoder, - "tokenizer": tokenizer, - "tokenizer_2": tokenizer_2, - "tokenizer_3": t5_tokenizer, - } - } - -class SD3PromptEncoder(NodeBase): - label = "SD3 Prompt Encoder" - category = "embedding" - resizable = True - style = { "minWidth": '280px' } - params = { - "pipeline": { "label": "Encoders", "display": "input", "type": ["pipeline", "SD3TextEncoders"] }, - "embeds": { "label": "Embeddings", "display": "output", "type": "embedding" }, - "prompt": { "label": "Prompt", "type": "string", "display": "textarea" }, - "negative_prompt": { "label": "Negative Prompt", "type": "string", "display": "textarea" }, - "device": { "label": "Device", "type": "string", "default": DEFAULT_DEVICE, "options": DEVICE_LIST }, - } - - def execute(self, pipeline, prompt, negative_prompt, device, **kwargs): - prompt = prompt or '' - prompt_2 = prompt - prompt_3 = prompt - negative_prompt = negative_prompt or '' - negative_prompt_2 = negative_prompt - negative_prompt_3 = negative_prompt - - encoders = { - 'text_encoder': pipeline.text_encoder, - 'text_encoder_2': pipeline.text_encoder_2, - 'text_encoder_3': pipeline.text_encoder_3, - 'tokenizer': pipeline.tokenizer, - 'tokenizer_2': pipeline.tokenizer_2, - 'tokenizer_3': pipeline.tokenizer_3, - } - - def encode(positive_prompt, negative_prompt, tokenizer, text_encoder): - prompt_embeds, pooled_prompt_embeds = get_clip_prompt_embeds(positive_prompt, tokenizer, text_encoder) - negative_prompt_embeds, negative_pooled_prompt_embeds = get_clip_prompt_embeds(negative_prompt, tokenizer, text_encoder) - return prompt_embeds, pooled_prompt_embeds, negative_prompt_embeds, negative_pooled_prompt_embeds - - # 1. encode the prompts with the first text encoder - self.mm_load(encoders['text_encoder'], device) - prompt_embeds, pooled_prompt_embeds, negative_prompt_embeds, negative_pooled_prompt_embeds = self.mm_exec( - lambda: encode(prompt, negative_prompt, encoders['tokenizer'], encoders['text_encoder']), - device, - exclude=[encoders['text_encoder']], - ) - - # 2. encode the prompts with the second text encoder - self.mm_load(encoders['text_encoder_2'], device) - prompt_embeds_2, pooled_prompt_embeds_2, negative_prompt_embeds_2, negative_pooled_prompt_embeds_2 = self.mm_exec( - lambda: encode(prompt_2, negative_prompt_2, encoders['tokenizer_2'], encoders['text_encoder_2']), - device, - exclude=[encoders['text_encoder_2']], - ) - - # 3. concatenate the prompt embeddings - prompt_embeds = torch.cat([prompt_embeds, prompt_embeds_2], dim=-1) - negative_prompt_embeds = torch.cat([negative_prompt_embeds, negative_prompt_embeds_2], dim=-1) - pooled_prompt_embeds = torch.cat([pooled_prompt_embeds, pooled_prompt_embeds_2], dim=-1) - negative_pooled_prompt_embeds = torch.cat([negative_pooled_prompt_embeds, negative_pooled_prompt_embeds_2], dim=-1) - - del prompt_embeds_2, negative_prompt_embeds_2, pooled_prompt_embeds_2, negative_pooled_prompt_embeds_2 - - # 4. encode the prompts with the third text encoder - if encoders['text_encoder_3']: - self.mm_load(encoders['text_encoder_3'], device) - prompt_embeds_3 = self.mm_exec( - lambda: get_t5_prompt_embeds(prompt_3, encoders['tokenizer_3'], encoders['text_encoder_3']), - device, - exclude=[encoders['text_encoder_3']], - ) - negative_prompt_embeds_3 = self.mm_exec( - lambda: get_t5_prompt_embeds(negative_prompt_3, encoders['tokenizer_3'], encoders['text_encoder_3']), - device, - exclude=[encoders['text_encoder_3']], - ) - else: - prompt_embeds_3 = torch.zeros((prompt_embeds.shape[0], 256, 4096), device='cpu', dtype=prompt_embeds.dtype) - negative_prompt_embeds_3 = prompt_embeds_3 - - del encoders - memory_flush() - - # 5. Merge clip and T5 embedings - # T5 should be always longer but you never know with long prompt support - if prompt_embeds.shape[-1] > prompt_embeds_3.shape[-1]: - prompt_embeds_3 = torch.nn.functional.pad(prompt_embeds_3, (0, prompt_embeds.shape[-1] - prompt_embeds_3.shape[-1])) - elif prompt_embeds.shape[-1] < prompt_embeds_3.shape[-1]: - prompt_embeds = torch.nn.functional.pad(prompt_embeds, (0, prompt_embeds_3.shape[-1] - prompt_embeds.shape[-1])) - - if negative_prompt_embeds.shape[-1] > negative_prompt_embeds_3.shape[-1]: - negative_prompt_embeds_3 = torch.nn.functional.pad(negative_prompt_embeds_3, (0, negative_prompt_embeds.shape[-1] - negative_prompt_embeds_3.shape[-1])) - elif negative_prompt_embeds.shape[-1] < negative_prompt_embeds_3.shape[-1]: - negative_prompt_embeds = torch.nn.functional.pad(negative_prompt_embeds, (0, negative_prompt_embeds_3.shape[-1] - negative_prompt_embeds.shape[-1])) - - # concat the embedings - prompt_embeds_3 = prompt_embeds_3 - negative_prompt_embeds_3 = negative_prompt_embeds_3 - prompt_embeds = torch.cat([prompt_embeds, prompt_embeds_3], dim=-2) - negative_prompt_embeds = torch.cat([negative_prompt_embeds, negative_prompt_embeds_3], dim=-2) - - del prompt_embeds_3, negative_prompt_embeds_3 - - # Finally ensure positive and negative embeddings have the same length - if prompt_embeds.shape[1] > negative_prompt_embeds.shape[1]: - negative_prompt_embeds = torch.nn.functional.pad(negative_prompt_embeds, (0, 0, 0, prompt_embeds.shape[1] - negative_prompt_embeds.shape[1])) - elif prompt_embeds.shape[1] < negative_prompt_embeds.shape[1]: - prompt_embeds = torch.nn.functional.pad(prompt_embeds, (0, 0, 0, negative_prompt_embeds.shape[1] - prompt_embeds.shape[1])) - - return { - "embeds": { - "prompt_embeds": prompt_embeds, - "pooled_prompt_embeds": pooled_prompt_embeds, - "negative_prompt_embeds": negative_prompt_embeds, - "negative_pooled_prompt_embeds": negative_pooled_prompt_embeds, - }, - } - -class SD3LatentsPreview(NodeBase): - label = "SD3 Latents Preview" - category = "image" - params = { - "latents": { "label": "Latents", "display": "input", "type": "latent" }, - "image": { "label": "Image", "display": "output", "type": "image", "hidden": True }, - "preview": { "display": "ui_image", "dataSource": "image", "type": "url" }, - } - - def execute(self, latents, **kwargs): - image = sd3_latents_to_rgb(latents) - if image: - image = image.resize((image.width * 2, image.height * 2), resample=Image.Resampling.BICUBIC) - return { "image": image } - -class SD3Sampler(NodeBase): - label = "SD3 Sampler" - category = "sampler" - resizable = True - params = { - "pipeline": { "label": "Pipeline", "display": "input", "type": ["pipeline", "StableDiffusion3Pipeline"] }, - "embeds": { "label": "Embeddings", "display": "input", "type": "embedding" }, - "latents": { "label": "Latents", "display": "output", "type": "latent" }, - "width": { "label": "Width", "type": "int", "default": 1024, "min": 64, "step": 8 }, - "height": { "label": "Height", "type": "int", "default": 1024, "min": 64, "step": 8 }, - "seed": { "label": "Seed", "type": "int", "display": "random", "default": 0, "min": 0, "max": 4294967295 }, - "steps": { "label": "Steps", "type": "int", "display": "slider", "default": 30, "min": 1, "max": 100 }, - "cfg": { "label": "Guidance", "type": "float", "display": "slider","default": 5, "min": 0.0, "max": 50.0, "step": 0.5 }, - "cfg_cutoff": { "label": "Enable CFG Cutoff", "type": "boolean", "default": False, "onChange": { True: ["cfg_step"], False: [] } }, - "cfg_step": { "label": "Cutoff Step", "type": "float", "display": "slider", "default": 0.5, "min": 0, "max": 1, "step": 0.01, "onChange": { True: ["cfg_cutoff"], False: [] } }, - "scheduler": { "label": "Scheduler", "type": "string", "options": { - "FlowMatchEulerDiscreteScheduler": "Flow Match Euler Discrete", - "FlowMatchHeunDiscreteScheduler": "Flow Match Heun Discrete", - }, "default": "FlowMatchEulerDiscreteScheduler" }, - "device": { "label": "Device", "type": "string", "default": DEFAULT_DEVICE, "options": DEVICE_LIST }, - "latents_preview": { "label": "Latents Preview", "display": "output", "type": "latent" }, - } - - def execute(self, pipeline, **kwargs): - embeds = kwargs['embeds'] - width = kwargs.get('width', 1024) - height = kwargs.get('height', 1024) - seed = kwargs.get('seed', 0) - steps = kwargs.get('steps', 30) - cfg = kwargs.get('cfg', 5) - cfg_cutoff = kwargs.get('cfg_cutoff', False) - cfg_step = kwargs.get('cfg_step', 0) - scheduler = kwargs.get('scheduler', 'FlowMatchEulerDiscreteScheduler') - device = kwargs.get('device', DEFAULT_DEVICE) - - generator = torch.Generator(device=device).manual_seed(seed) - - # 1. Create the scheduler - use_dynamic_shifting = True - if ( pipeline.scheduler.__class__.__name__ != scheduler ): - if scheduler == 'FlowMatchHeunDiscreteScheduler': - from diffusers import FlowMatchHeunDiscreteScheduler as SchedulerCls - use_dynamic_shifting = False # not supported by Heun - else: - from diffusers import FlowMatchEulerDiscreteScheduler as SchedulerCls - else: - SchedulerCls = pipeline.scheduler.__class__ - - scheduler_config = pipeline.scheduler.config - sampling_scheduler = SchedulerCls.from_config(scheduler_config, use_dynamic_shifting=use_dynamic_shifting) - - # 2. Prepare the prompts - positive = { "prompt_embeds": embeds['prompt_embeds'], "pooled_prompt_embeds": embeds['pooled_prompt_embeds'] } - negative = None - - if 'negative_prompt_embeds' in embeds: - negative = { "prompt_embeds": embeds['negative_prompt_embeds'], "pooled_prompt_embeds": embeds['negative_pooled_prompt_embeds'] } - - if not negative: - negative = { 'prompt_embeds': torch.zeros_like(positive['prompt_embeds']), 'pooled_prompt_embeds': torch.zeros_like(positive['pooled_prompt_embeds']) } - - # Ensure both prompt embeddings have the same length - if positive['prompt_embeds'].shape[1] > negative['prompt_embeds'].shape[1]: - negative['prompt_embeds'] = torch.nn.functional.pad(negative['prompt_embeds'], (0, 0, 0, positive['prompt_embeds'].shape[1] - negative['prompt_embeds'].shape[1])) - elif positive['prompt_embeds'].shape[1] < negative['prompt_embeds'].shape[1]: - positive['prompt_embeds'] = torch.nn.functional.pad(positive['prompt_embeds'], (0, 0, 0, negative['prompt_embeds'].shape[1] - positive['prompt_embeds'].shape[1])) - - dummy_vae = AutoencoderKL( - in_channels=3, - out_channels=3, - down_block_types=['DownEncoderBlock2D', 'DownEncoderBlock2D', 'DownEncoderBlock2D', 'DownEncoderBlock2D'], - up_block_types=['UpDecoderBlock2D', 'UpDecoderBlock2D', 'UpDecoderBlock2D', 'UpDecoderBlock2D'], - block_out_channels=[128, 256, 512, 512], - layers_per_block=2, - latent_channels=16, - ) - - sampling_pipeline = StableDiffusion3Pipeline.from_pretrained( - pipeline.config._name_or_path, - transformer=pipeline.transformer, - text_encoder=None, - text_encoder_2=None, - text_encoder_3=None, - tokenizer=None, - tokenizer_2=None, - tokenizer_3=None, - scheduler=sampling_scheduler, - local_files_only=True, - vae=dummy_vae, - ) - - def preview_callback(pipe, step_index, timestep, callback_kwargs): - latents = callback_kwargs['latents'] - self.trigger_output("latents_preview", latents) - self.pipe_callback(pipe, step_index, timestep, callback_kwargs) - return callback_kwargs - - sampling_config = { - 'generator': generator, - 'prompt_embeds': positive['prompt_embeds'], - 'pooled_prompt_embeds': positive['pooled_prompt_embeds'], - 'negative_prompt_embeds': negative['prompt_embeds'], - 'negative_pooled_prompt_embeds': negative['pooled_prompt_embeds'], - 'width': width, - 'height': height, - 'guidance_scale': cfg, - 'num_inference_steps': steps, - 'output_type': "latent", - 'callback_on_step_end': preview_callback, - #'num_images_per_prompt': 1, TODO: add support for multiple images - } - - if cfg_cutoff: - sampling_pipeline._cfg_cutoff_step = cfg_step - sampling_config['callback_on_step_end_tensor_inputs'] = ["latents", "prompt_embeds", "pooled_prompt_embeds"] - - del positive, negative, pipeline, embeds - - # 3. Run the denoise loop - def sampling(pipe, config, device): - pipe.vae.to(device) - config['prompt_embeds'] = config['prompt_embeds'].to(device, dtype=pipe.transformer.dtype) - config['pooled_prompt_embeds'] = config['pooled_prompt_embeds'].to(device, dtype=pipe.transformer.dtype) - config['negative_prompt_embeds'] = config['negative_prompt_embeds'].to(device, dtype=pipe.transformer.dtype) - config['negative_pooled_prompt_embeds'] = config['negative_pooled_prompt_embeds'].to(device, dtype=pipe.transformer.dtype) - - latents = pipe(**config).images - config['prompt_embeds'] = config['prompt_embeds'].to('cpu') - config['pooled_prompt_embeds'] = config['pooled_prompt_embeds'].to('cpu') - config['negative_prompt_embeds'] = config['negative_prompt_embeds'].to('cpu') - config['negative_pooled_prompt_embeds'] = config['negative_pooled_prompt_embeds'].to('cpu') - del pipe, config - return latents.to('cpu').detach().clone() - - self.mm_load(sampling_pipeline.transformer, device) - latents = self.mm_exec( - lambda: sampling(sampling_pipeline, sampling_config, device), - device, - exclude=[sampling_pipeline.transformer], - ) - - del sampling_pipeline, sampling_config, dummy_vae - - return { "latents": latents, "latents_preview": latents } diff --git a/modules/Experiments/StableDiffusionXL.py b/modules/Experiments/StableDiffusionXL.py deleted file mode 100644 index 16f2f0f..0000000 --- a/modules/Experiments/StableDiffusionXL.py +++ /dev/null @@ -1,48 +0,0 @@ -import torch -from PIL import Image -from modiff.NodeBase import NodeBase -from utils.torch_utils import str_to_dtype, DEVICE_LIST, DEFAULT_DEVICE -from utils.memory_menager import memory_flush -from modiff.config import CONFIG -from utils.huggingface import local_files_only, get_local_model_ids -from diffusers import StableDiffusionXLPipeline, AutoencoderKL -from transformers import CLIPTextModelWithProjection, CLIPTokenizer -from .utils import get_clip_prompt_embeds, get_t5_prompt_embeds, upcast_vae - -HF_TOKEN = CONFIG.hf['token'] - -class SDXLPipelineLoader(NodeBase): - label = "SDXL Pipeline Loader" - category = "loader" - style = { "minWidth": 360 } - params = { - "pipeline": { "label": "SD3 Pipeline", "display": "output", "type": "pipeline" }, - "model_id": { - "label": "Model", - "display": "autocomplete", - "type": "string", - "default": "stabilityai/stable-diffusion-xl-base-1.0", - "optionsSource": { "source": "hf_cache", "filter": { "className": "StableDiffusionXLPipeline" } }, - "fieldOptions": { "noValidation": True } - }, - "dtype": { - "label": "Dtype", - "type": "string", - "default": "bfloat16", - "options": ['auto', 'float32', 'float16', 'bfloat16'], - }, - } - - def execute(self, **kwargs): - model_id = kwargs.get('model_id', 'stabilityai/stable-diffusion-xl-base-1.0') - dtype = str_to_dtype(kwargs['dtype']) - - pipeline = StableDiffusionXLPipeline.from_pretrained( - model_id, - torch_dtype=dtype, - token=HF_TOKEN, - local_files_only=local_files_only(model_id), - variant="fp16", - ) - - return { "pipeline": pipeline } diff --git a/modules/Experiments/VAE.py b/modules/Experiments/VAE.py deleted file mode 100644 index 03db125..0000000 --- a/modules/Experiments/VAE.py +++ /dev/null @@ -1,65 +0,0 @@ -from modiff.NodeBase import NodeBase -from utils.torch_utils import DEFAULT_DEVICE, DEVICE_LIST -from utils.torch_utils import TensorToImage -import torch - -def unpack_latents(latents, height, width, vae_scale_factor): - batch_size, num_patches, channels = latents.shape - - # VAE applies 8x compression on images but we must also account for packing which requires - # latent height and width to be divisible by 2. - height = 2 * (int(height) // int(vae_scale_factor * 2)) - width = 2 * (int(width) // int(vae_scale_factor * 2)) - - latents = latents.view(batch_size, height // 2, width // 2, channels // 4, 2, 2) - latents = latents.permute(0, 3, 1, 4, 2, 5) - - latents = latents.reshape(batch_size, channels // (2 * 2), height, width) - - return latents - -class VAEDecode(NodeBase): - label = "VAE Decode" - category = "sampler" - params = { - "images": { "label": "Images", "display": "output", "type": "image" }, - "pipeline": { "label": "VAE", "display": "input", "type": "pipeline" }, - "latents": { "label": "Latents", "display": "input", "type": "latent" }, - "tiling": { "label": "Enable Tiling", "type": "boolean", "default": False }, - "device": { "label": "Device", "type": "string", "default": DEFAULT_DEVICE, "options": DEVICE_LIST }, - } - - def execute(self, pipeline, **kwargs): - latents = kwargs['latents'] - vae = pipeline.vae if hasattr(pipeline, 'vae') else pipeline - device = kwargs.get('device', DEFAULT_DEVICE) - tiling = kwargs.get('tiling', False) - - if tiling: - vae.enable_tiling() - else: - vae.disable_tiling() - - self.mm_load(vae, device) - images = self.mm_exec(lambda: self.decode(vae, latents), device, exclude=[vae]) - - return { "images": images } - - def decode(self, model, latents, size=None): - if hasattr(model, 'post_quant_conv') and hasattr(model.post_quant_conv, 'parameters'): - latents = latents.to(dtype=next(iter(model.post_quant_conv.parameters())).dtype) - else: - latents = latents.to(dtype=model.dtype) - - if size is not None: - latents = unpack_latents(latents, size[0], size[1], 2 ** (len(model.config.block_out_channels) - 1)) - - #latents = 1 / model.config['scaling_factor'] * latents - latents = (latents / model.config.scaling_factor) + model.config.shift_factor - images = model.decode(latents.to(model.device), return_dict=False)[0] - latents = latents.to('cpu') - del latents, model - - images = images / 2 + 0.5 - images = TensorToImage(images.to('cpu').detach().clone()) - return images diff --git a/modules/Experiments/__init__.py b/modules/Experiments/__init__.py deleted file mode 100644 index b002a6a..0000000 --- a/modules/Experiments/__init__.py +++ /dev/null @@ -1,347 +0,0 @@ -from utils.torch_utils import DEVICE_LIST, DEFAULT_DEVICE -from .flux_layers import FLUX_LAYERS -from .t5_layers import T5_LAYERS -from .sd3_layers import SD3_LAYERS -from copy import deepcopy - -def str_to_none(value, params): - none_values = ['none', 'null', 'no', 'empty', 'ignore'] - return None if value in none_values else value - -MODULE_PARSE = ['StableDiffusion3', 'VAE', 'FLUXKontext', 'StableDiffusionXL'] - -QUANT_FIELDS = { - 'bnb_group': { - 'label': 'BitsAndBytes quantization', - 'display': 'ui_group', - 'options': ['bnb_type', 'bnb_double_quant'], - 'default': 'none', - 'type': 'string', - 'style': { - 'borderTop': '1px solid rgba(255,255,255,0.1)', - 'paddingTop': 1, - } - }, - 'bnb_type': { - 'label': 'Type', - 'options': ['8bit', '4bit'], - 'default': '4bit', - 'type': 'string', - 'onChange': { - '8bit': [], - '4bit': ['bnb_double_quant'] - } - }, - 'bnb_double_quant': { - 'label': 'Double Quantization', - 'type': 'boolean', - 'display': 'checkbox', - 'default': True, - }, - - 'quanto_group': { - 'label': 'Quanto quantization', - 'display': 'ui_group', - 'options': ['quanto_weights', 'quanto_activations'], - 'default': 'none', - 'style': { - 'borderTop': '1px solid rgba(255,255,255,0.1)', - 'paddingTop': 1, - } - }, - 'quanto_weights': { - 'label': 'Weights', - 'options': ['float8', 'int8', 'int4', 'int2'], - 'default': 'float8', - 'type': 'string', - }, - 'quanto_activations': { - 'label': 'Activations', - 'options': ['none', 'float8', 'int8'], - 'default': 'none', - 'type': 'string', - }, - - 'torchao_group': { - 'label': 'TorchAO quantization', - 'display': 'ui_group', - 'options': ['torchao_quant_type'], - 'default': 'none', - 'style': { - 'borderTop': '1px solid rgba(255,255,255,0.1)', - 'paddingTop': 1, - } - }, - - 'torchao_quant_type': { - 'label': 'Quant Type', - 'options': [ - 'int4wo', 'int4dq', 'int8wo', 'int8dq', - 'uint1wo', 'uint2wo', 'uint3wo', 'uint4wo', 'uint5wo', 'uint6wo', 'uint7wo', - 'float8wo_e5m2', 'float8wo_e4m3', 'float8dq_e4m3', 'float8dq_e4m3_tensor', 'float8dq_e4m3_row', - 'fp3_e1m1', 'fp3_e2m0', 'fp4_e1m2', 'fp4_e2m1', 'fp4_e3m0', 'fp5_e1m3', 'fp5_e2m2', - 'fp5_e3m1', 'fp5_e4m0', 'fp6_e1m4', 'fp6_e2m3', 'fp6_e3m2', 'fp6_e4m1', 'fp6_e5m0', - 'fp7_e1m5', 'fp7_e2m4', 'fp7_e3m3', 'fp7_e4m2', 'fp7_e5m1', 'fp7_e6m0' - ], - 'default': 'float8wo_e4m3', - 'type': 'string', - }, - - "quant_exclude": { - "description": "Exclude layers from the quantization process.", - "label": "Exclude Layers", - "display": "autocomplete", - "default": "", - "options": [], - "fieldOptions": { - "multiple": True, - "disableCloseOnSelect": True - } - }, - 'quant_device': { - 'label': 'Quant Device', - 'options': DEVICE_LIST, - 'default': DEFAULT_DEVICE, - 'type': 'string', - } -} - -QUANT_SELECT = { - 'quantization': { - 'label': 'Quantization', - 'type': 'string', - 'options': { 'none': 'None', 'bnb': 'BitsAndBytes', 'quanto': 'Optimum Quanto', 'torchao': 'TorchAO' }, - 'default': 'none', - 'onChange': { - 'none': [], - 'bnb': ['bnb_group', 'quant_device'], - 'quanto': ['quanto_group', 'quant_exclude', 'quant_device'], - 'torchao': ['torchao_group', 'quant_exclude', 'quant_device'] - }, - }, -} - -PREFERRED_KONTEXT_RESOLUTIONS = [ - (672, 1568), - (688, 1504), - (720, 1456), - (752, 1392), - #(800, 1328), - (832, 1248), - (880, 1184), - (944, 1104), - (1024, 1024), - (1104, 944), - (1184, 880), - (1248, 832), - #(1328, 800), - (1392, 752), - (1456, 720), - (1504, 688), - (1568, 672), -] - -MODULE_MAP = { - 'SD3TransformerLoader': { - 'label': "SD3 Transformer Loader", - 'category': "loader", - 'style': { "minWidth": 300 }, - 'resizable': True, - 'params': { - "model_id": { - "label": "Model", - "display": "modelselect", - "type": "string", - "default": { 'source': 'hub', 'value': 'stabilityai/stable-diffusion-3.5-large' }, - "fieldOptions": { - "noValidation": True, - "sources": ['hub', 'local'], - "filter": { - "hub": { "className": ["SD3Transformer2DModel"] }, - "local": { "id": r"sd3\.5" }, - }, - }, - }, - "dtype": { - "label": "Dtype", - "type": "string", - "default": "bfloat16", - "options": ['auto', 'float32', 'float16', 'bfloat16'], - }, - **QUANT_SELECT, - **deepcopy(QUANT_FIELDS), - "fuse_qkv": { - "description": "Improve performance at the cost of increased memory usage.", - "label": "Fuse QKV projections", - "type": "boolean", - "default": False, - }, - "compile": { - "description": "Use Torch to compile the model for improved performance. Works only on supported platforms.", - "label": "Compile", - "type": "boolean", - "default": False, - "onChange": { - True: ['compile_mode', 'compile_fullgraph'], - False: [] - } - }, - "compile_mode": { - "label": "Mode", - "type": "string", - "default": "max-autotune", - "options": ['default', 'reduce-overhead', 'max-autotune', 'max-autotune-no-cudagraphs'], - }, - "compile_fullgraph": { - "label": "Full Graph", - "type": "boolean", - "default": True, - }, - "transformer": { "label": "Transformer", "display": "output", "type": "SD3Transformer2DModel" }, - } - }, - - 'SD3TextEncodersLoader': { - 'description': "Load the CLIP and T5 Text Encoders", - 'label': 'SD3 Text Encoders Loader', - 'category': 'loader', - 'resizable': True, - 'params': { - 'encoders': { - 'label': 'SD3 Encoders', - 'display': 'output', - 'type': 'SD3TextEncoders', - }, - 'model_id': { - "label": "Model", - "display": "modelselect", - "type": "string", - "default": { 'source': 'hub', 'value': "stabilityai/stable-diffusion-3.5-large" }, - "fieldOptions": { - "noValidation": True, - "sources": ['hub', 'local'], - "filter": { - "hub": { "className": ["StableDiffusion3Pipeline"] }, - "local": { "id": r"SD3\.5" }, - }, - }, - }, - 'dtype': { - 'label': 'Dtype', - 'type': 'string', - 'default': 'bfloat16', - 'options': ['auto', 'float32', 'float16', 'bfloat16'], - }, - **QUANT_SELECT, - **deepcopy(QUANT_FIELDS), - 'load_t5': { 'label': 'Load T5 Encoder', 'type': 'boolean', 'default': True }, - } - }, - - 'FluxTransformerLoader': { - 'label': "FLUX Transformer Loader", - 'category': "loader", - 'style': { "minWidth": 300 }, - 'resizable': True, - 'params': { - "model_id": { - "label": "Model", - "display": "modelselect", - "type": "string", - "default": { 'source': 'hub', 'value': 'black-forest-labs/FLUX.1-dev' }, - "fieldOptions": { - "noValidation": True, - "sources": ['hub', 'local'], - "filter": { - "hub": { "className": ["FluxTransformer2DModel"] }, - "local": { "id": r"flux" }, - }, - }, - }, - "dtype": { - "label": "Dtype", - "type": "string", - "default": "bfloat16", - "options": ['auto', 'float32', 'float16', 'bfloat16'], - }, - **QUANT_SELECT, - **deepcopy(QUANT_FIELDS), - "fuse_qkv": { - "description": "Improve performance at the cost of increased memory usage.", - "label": "Fuse QKV projections", - "type": "boolean", - "default": False, - }, - "compile": { - "description": "Use Torch to compile the model for improved performance. Works only on supported platforms.", - "label": "Compile", - "type": "boolean", - "default": False, - "onChange": { - True: ['compile_mode', 'compile_fullgraph'], - False: [] - } - }, - "compile_mode": { - "label": "Mode", - "type": "string", - "default": "max-autotune", - "options": ['default', 'reduce-overhead', 'max-autotune', 'max-autotune-no-cudagraphs'], - }, - "compile_fullgraph": { - "label": "Full Graph", - "type": "boolean", - "default": True, - }, - "transformer": { "label": "Transformer", "display": "output", "type": "FluxTransformer2DModel" }, - } - }, - - 'FluxTextEncoderLoader': { - 'label': "FLUX Text Encoders Loader", - 'category': "loader", - 'params': { - "model_id": { - "label": "Model", - "display": "modelselect", - "type": "string", - "default": { 'source': 'hub', 'value': 'black-forest-labs/FLUX.1-dev' }, - "fieldOptions": { - "noValidation": True, - "sources": ['hub'], - "filter": { - "hub": { "className": r"^Flux" }, - }, - }, - }, - "dtype": { - "label": "Dtype", - "type": "string", - "default": "bfloat16", - "options": ['auto', 'float32', 'float16', 'bfloat16'], - }, - **QUANT_SELECT, - **deepcopy(QUANT_FIELDS), - "t5": { - "label": "T5 Encoder", - "display": "input", - "type": "T5EncoderModel", - }, - "encoders": { - "label": "Encoders", - "display": "output", - "type": "FluxTextEncoders", - }, - } - } -} - -MODULE_MAP['SD3TextEncodersLoader']['params']['quant_exclude']['options'] = T5_LAYERS -MODULE_MAP['SD3TextEncodersLoader']['params']['quant_exclude']['default'] = T5_LAYERS[-1] -MODULE_MAP['SD3TransformerLoader']['params']['quant_exclude']['options'] = SD3_LAYERS -MODULE_MAP['SD3TransformerLoader']['params']['quant_exclude']['default'] = SD3_LAYERS[-1] - -MODULE_MAP['FluxTransformerLoader']['params']['quant_exclude']['options'] = FLUX_LAYERS -MODULE_MAP['FluxTextEncoderLoader']['params']['quant_exclude']['options'] = T5_LAYERS -MODULE_MAP['FluxTextEncoderLoader']['params']['quant_exclude']['default'] = T5_LAYERS[-1] -MODULE_MAP['FluxTransformerLoader']['params']['quant_exclude']['default'] = FLUX_LAYERS[-1] diff --git a/modules/Experiments/flux_layers.py b/modules/Experiments/flux_layers.py deleted file mode 100644 index d91be71..0000000 --- a/modules/Experiments/flux_layers.py +++ /dev/null @@ -1,1279 +0,0 @@ -FLUX_LAYERS = [ -"pos_embed", -"time_text_embed", -"time_text_embed.time_proj", -"time_text_embed.timestep_embedder", -"time_text_embed.timestep_embedder.linear_1", -"time_text_embed.timestep_embedder.act", -"time_text_embed.timestep_embedder.linear_2", -"time_text_embed.guidance_embedder", -"time_text_embed.guidance_embedder.linear_1", -"time_text_embed.guidance_embedder.act", -"time_text_embed.guidance_embedder.linear_2", -"time_text_embed.text_embedder", -"time_text_embed.text_embedder.linear_1", -"time_text_embed.text_embedder.act_1", -"time_text_embed.text_embedder.linear_2", -"context_embedder", -"x_embedder", -"transformer_blocks", -"transformer_blocks.0", -"transformer_blocks.0.norm1", -"transformer_blocks.0.norm1.silu", -"transformer_blocks.0.norm1.linear", -"transformer_blocks.0.norm1.norm", -"transformer_blocks.0.norm1_context", -"transformer_blocks.0.norm1_context.silu", -"transformer_blocks.0.norm1_context.linear", -"transformer_blocks.0.norm1_context.norm", -"transformer_blocks.0.attn", -"transformer_blocks.0.attn.norm_q", -"transformer_blocks.0.attn.norm_k", -"transformer_blocks.0.attn.to_q", -"transformer_blocks.0.attn.to_k", -"transformer_blocks.0.attn.to_v", -"transformer_blocks.0.attn.to_out", -"transformer_blocks.0.attn.to_out.0", -"transformer_blocks.0.attn.to_out.1", -"transformer_blocks.0.attn.norm_added_q", -"transformer_blocks.0.attn.norm_added_k", -"transformer_blocks.0.attn.add_q_proj", -"transformer_blocks.0.attn.add_k_proj", -"transformer_blocks.0.attn.add_v_proj", -"transformer_blocks.0.attn.to_add_out", -"transformer_blocks.0.norm2", -"transformer_blocks.0.ff", -"transformer_blocks.0.ff.net", -"transformer_blocks.0.ff.net.0", -"transformer_blocks.0.ff.net.0.proj", -"transformer_blocks.0.ff.net.1", -"transformer_blocks.0.ff.net.2", -"transformer_blocks.0.norm2_context", -"transformer_blocks.0.ff_context", -"transformer_blocks.0.ff_context.net", -"transformer_blocks.0.ff_context.net.0", -"transformer_blocks.0.ff_context.net.0.proj", -"transformer_blocks.0.ff_context.net.1", -"transformer_blocks.0.ff_context.net.2", -"transformer_blocks.1", -"transformer_blocks.1.norm1", -"transformer_blocks.1.norm1.silu", -"transformer_blocks.1.norm1.linear", -"transformer_blocks.1.norm1.norm", -"transformer_blocks.1.norm1_context", -"transformer_blocks.1.norm1_context.silu", -"transformer_blocks.1.norm1_context.linear", -"transformer_blocks.1.norm1_context.norm", -"transformer_blocks.1.attn", -"transformer_blocks.1.attn.norm_q", -"transformer_blocks.1.attn.norm_k", -"transformer_blocks.1.attn.to_q", -"transformer_blocks.1.attn.to_k", -"transformer_blocks.1.attn.to_v", -"transformer_blocks.1.attn.to_out", -"transformer_blocks.1.attn.to_out.0", -"transformer_blocks.1.attn.to_out.1", -"transformer_blocks.1.attn.norm_added_q", -"transformer_blocks.1.attn.norm_added_k", -"transformer_blocks.1.attn.add_q_proj", -"transformer_blocks.1.attn.add_k_proj", -"transformer_blocks.1.attn.add_v_proj", -"transformer_blocks.1.attn.to_add_out", -"transformer_blocks.1.norm2", -"transformer_blocks.1.ff", -"transformer_blocks.1.ff.net", -"transformer_blocks.1.ff.net.0", -"transformer_blocks.1.ff.net.0.proj", -"transformer_blocks.1.ff.net.1", -"transformer_blocks.1.ff.net.2", -"transformer_blocks.1.norm2_context", -"transformer_blocks.1.ff_context", -"transformer_blocks.1.ff_context.net", -"transformer_blocks.1.ff_context.net.0", -"transformer_blocks.1.ff_context.net.0.proj", -"transformer_blocks.1.ff_context.net.1", -"transformer_blocks.1.ff_context.net.2", -"transformer_blocks.2", -"transformer_blocks.2.norm1", -"transformer_blocks.2.norm1.silu", -"transformer_blocks.2.norm1.linear", -"transformer_blocks.2.norm1.norm", -"transformer_blocks.2.norm1_context", -"transformer_blocks.2.norm1_context.silu", -"transformer_blocks.2.norm1_context.linear", -"transformer_blocks.2.norm1_context.norm", -"transformer_blocks.2.attn", -"transformer_blocks.2.attn.norm_q", -"transformer_blocks.2.attn.norm_k", -"transformer_blocks.2.attn.to_q", -"transformer_blocks.2.attn.to_k", -"transformer_blocks.2.attn.to_v", -"transformer_blocks.2.attn.to_out", -"transformer_blocks.2.attn.to_out.0", -"transformer_blocks.2.attn.to_out.1", -"transformer_blocks.2.attn.norm_added_q", -"transformer_blocks.2.attn.norm_added_k", -"transformer_blocks.2.attn.add_q_proj", -"transformer_blocks.2.attn.add_k_proj", -"transformer_blocks.2.attn.add_v_proj", -"transformer_blocks.2.attn.to_add_out", -"transformer_blocks.2.norm2", -"transformer_blocks.2.ff", -"transformer_blocks.2.ff.net", -"transformer_blocks.2.ff.net.0", -"transformer_blocks.2.ff.net.0.proj", -"transformer_blocks.2.ff.net.1", -"transformer_blocks.2.ff.net.2", -"transformer_blocks.2.norm2_context", -"transformer_blocks.2.ff_context", -"transformer_blocks.2.ff_context.net", -"transformer_blocks.2.ff_context.net.0", -"transformer_blocks.2.ff_context.net.0.proj", -"transformer_blocks.2.ff_context.net.1", -"transformer_blocks.2.ff_context.net.2", -"transformer_blocks.3", -"transformer_blocks.3.norm1", -"transformer_blocks.3.norm1.silu", -"transformer_blocks.3.norm1.linear", -"transformer_blocks.3.norm1.norm", -"transformer_blocks.3.norm1_context", -"transformer_blocks.3.norm1_context.silu", -"transformer_blocks.3.norm1_context.linear", -"transformer_blocks.3.norm1_context.norm", -"transformer_blocks.3.attn", -"transformer_blocks.3.attn.norm_q", -"transformer_blocks.3.attn.norm_k", -"transformer_blocks.3.attn.to_q", -"transformer_blocks.3.attn.to_k", -"transformer_blocks.3.attn.to_v", -"transformer_blocks.3.attn.to_out", -"transformer_blocks.3.attn.to_out.0", -"transformer_blocks.3.attn.to_out.1", -"transformer_blocks.3.attn.norm_added_q", -"transformer_blocks.3.attn.norm_added_k", -"transformer_blocks.3.attn.add_q_proj", -"transformer_blocks.3.attn.add_k_proj", -"transformer_blocks.3.attn.add_v_proj", -"transformer_blocks.3.attn.to_add_out", -"transformer_blocks.3.norm2", -"transformer_blocks.3.ff", -"transformer_blocks.3.ff.net", -"transformer_blocks.3.ff.net.0", -"transformer_blocks.3.ff.net.0.proj", -"transformer_blocks.3.ff.net.1", -"transformer_blocks.3.ff.net.2", -"transformer_blocks.3.norm2_context", -"transformer_blocks.3.ff_context", -"transformer_blocks.3.ff_context.net", -"transformer_blocks.3.ff_context.net.0", -"transformer_blocks.3.ff_context.net.0.proj", -"transformer_blocks.3.ff_context.net.1", -"transformer_blocks.3.ff_context.net.2", -"transformer_blocks.4", -"transformer_blocks.4.norm1", -"transformer_blocks.4.norm1.silu", -"transformer_blocks.4.norm1.linear", -"transformer_blocks.4.norm1.norm", -"transformer_blocks.4.norm1_context", -"transformer_blocks.4.norm1_context.silu", -"transformer_blocks.4.norm1_context.linear", -"transformer_blocks.4.norm1_context.norm", -"transformer_blocks.4.attn", -"transformer_blocks.4.attn.norm_q", -"transformer_blocks.4.attn.norm_k", -"transformer_blocks.4.attn.to_q", -"transformer_blocks.4.attn.to_k", -"transformer_blocks.4.attn.to_v", -"transformer_blocks.4.attn.to_out", -"transformer_blocks.4.attn.to_out.0", -"transformer_blocks.4.attn.to_out.1", -"transformer_blocks.4.attn.norm_added_q", -"transformer_blocks.4.attn.norm_added_k", -"transformer_blocks.4.attn.add_q_proj", -"transformer_blocks.4.attn.add_k_proj", -"transformer_blocks.4.attn.add_v_proj", -"transformer_blocks.4.attn.to_add_out", -"transformer_blocks.4.norm2", -"transformer_blocks.4.ff", -"transformer_blocks.4.ff.net", -"transformer_blocks.4.ff.net.0", -"transformer_blocks.4.ff.net.0.proj", -"transformer_blocks.4.ff.net.1", -"transformer_blocks.4.ff.net.2", -"transformer_blocks.4.norm2_context", -"transformer_blocks.4.ff_context", -"transformer_blocks.4.ff_context.net", -"transformer_blocks.4.ff_context.net.0", -"transformer_blocks.4.ff_context.net.0.proj", -"transformer_blocks.4.ff_context.net.1", -"transformer_blocks.4.ff_context.net.2", -"transformer_blocks.5", -"transformer_blocks.5.norm1", -"transformer_blocks.5.norm1.silu", -"transformer_blocks.5.norm1.linear", -"transformer_blocks.5.norm1.norm", -"transformer_blocks.5.norm1_context", -"transformer_blocks.5.norm1_context.silu", -"transformer_blocks.5.norm1_context.linear", -"transformer_blocks.5.norm1_context.norm", -"transformer_blocks.5.attn", -"transformer_blocks.5.attn.norm_q", -"transformer_blocks.5.attn.norm_k", -"transformer_blocks.5.attn.to_q", -"transformer_blocks.5.attn.to_k", -"transformer_blocks.5.attn.to_v", -"transformer_blocks.5.attn.to_out", -"transformer_blocks.5.attn.to_out.0", -"transformer_blocks.5.attn.to_out.1", -"transformer_blocks.5.attn.norm_added_q", -"transformer_blocks.5.attn.norm_added_k", -"transformer_blocks.5.attn.add_q_proj", -"transformer_blocks.5.attn.add_k_proj", -"transformer_blocks.5.attn.add_v_proj", -"transformer_blocks.5.attn.to_add_out", -"transformer_blocks.5.norm2", -"transformer_blocks.5.ff", -"transformer_blocks.5.ff.net", -"transformer_blocks.5.ff.net.0", -"transformer_blocks.5.ff.net.0.proj", -"transformer_blocks.5.ff.net.1", -"transformer_blocks.5.ff.net.2", -"transformer_blocks.5.norm2_context", -"transformer_blocks.5.ff_context", -"transformer_blocks.5.ff_context.net", -"transformer_blocks.5.ff_context.net.0", -"transformer_blocks.5.ff_context.net.0.proj", -"transformer_blocks.5.ff_context.net.1", -"transformer_blocks.5.ff_context.net.2", -"transformer_blocks.6", -"transformer_blocks.6.norm1", -"transformer_blocks.6.norm1.silu", -"transformer_blocks.6.norm1.linear", -"transformer_blocks.6.norm1.norm", -"transformer_blocks.6.norm1_context", -"transformer_blocks.6.norm1_context.silu", -"transformer_blocks.6.norm1_context.linear", -"transformer_blocks.6.norm1_context.norm", -"transformer_blocks.6.attn", -"transformer_blocks.6.attn.norm_q", -"transformer_blocks.6.attn.norm_k", -"transformer_blocks.6.attn.to_q", -"transformer_blocks.6.attn.to_k", -"transformer_blocks.6.attn.to_v", -"transformer_blocks.6.attn.to_out", -"transformer_blocks.6.attn.to_out.0", -"transformer_blocks.6.attn.to_out.1", -"transformer_blocks.6.attn.norm_added_q", -"transformer_blocks.6.attn.norm_added_k", -"transformer_blocks.6.attn.add_q_proj", -"transformer_blocks.6.attn.add_k_proj", -"transformer_blocks.6.attn.add_v_proj", -"transformer_blocks.6.attn.to_add_out", -"transformer_blocks.6.norm2", -"transformer_blocks.6.ff", -"transformer_blocks.6.ff.net", -"transformer_blocks.6.ff.net.0", -"transformer_blocks.6.ff.net.0.proj", -"transformer_blocks.6.ff.net.1", -"transformer_blocks.6.ff.net.2", -"transformer_blocks.6.norm2_context", -"transformer_blocks.6.ff_context", -"transformer_blocks.6.ff_context.net", -"transformer_blocks.6.ff_context.net.0", -"transformer_blocks.6.ff_context.net.0.proj", -"transformer_blocks.6.ff_context.net.1", -"transformer_blocks.6.ff_context.net.2", -"transformer_blocks.7", -"transformer_blocks.7.norm1", -"transformer_blocks.7.norm1.silu", -"transformer_blocks.7.norm1.linear", -"transformer_blocks.7.norm1.norm", -"transformer_blocks.7.norm1_context", -"transformer_blocks.7.norm1_context.silu", -"transformer_blocks.7.norm1_context.linear", -"transformer_blocks.7.norm1_context.norm", -"transformer_blocks.7.attn", -"transformer_blocks.7.attn.norm_q", -"transformer_blocks.7.attn.norm_k", -"transformer_blocks.7.attn.to_q", -"transformer_blocks.7.attn.to_k", -"transformer_blocks.7.attn.to_v", -"transformer_blocks.7.attn.to_out", -"transformer_blocks.7.attn.to_out.0", -"transformer_blocks.7.attn.to_out.1", -"transformer_blocks.7.attn.norm_added_q", -"transformer_blocks.7.attn.norm_added_k", -"transformer_blocks.7.attn.add_q_proj", -"transformer_blocks.7.attn.add_k_proj", -"transformer_blocks.7.attn.add_v_proj", -"transformer_blocks.7.attn.to_add_out", -"transformer_blocks.7.norm2", -"transformer_blocks.7.ff", -"transformer_blocks.7.ff.net", -"transformer_blocks.7.ff.net.0", -"transformer_blocks.7.ff.net.0.proj", -"transformer_blocks.7.ff.net.1", -"transformer_blocks.7.ff.net.2", -"transformer_blocks.7.norm2_context", -"transformer_blocks.7.ff_context", -"transformer_blocks.7.ff_context.net", -"transformer_blocks.7.ff_context.net.0", -"transformer_blocks.7.ff_context.net.0.proj", -"transformer_blocks.7.ff_context.net.1", -"transformer_blocks.7.ff_context.net.2", -"transformer_blocks.8", -"transformer_blocks.8.norm1", -"transformer_blocks.8.norm1.silu", -"transformer_blocks.8.norm1.linear", -"transformer_blocks.8.norm1.norm", -"transformer_blocks.8.norm1_context", -"transformer_blocks.8.norm1_context.silu", -"transformer_blocks.8.norm1_context.linear", -"transformer_blocks.8.norm1_context.norm", -"transformer_blocks.8.attn", -"transformer_blocks.8.attn.norm_q", -"transformer_blocks.8.attn.norm_k", -"transformer_blocks.8.attn.to_q", -"transformer_blocks.8.attn.to_k", -"transformer_blocks.8.attn.to_v", -"transformer_blocks.8.attn.to_out", -"transformer_blocks.8.attn.to_out.0", -"transformer_blocks.8.attn.to_out.1", -"transformer_blocks.8.attn.norm_added_q", -"transformer_blocks.8.attn.norm_added_k", -"transformer_blocks.8.attn.add_q_proj", -"transformer_blocks.8.attn.add_k_proj", -"transformer_blocks.8.attn.add_v_proj", -"transformer_blocks.8.attn.to_add_out", -"transformer_blocks.8.norm2", -"transformer_blocks.8.ff", -"transformer_blocks.8.ff.net", -"transformer_blocks.8.ff.net.0", -"transformer_blocks.8.ff.net.0.proj", -"transformer_blocks.8.ff.net.1", -"transformer_blocks.8.ff.net.2", -"transformer_blocks.8.norm2_context", -"transformer_blocks.8.ff_context", -"transformer_blocks.8.ff_context.net", -"transformer_blocks.8.ff_context.net.0", -"transformer_blocks.8.ff_context.net.0.proj", -"transformer_blocks.8.ff_context.net.1", -"transformer_blocks.8.ff_context.net.2", -"transformer_blocks.9", -"transformer_blocks.9.norm1", -"transformer_blocks.9.norm1.silu", -"transformer_blocks.9.norm1.linear", -"transformer_blocks.9.norm1.norm", -"transformer_blocks.9.norm1_context", -"transformer_blocks.9.norm1_context.silu", -"transformer_blocks.9.norm1_context.linear", -"transformer_blocks.9.norm1_context.norm", -"transformer_blocks.9.attn", -"transformer_blocks.9.attn.norm_q", -"transformer_blocks.9.attn.norm_k", -"transformer_blocks.9.attn.to_q", -"transformer_blocks.9.attn.to_k", -"transformer_blocks.9.attn.to_v", -"transformer_blocks.9.attn.to_out", -"transformer_blocks.9.attn.to_out.0", -"transformer_blocks.9.attn.to_out.1", -"transformer_blocks.9.attn.norm_added_q", -"transformer_blocks.9.attn.norm_added_k", -"transformer_blocks.9.attn.add_q_proj", -"transformer_blocks.9.attn.add_k_proj", -"transformer_blocks.9.attn.add_v_proj", -"transformer_blocks.9.attn.to_add_out", -"transformer_blocks.9.norm2", -"transformer_blocks.9.ff", -"transformer_blocks.9.ff.net", -"transformer_blocks.9.ff.net.0", -"transformer_blocks.9.ff.net.0.proj", -"transformer_blocks.9.ff.net.1", -"transformer_blocks.9.ff.net.2", -"transformer_blocks.9.norm2_context", -"transformer_blocks.9.ff_context", -"transformer_blocks.9.ff_context.net", -"transformer_blocks.9.ff_context.net.0", -"transformer_blocks.9.ff_context.net.0.proj", -"transformer_blocks.9.ff_context.net.1", -"transformer_blocks.9.ff_context.net.2", -"transformer_blocks.10", -"transformer_blocks.10.norm1", -"transformer_blocks.10.norm1.silu", -"transformer_blocks.10.norm1.linear", -"transformer_blocks.10.norm1.norm", -"transformer_blocks.10.norm1_context", -"transformer_blocks.10.norm1_context.silu", -"transformer_blocks.10.norm1_context.linear", -"transformer_blocks.10.norm1_context.norm", -"transformer_blocks.10.attn", -"transformer_blocks.10.attn.norm_q", -"transformer_blocks.10.attn.norm_k", -"transformer_blocks.10.attn.to_q", -"transformer_blocks.10.attn.to_k", -"transformer_blocks.10.attn.to_v", -"transformer_blocks.10.attn.to_out", -"transformer_blocks.10.attn.to_out.0", -"transformer_blocks.10.attn.to_out.1", -"transformer_blocks.10.attn.norm_added_q", -"transformer_blocks.10.attn.norm_added_k", -"transformer_blocks.10.attn.add_q_proj", -"transformer_blocks.10.attn.add_k_proj", -"transformer_blocks.10.attn.add_v_proj", -"transformer_blocks.10.attn.to_add_out", -"transformer_blocks.10.norm2", -"transformer_blocks.10.ff", -"transformer_blocks.10.ff.net", -"transformer_blocks.10.ff.net.0", -"transformer_blocks.10.ff.net.0.proj", -"transformer_blocks.10.ff.net.1", -"transformer_blocks.10.ff.net.2", -"transformer_blocks.10.norm2_context", -"transformer_blocks.10.ff_context", -"transformer_blocks.10.ff_context.net", -"transformer_blocks.10.ff_context.net.0", -"transformer_blocks.10.ff_context.net.0.proj", -"transformer_blocks.10.ff_context.net.1", -"transformer_blocks.10.ff_context.net.2", -"transformer_blocks.11", -"transformer_blocks.11.norm1", -"transformer_blocks.11.norm1.silu", -"transformer_blocks.11.norm1.linear", -"transformer_blocks.11.norm1.norm", -"transformer_blocks.11.norm1_context", -"transformer_blocks.11.norm1_context.silu", -"transformer_blocks.11.norm1_context.linear", -"transformer_blocks.11.norm1_context.norm", -"transformer_blocks.11.attn", -"transformer_blocks.11.attn.norm_q", -"transformer_blocks.11.attn.norm_k", -"transformer_blocks.11.attn.to_q", -"transformer_blocks.11.attn.to_k", -"transformer_blocks.11.attn.to_v", -"transformer_blocks.11.attn.to_out", -"transformer_blocks.11.attn.to_out.0", -"transformer_blocks.11.attn.to_out.1", -"transformer_blocks.11.attn.norm_added_q", -"transformer_blocks.11.attn.norm_added_k", -"transformer_blocks.11.attn.add_q_proj", -"transformer_blocks.11.attn.add_k_proj", -"transformer_blocks.11.attn.add_v_proj", -"transformer_blocks.11.attn.to_add_out", -"transformer_blocks.11.norm2", -"transformer_blocks.11.ff", -"transformer_blocks.11.ff.net", -"transformer_blocks.11.ff.net.0", -"transformer_blocks.11.ff.net.0.proj", -"transformer_blocks.11.ff.net.1", -"transformer_blocks.11.ff.net.2", -"transformer_blocks.11.norm2_context", -"transformer_blocks.11.ff_context", -"transformer_blocks.11.ff_context.net", -"transformer_blocks.11.ff_context.net.0", -"transformer_blocks.11.ff_context.net.0.proj", -"transformer_blocks.11.ff_context.net.1", -"transformer_blocks.11.ff_context.net.2", -"transformer_blocks.12", -"transformer_blocks.12.norm1", -"transformer_blocks.12.norm1.silu", -"transformer_blocks.12.norm1.linear", -"transformer_blocks.12.norm1.norm", -"transformer_blocks.12.norm1_context", -"transformer_blocks.12.norm1_context.silu", -"transformer_blocks.12.norm1_context.linear", -"transformer_blocks.12.norm1_context.norm", -"transformer_blocks.12.attn", -"transformer_blocks.12.attn.norm_q", -"transformer_blocks.12.attn.norm_k", -"transformer_blocks.12.attn.to_q", -"transformer_blocks.12.attn.to_k", -"transformer_blocks.12.attn.to_v", -"transformer_blocks.12.attn.to_out", -"transformer_blocks.12.attn.to_out.0", -"transformer_blocks.12.attn.to_out.1", -"transformer_blocks.12.attn.norm_added_q", -"transformer_blocks.12.attn.norm_added_k", -"transformer_blocks.12.attn.add_q_proj", -"transformer_blocks.12.attn.add_k_proj", -"transformer_blocks.12.attn.add_v_proj", -"transformer_blocks.12.attn.to_add_out", -"transformer_blocks.12.norm2", -"transformer_blocks.12.ff", -"transformer_blocks.12.ff.net", -"transformer_blocks.12.ff.net.0", -"transformer_blocks.12.ff.net.0.proj", -"transformer_blocks.12.ff.net.1", -"transformer_blocks.12.ff.net.2", -"transformer_blocks.12.norm2_context", -"transformer_blocks.12.ff_context", -"transformer_blocks.12.ff_context.net", -"transformer_blocks.12.ff_context.net.0", -"transformer_blocks.12.ff_context.net.0.proj", -"transformer_blocks.12.ff_context.net.1", -"transformer_blocks.12.ff_context.net.2", -"transformer_blocks.13", -"transformer_blocks.13.norm1", -"transformer_blocks.13.norm1.silu", -"transformer_blocks.13.norm1.linear", -"transformer_blocks.13.norm1.norm", -"transformer_blocks.13.norm1_context", -"transformer_blocks.13.norm1_context.silu", -"transformer_blocks.13.norm1_context.linear", -"transformer_blocks.13.norm1_context.norm", -"transformer_blocks.13.attn", -"transformer_blocks.13.attn.norm_q", -"transformer_blocks.13.attn.norm_k", -"transformer_blocks.13.attn.to_q", -"transformer_blocks.13.attn.to_k", -"transformer_blocks.13.attn.to_v", -"transformer_blocks.13.attn.to_out", -"transformer_blocks.13.attn.to_out.0", -"transformer_blocks.13.attn.to_out.1", -"transformer_blocks.13.attn.norm_added_q", -"transformer_blocks.13.attn.norm_added_k", -"transformer_blocks.13.attn.add_q_proj", -"transformer_blocks.13.attn.add_k_proj", -"transformer_blocks.13.attn.add_v_proj", -"transformer_blocks.13.attn.to_add_out", -"transformer_blocks.13.norm2", -"transformer_blocks.13.ff", -"transformer_blocks.13.ff.net", -"transformer_blocks.13.ff.net.0", -"transformer_blocks.13.ff.net.0.proj", -"transformer_blocks.13.ff.net.1", -"transformer_blocks.13.ff.net.2", -"transformer_blocks.13.norm2_context", -"transformer_blocks.13.ff_context", -"transformer_blocks.13.ff_context.net", -"transformer_blocks.13.ff_context.net.0", -"transformer_blocks.13.ff_context.net.0.proj", -"transformer_blocks.13.ff_context.net.1", -"transformer_blocks.13.ff_context.net.2", -"transformer_blocks.14", -"transformer_blocks.14.norm1", -"transformer_blocks.14.norm1.silu", -"transformer_blocks.14.norm1.linear", -"transformer_blocks.14.norm1.norm", -"transformer_blocks.14.norm1_context", -"transformer_blocks.14.norm1_context.silu", -"transformer_blocks.14.norm1_context.linear", -"transformer_blocks.14.norm1_context.norm", -"transformer_blocks.14.attn", -"transformer_blocks.14.attn.norm_q", -"transformer_blocks.14.attn.norm_k", -"transformer_blocks.14.attn.to_q", -"transformer_blocks.14.attn.to_k", -"transformer_blocks.14.attn.to_v", -"transformer_blocks.14.attn.to_out", -"transformer_blocks.14.attn.to_out.0", -"transformer_blocks.14.attn.to_out.1", -"transformer_blocks.14.attn.norm_added_q", -"transformer_blocks.14.attn.norm_added_k", -"transformer_blocks.14.attn.add_q_proj", -"transformer_blocks.14.attn.add_k_proj", -"transformer_blocks.14.attn.add_v_proj", -"transformer_blocks.14.attn.to_add_out", -"transformer_blocks.14.norm2", -"transformer_blocks.14.ff", -"transformer_blocks.14.ff.net", -"transformer_blocks.14.ff.net.0", -"transformer_blocks.14.ff.net.0.proj", -"transformer_blocks.14.ff.net.1", -"transformer_blocks.14.ff.net.2", -"transformer_blocks.14.norm2_context", -"transformer_blocks.14.ff_context", -"transformer_blocks.14.ff_context.net", -"transformer_blocks.14.ff_context.net.0", -"transformer_blocks.14.ff_context.net.0.proj", -"transformer_blocks.14.ff_context.net.1", -"transformer_blocks.14.ff_context.net.2", -"transformer_blocks.15", -"transformer_blocks.15.norm1", -"transformer_blocks.15.norm1.silu", -"transformer_blocks.15.norm1.linear", -"transformer_blocks.15.norm1.norm", -"transformer_blocks.15.norm1_context", -"transformer_blocks.15.norm1_context.silu", -"transformer_blocks.15.norm1_context.linear", -"transformer_blocks.15.norm1_context.norm", -"transformer_blocks.15.attn", -"transformer_blocks.15.attn.norm_q", -"transformer_blocks.15.attn.norm_k", -"transformer_blocks.15.attn.to_q", -"transformer_blocks.15.attn.to_k", -"transformer_blocks.15.attn.to_v", -"transformer_blocks.15.attn.to_out", -"transformer_blocks.15.attn.to_out.0", -"transformer_blocks.15.attn.to_out.1", -"transformer_blocks.15.attn.norm_added_q", -"transformer_blocks.15.attn.norm_added_k", -"transformer_blocks.15.attn.add_q_proj", -"transformer_blocks.15.attn.add_k_proj", -"transformer_blocks.15.attn.add_v_proj", -"transformer_blocks.15.attn.to_add_out", -"transformer_blocks.15.norm2", -"transformer_blocks.15.ff", -"transformer_blocks.15.ff.net", -"transformer_blocks.15.ff.net.0", -"transformer_blocks.15.ff.net.0.proj", -"transformer_blocks.15.ff.net.1", -"transformer_blocks.15.ff.net.2", -"transformer_blocks.15.norm2_context", -"transformer_blocks.15.ff_context", -"transformer_blocks.15.ff_context.net", -"transformer_blocks.15.ff_context.net.0", -"transformer_blocks.15.ff_context.net.0.proj", -"transformer_blocks.15.ff_context.net.1", -"transformer_blocks.15.ff_context.net.2", -"transformer_blocks.16", -"transformer_blocks.16.norm1", -"transformer_blocks.16.norm1.silu", -"transformer_blocks.16.norm1.linear", -"transformer_blocks.16.norm1.norm", -"transformer_blocks.16.norm1_context", -"transformer_blocks.16.norm1_context.silu", -"transformer_blocks.16.norm1_context.linear", -"transformer_blocks.16.norm1_context.norm", -"transformer_blocks.16.attn", -"transformer_blocks.16.attn.norm_q", -"transformer_blocks.16.attn.norm_k", -"transformer_blocks.16.attn.to_q", -"transformer_blocks.16.attn.to_k", -"transformer_blocks.16.attn.to_v", -"transformer_blocks.16.attn.to_out", -"transformer_blocks.16.attn.to_out.0", -"transformer_blocks.16.attn.to_out.1", -"transformer_blocks.16.attn.norm_added_q", -"transformer_blocks.16.attn.norm_added_k", -"transformer_blocks.16.attn.add_q_proj", -"transformer_blocks.16.attn.add_k_proj", -"transformer_blocks.16.attn.add_v_proj", -"transformer_blocks.16.attn.to_add_out", -"transformer_blocks.16.norm2", -"transformer_blocks.16.ff", -"transformer_blocks.16.ff.net", -"transformer_blocks.16.ff.net.0", -"transformer_blocks.16.ff.net.0.proj", -"transformer_blocks.16.ff.net.1", -"transformer_blocks.16.ff.net.2", -"transformer_blocks.16.norm2_context", -"transformer_blocks.16.ff_context", -"transformer_blocks.16.ff_context.net", -"transformer_blocks.16.ff_context.net.0", -"transformer_blocks.16.ff_context.net.0.proj", -"transformer_blocks.16.ff_context.net.1", -"transformer_blocks.16.ff_context.net.2", -"transformer_blocks.17", -"transformer_blocks.17.norm1", -"transformer_blocks.17.norm1.silu", -"transformer_blocks.17.norm1.linear", -"transformer_blocks.17.norm1.norm", -"transformer_blocks.17.norm1_context", -"transformer_blocks.17.norm1_context.silu", -"transformer_blocks.17.norm1_context.linear", -"transformer_blocks.17.norm1_context.norm", -"transformer_blocks.17.attn", -"transformer_blocks.17.attn.norm_q", -"transformer_blocks.17.attn.norm_k", -"transformer_blocks.17.attn.to_q", -"transformer_blocks.17.attn.to_k", -"transformer_blocks.17.attn.to_v", -"transformer_blocks.17.attn.to_out", -"transformer_blocks.17.attn.to_out.0", -"transformer_blocks.17.attn.to_out.1", -"transformer_blocks.17.attn.norm_added_q", -"transformer_blocks.17.attn.norm_added_k", -"transformer_blocks.17.attn.add_q_proj", -"transformer_blocks.17.attn.add_k_proj", -"transformer_blocks.17.attn.add_v_proj", -"transformer_blocks.17.attn.to_add_out", -"transformer_blocks.17.norm2", -"transformer_blocks.17.ff", -"transformer_blocks.17.ff.net", -"transformer_blocks.17.ff.net.0", -"transformer_blocks.17.ff.net.0.proj", -"transformer_blocks.17.ff.net.1", -"transformer_blocks.17.ff.net.2", -"transformer_blocks.17.norm2_context", -"transformer_blocks.17.ff_context", -"transformer_blocks.17.ff_context.net", -"transformer_blocks.17.ff_context.net.0", -"transformer_blocks.17.ff_context.net.0.proj", -"transformer_blocks.17.ff_context.net.1", -"transformer_blocks.17.ff_context.net.2", -"transformer_blocks.18", -"transformer_blocks.18.norm1", -"transformer_blocks.18.norm1.silu", -"transformer_blocks.18.norm1.linear", -"transformer_blocks.18.norm1.norm", -"transformer_blocks.18.norm1_context", -"transformer_blocks.18.norm1_context.silu", -"transformer_blocks.18.norm1_context.linear", -"transformer_blocks.18.norm1_context.norm", -"transformer_blocks.18.attn", -"transformer_blocks.18.attn.norm_q", -"transformer_blocks.18.attn.norm_k", -"transformer_blocks.18.attn.to_q", -"transformer_blocks.18.attn.to_k", -"transformer_blocks.18.attn.to_v", -"transformer_blocks.18.attn.to_out", -"transformer_blocks.18.attn.to_out.0", -"transformer_blocks.18.attn.to_out.1", -"transformer_blocks.18.attn.norm_added_q", -"transformer_blocks.18.attn.norm_added_k", -"transformer_blocks.18.attn.add_q_proj", -"transformer_blocks.18.attn.add_k_proj", -"transformer_blocks.18.attn.add_v_proj", -"transformer_blocks.18.attn.to_add_out", -"transformer_blocks.18.norm2", -"transformer_blocks.18.ff", -"transformer_blocks.18.ff.net", -"transformer_blocks.18.ff.net.0", -"transformer_blocks.18.ff.net.0.proj", -"transformer_blocks.18.ff.net.1", -"transformer_blocks.18.ff.net.2", -"transformer_blocks.18.norm2_context", -"transformer_blocks.18.ff_context", -"transformer_blocks.18.ff_context.net", -"transformer_blocks.18.ff_context.net.0", -"transformer_blocks.18.ff_context.net.0.proj", -"transformer_blocks.18.ff_context.net.1", -"transformer_blocks.18.ff_context.net.2", -"single_transformer_blocks", -"single_transformer_blocks.0", -"single_transformer_blocks.0.norm", -"single_transformer_blocks.0.norm.silu", -"single_transformer_blocks.0.norm.linear", -"single_transformer_blocks.0.norm.norm", -"single_transformer_blocks.0.proj_mlp", -"single_transformer_blocks.0.act_mlp", -"single_transformer_blocks.0.proj_out", -"single_transformer_blocks.0.attn", -"single_transformer_blocks.0.attn.norm_q", -"single_transformer_blocks.0.attn.norm_k", -"single_transformer_blocks.0.attn.to_q", -"single_transformer_blocks.0.attn.to_k", -"single_transformer_blocks.0.attn.to_v", -"single_transformer_blocks.1", -"single_transformer_blocks.1.norm", -"single_transformer_blocks.1.norm.silu", -"single_transformer_blocks.1.norm.linear", -"single_transformer_blocks.1.norm.norm", -"single_transformer_blocks.1.proj_mlp", -"single_transformer_blocks.1.act_mlp", -"single_transformer_blocks.1.proj_out", -"single_transformer_blocks.1.attn", -"single_transformer_blocks.1.attn.norm_q", -"single_transformer_blocks.1.attn.norm_k", -"single_transformer_blocks.1.attn.to_q", -"single_transformer_blocks.1.attn.to_k", -"single_transformer_blocks.1.attn.to_v", -"single_transformer_blocks.2", -"single_transformer_blocks.2.norm", -"single_transformer_blocks.2.norm.silu", -"single_transformer_blocks.2.norm.linear", -"single_transformer_blocks.2.norm.norm", -"single_transformer_blocks.2.proj_mlp", -"single_transformer_blocks.2.act_mlp", -"single_transformer_blocks.2.proj_out", -"single_transformer_blocks.2.attn", -"single_transformer_blocks.2.attn.norm_q", -"single_transformer_blocks.2.attn.norm_k", -"single_transformer_blocks.2.attn.to_q", -"single_transformer_blocks.2.attn.to_k", -"single_transformer_blocks.2.attn.to_v", -"single_transformer_blocks.3", -"single_transformer_blocks.3.norm", -"single_transformer_blocks.3.norm.silu", -"single_transformer_blocks.3.norm.linear", -"single_transformer_blocks.3.norm.norm", -"single_transformer_blocks.3.proj_mlp", -"single_transformer_blocks.3.act_mlp", -"single_transformer_blocks.3.proj_out", -"single_transformer_blocks.3.attn", -"single_transformer_blocks.3.attn.norm_q", -"single_transformer_blocks.3.attn.norm_k", -"single_transformer_blocks.3.attn.to_q", -"single_transformer_blocks.3.attn.to_k", -"single_transformer_blocks.3.attn.to_v", -"single_transformer_blocks.4", -"single_transformer_blocks.4.norm", -"single_transformer_blocks.4.norm.silu", -"single_transformer_blocks.4.norm.linear", -"single_transformer_blocks.4.norm.norm", -"single_transformer_blocks.4.proj_mlp", -"single_transformer_blocks.4.act_mlp", -"single_transformer_blocks.4.proj_out", -"single_transformer_blocks.4.attn", -"single_transformer_blocks.4.attn.norm_q", -"single_transformer_blocks.4.attn.norm_k", -"single_transformer_blocks.4.attn.to_q", -"single_transformer_blocks.4.attn.to_k", -"single_transformer_blocks.4.attn.to_v", -"single_transformer_blocks.5", -"single_transformer_blocks.5.norm", -"single_transformer_blocks.5.norm.silu", -"single_transformer_blocks.5.norm.linear", -"single_transformer_blocks.5.norm.norm", -"single_transformer_blocks.5.proj_mlp", -"single_transformer_blocks.5.act_mlp", -"single_transformer_blocks.5.proj_out", -"single_transformer_blocks.5.attn", -"single_transformer_blocks.5.attn.norm_q", -"single_transformer_blocks.5.attn.norm_k", -"single_transformer_blocks.5.attn.to_q", -"single_transformer_blocks.5.attn.to_k", -"single_transformer_blocks.5.attn.to_v", -"single_transformer_blocks.6", -"single_transformer_blocks.6.norm", -"single_transformer_blocks.6.norm.silu", -"single_transformer_blocks.6.norm.linear", -"single_transformer_blocks.6.norm.norm", -"single_transformer_blocks.6.proj_mlp", -"single_transformer_blocks.6.act_mlp", -"single_transformer_blocks.6.proj_out", -"single_transformer_blocks.6.attn", -"single_transformer_blocks.6.attn.norm_q", -"single_transformer_blocks.6.attn.norm_k", -"single_transformer_blocks.6.attn.to_q", -"single_transformer_blocks.6.attn.to_k", -"single_transformer_blocks.6.attn.to_v", -"single_transformer_blocks.7", -"single_transformer_blocks.7.norm", -"single_transformer_blocks.7.norm.silu", -"single_transformer_blocks.7.norm.linear", -"single_transformer_blocks.7.norm.norm", -"single_transformer_blocks.7.proj_mlp", -"single_transformer_blocks.7.act_mlp", -"single_transformer_blocks.7.proj_out", -"single_transformer_blocks.7.attn", -"single_transformer_blocks.7.attn.norm_q", -"single_transformer_blocks.7.attn.norm_k", -"single_transformer_blocks.7.attn.to_q", -"single_transformer_blocks.7.attn.to_k", -"single_transformer_blocks.7.attn.to_v", -"single_transformer_blocks.8", -"single_transformer_blocks.8.norm", -"single_transformer_blocks.8.norm.silu", -"single_transformer_blocks.8.norm.linear", -"single_transformer_blocks.8.norm.norm", -"single_transformer_blocks.8.proj_mlp", -"single_transformer_blocks.8.act_mlp", -"single_transformer_blocks.8.proj_out", -"single_transformer_blocks.8.attn", -"single_transformer_blocks.8.attn.norm_q", -"single_transformer_blocks.8.attn.norm_k", -"single_transformer_blocks.8.attn.to_q", -"single_transformer_blocks.8.attn.to_k", -"single_transformer_blocks.8.attn.to_v", -"single_transformer_blocks.9", -"single_transformer_blocks.9.norm", -"single_transformer_blocks.9.norm.silu", -"single_transformer_blocks.9.norm.linear", -"single_transformer_blocks.9.norm.norm", -"single_transformer_blocks.9.proj_mlp", -"single_transformer_blocks.9.act_mlp", -"single_transformer_blocks.9.proj_out", -"single_transformer_blocks.9.attn", -"single_transformer_blocks.9.attn.norm_q", -"single_transformer_blocks.9.attn.norm_k", -"single_transformer_blocks.9.attn.to_q", -"single_transformer_blocks.9.attn.to_k", -"single_transformer_blocks.9.attn.to_v", -"single_transformer_blocks.10", -"single_transformer_blocks.10.norm", -"single_transformer_blocks.10.norm.silu", -"single_transformer_blocks.10.norm.linear", -"single_transformer_blocks.10.norm.norm", -"single_transformer_blocks.10.proj_mlp", -"single_transformer_blocks.10.act_mlp", -"single_transformer_blocks.10.proj_out", -"single_transformer_blocks.10.attn", -"single_transformer_blocks.10.attn.norm_q", -"single_transformer_blocks.10.attn.norm_k", -"single_transformer_blocks.10.attn.to_q", -"single_transformer_blocks.10.attn.to_k", -"single_transformer_blocks.10.attn.to_v", -"single_transformer_blocks.11", -"single_transformer_blocks.11.norm", -"single_transformer_blocks.11.norm.silu", -"single_transformer_blocks.11.norm.linear", -"single_transformer_blocks.11.norm.norm", -"single_transformer_blocks.11.proj_mlp", -"single_transformer_blocks.11.act_mlp", -"single_transformer_blocks.11.proj_out", -"single_transformer_blocks.11.attn", -"single_transformer_blocks.11.attn.norm_q", -"single_transformer_blocks.11.attn.norm_k", -"single_transformer_blocks.11.attn.to_q", -"single_transformer_blocks.11.attn.to_k", -"single_transformer_blocks.11.attn.to_v", -"single_transformer_blocks.12", -"single_transformer_blocks.12.norm", -"single_transformer_blocks.12.norm.silu", -"single_transformer_blocks.12.norm.linear", -"single_transformer_blocks.12.norm.norm", -"single_transformer_blocks.12.proj_mlp", -"single_transformer_blocks.12.act_mlp", -"single_transformer_blocks.12.proj_out", -"single_transformer_blocks.12.attn", -"single_transformer_blocks.12.attn.norm_q", -"single_transformer_blocks.12.attn.norm_k", -"single_transformer_blocks.12.attn.to_q", -"single_transformer_blocks.12.attn.to_k", -"single_transformer_blocks.12.attn.to_v", -"single_transformer_blocks.13", -"single_transformer_blocks.13.norm", -"single_transformer_blocks.13.norm.silu", -"single_transformer_blocks.13.norm.linear", -"single_transformer_blocks.13.norm.norm", -"single_transformer_blocks.13.proj_mlp", -"single_transformer_blocks.13.act_mlp", -"single_transformer_blocks.13.proj_out", -"single_transformer_blocks.13.attn", -"single_transformer_blocks.13.attn.norm_q", -"single_transformer_blocks.13.attn.norm_k", -"single_transformer_blocks.13.attn.to_q", -"single_transformer_blocks.13.attn.to_k", -"single_transformer_blocks.13.attn.to_v", -"single_transformer_blocks.14", -"single_transformer_blocks.14.norm", -"single_transformer_blocks.14.norm.silu", -"single_transformer_blocks.14.norm.linear", -"single_transformer_blocks.14.norm.norm", -"single_transformer_blocks.14.proj_mlp", -"single_transformer_blocks.14.act_mlp", -"single_transformer_blocks.14.proj_out", -"single_transformer_blocks.14.attn", -"single_transformer_blocks.14.attn.norm_q", -"single_transformer_blocks.14.attn.norm_k", -"single_transformer_blocks.14.attn.to_q", -"single_transformer_blocks.14.attn.to_k", -"single_transformer_blocks.14.attn.to_v", -"single_transformer_blocks.15", -"single_transformer_blocks.15.norm", -"single_transformer_blocks.15.norm.silu", -"single_transformer_blocks.15.norm.linear", -"single_transformer_blocks.15.norm.norm", -"single_transformer_blocks.15.proj_mlp", -"single_transformer_blocks.15.act_mlp", -"single_transformer_blocks.15.proj_out", -"single_transformer_blocks.15.attn", -"single_transformer_blocks.15.attn.norm_q", -"single_transformer_blocks.15.attn.norm_k", -"single_transformer_blocks.15.attn.to_q", -"single_transformer_blocks.15.attn.to_k", -"single_transformer_blocks.15.attn.to_v", -"single_transformer_blocks.16", -"single_transformer_blocks.16.norm", -"single_transformer_blocks.16.norm.silu", -"single_transformer_blocks.16.norm.linear", -"single_transformer_blocks.16.norm.norm", -"single_transformer_blocks.16.proj_mlp", -"single_transformer_blocks.16.act_mlp", -"single_transformer_blocks.16.proj_out", -"single_transformer_blocks.16.attn", -"single_transformer_blocks.16.attn.norm_q", -"single_transformer_blocks.16.attn.norm_k", -"single_transformer_blocks.16.attn.to_q", -"single_transformer_blocks.16.attn.to_k", -"single_transformer_blocks.16.attn.to_v", -"single_transformer_blocks.17", -"single_transformer_blocks.17.norm", -"single_transformer_blocks.17.norm.silu", -"single_transformer_blocks.17.norm.linear", -"single_transformer_blocks.17.norm.norm", -"single_transformer_blocks.17.proj_mlp", -"single_transformer_blocks.17.act_mlp", -"single_transformer_blocks.17.proj_out", -"single_transformer_blocks.17.attn", -"single_transformer_blocks.17.attn.norm_q", -"single_transformer_blocks.17.attn.norm_k", -"single_transformer_blocks.17.attn.to_q", -"single_transformer_blocks.17.attn.to_k", -"single_transformer_blocks.17.attn.to_v", -"single_transformer_blocks.18", -"single_transformer_blocks.18.norm", -"single_transformer_blocks.18.norm.silu", -"single_transformer_blocks.18.norm.linear", -"single_transformer_blocks.18.norm.norm", -"single_transformer_blocks.18.proj_mlp", -"single_transformer_blocks.18.act_mlp", -"single_transformer_blocks.18.proj_out", -"single_transformer_blocks.18.attn", -"single_transformer_blocks.18.attn.norm_q", -"single_transformer_blocks.18.attn.norm_k", -"single_transformer_blocks.18.attn.to_q", -"single_transformer_blocks.18.attn.to_k", -"single_transformer_blocks.18.attn.to_v", -"single_transformer_blocks.19", -"single_transformer_blocks.19.norm", -"single_transformer_blocks.19.norm.silu", -"single_transformer_blocks.19.norm.linear", -"single_transformer_blocks.19.norm.norm", -"single_transformer_blocks.19.proj_mlp", -"single_transformer_blocks.19.act_mlp", -"single_transformer_blocks.19.proj_out", -"single_transformer_blocks.19.attn", -"single_transformer_blocks.19.attn.norm_q", -"single_transformer_blocks.19.attn.norm_k", -"single_transformer_blocks.19.attn.to_q", -"single_transformer_blocks.19.attn.to_k", -"single_transformer_blocks.19.attn.to_v", -"single_transformer_blocks.20", -"single_transformer_blocks.20.norm", -"single_transformer_blocks.20.norm.silu", -"single_transformer_blocks.20.norm.linear", -"single_transformer_blocks.20.norm.norm", -"single_transformer_blocks.20.proj_mlp", -"single_transformer_blocks.20.act_mlp", -"single_transformer_blocks.20.proj_out", -"single_transformer_blocks.20.attn", -"single_transformer_blocks.20.attn.norm_q", -"single_transformer_blocks.20.attn.norm_k", -"single_transformer_blocks.20.attn.to_q", -"single_transformer_blocks.20.attn.to_k", -"single_transformer_blocks.20.attn.to_v", -"single_transformer_blocks.21", -"single_transformer_blocks.21.norm", -"single_transformer_blocks.21.norm.silu", -"single_transformer_blocks.21.norm.linear", -"single_transformer_blocks.21.norm.norm", -"single_transformer_blocks.21.proj_mlp", -"single_transformer_blocks.21.act_mlp", -"single_transformer_blocks.21.proj_out", -"single_transformer_blocks.21.attn", -"single_transformer_blocks.21.attn.norm_q", -"single_transformer_blocks.21.attn.norm_k", -"single_transformer_blocks.21.attn.to_q", -"single_transformer_blocks.21.attn.to_k", -"single_transformer_blocks.21.attn.to_v", -"single_transformer_blocks.22", -"single_transformer_blocks.22.norm", -"single_transformer_blocks.22.norm.silu", -"single_transformer_blocks.22.norm.linear", -"single_transformer_blocks.22.norm.norm", -"single_transformer_blocks.22.proj_mlp", -"single_transformer_blocks.22.act_mlp", -"single_transformer_blocks.22.proj_out", -"single_transformer_blocks.22.attn", -"single_transformer_blocks.22.attn.norm_q", -"single_transformer_blocks.22.attn.norm_k", -"single_transformer_blocks.22.attn.to_q", -"single_transformer_blocks.22.attn.to_k", -"single_transformer_blocks.22.attn.to_v", -"single_transformer_blocks.23", -"single_transformer_blocks.23.norm", -"single_transformer_blocks.23.norm.silu", -"single_transformer_blocks.23.norm.linear", -"single_transformer_blocks.23.norm.norm", -"single_transformer_blocks.23.proj_mlp", -"single_transformer_blocks.23.act_mlp", -"single_transformer_blocks.23.proj_out", -"single_transformer_blocks.23.attn", -"single_transformer_blocks.23.attn.norm_q", -"single_transformer_blocks.23.attn.norm_k", -"single_transformer_blocks.23.attn.to_q", -"single_transformer_blocks.23.attn.to_k", -"single_transformer_blocks.23.attn.to_v", -"single_transformer_blocks.24", -"single_transformer_blocks.24.norm", -"single_transformer_blocks.24.norm.silu", -"single_transformer_blocks.24.norm.linear", -"single_transformer_blocks.24.norm.norm", -"single_transformer_blocks.24.proj_mlp", -"single_transformer_blocks.24.act_mlp", -"single_transformer_blocks.24.proj_out", -"single_transformer_blocks.24.attn", -"single_transformer_blocks.24.attn.norm_q", -"single_transformer_blocks.24.attn.norm_k", -"single_transformer_blocks.24.attn.to_q", -"single_transformer_blocks.24.attn.to_k", -"single_transformer_blocks.24.attn.to_v", -"single_transformer_blocks.25", -"single_transformer_blocks.25.norm", -"single_transformer_blocks.25.norm.silu", -"single_transformer_blocks.25.norm.linear", -"single_transformer_blocks.25.norm.norm", -"single_transformer_blocks.25.proj_mlp", -"single_transformer_blocks.25.act_mlp", -"single_transformer_blocks.25.proj_out", -"single_transformer_blocks.25.attn", -"single_transformer_blocks.25.attn.norm_q", -"single_transformer_blocks.25.attn.norm_k", -"single_transformer_blocks.25.attn.to_q", -"single_transformer_blocks.25.attn.to_k", -"single_transformer_blocks.25.attn.to_v", -"single_transformer_blocks.26", -"single_transformer_blocks.26.norm", -"single_transformer_blocks.26.norm.silu", -"single_transformer_blocks.26.norm.linear", -"single_transformer_blocks.26.norm.norm", -"single_transformer_blocks.26.proj_mlp", -"single_transformer_blocks.26.act_mlp", -"single_transformer_blocks.26.proj_out", -"single_transformer_blocks.26.attn", -"single_transformer_blocks.26.attn.norm_q", -"single_transformer_blocks.26.attn.norm_k", -"single_transformer_blocks.26.attn.to_q", -"single_transformer_blocks.26.attn.to_k", -"single_transformer_blocks.26.attn.to_v", -"single_transformer_blocks.27", -"single_transformer_blocks.27.norm", -"single_transformer_blocks.27.norm.silu", -"single_transformer_blocks.27.norm.linear", -"single_transformer_blocks.27.norm.norm", -"single_transformer_blocks.27.proj_mlp", -"single_transformer_blocks.27.act_mlp", -"single_transformer_blocks.27.proj_out", -"single_transformer_blocks.27.attn", -"single_transformer_blocks.27.attn.norm_q", -"single_transformer_blocks.27.attn.norm_k", -"single_transformer_blocks.27.attn.to_q", -"single_transformer_blocks.27.attn.to_k", -"single_transformer_blocks.27.attn.to_v", -"single_transformer_blocks.28", -"single_transformer_blocks.28.norm", -"single_transformer_blocks.28.norm.silu", -"single_transformer_blocks.28.norm.linear", -"single_transformer_blocks.28.norm.norm", -"single_transformer_blocks.28.proj_mlp", -"single_transformer_blocks.28.act_mlp", -"single_transformer_blocks.28.proj_out", -"single_transformer_blocks.28.attn", -"single_transformer_blocks.28.attn.norm_q", -"single_transformer_blocks.28.attn.norm_k", -"single_transformer_blocks.28.attn.to_q", -"single_transformer_blocks.28.attn.to_k", -"single_transformer_blocks.28.attn.to_v", -"single_transformer_blocks.29", -"single_transformer_blocks.29.norm", -"single_transformer_blocks.29.norm.silu", -"single_transformer_blocks.29.norm.linear", -"single_transformer_blocks.29.norm.norm", -"single_transformer_blocks.29.proj_mlp", -"single_transformer_blocks.29.act_mlp", -"single_transformer_blocks.29.proj_out", -"single_transformer_blocks.29.attn", -"single_transformer_blocks.29.attn.norm_q", -"single_transformer_blocks.29.attn.norm_k", -"single_transformer_blocks.29.attn.to_q", -"single_transformer_blocks.29.attn.to_k", -"single_transformer_blocks.29.attn.to_v", -"single_transformer_blocks.30", -"single_transformer_blocks.30.norm", -"single_transformer_blocks.30.norm.silu", -"single_transformer_blocks.30.norm.linear", -"single_transformer_blocks.30.norm.norm", -"single_transformer_blocks.30.proj_mlp", -"single_transformer_blocks.30.act_mlp", -"single_transformer_blocks.30.proj_out", -"single_transformer_blocks.30.attn", -"single_transformer_blocks.30.attn.norm_q", -"single_transformer_blocks.30.attn.norm_k", -"single_transformer_blocks.30.attn.to_q", -"single_transformer_blocks.30.attn.to_k", -"single_transformer_blocks.30.attn.to_v", -"single_transformer_blocks.31", -"single_transformer_blocks.31.norm", -"single_transformer_blocks.31.norm.silu", -"single_transformer_blocks.31.norm.linear", -"single_transformer_blocks.31.norm.norm", -"single_transformer_blocks.31.proj_mlp", -"single_transformer_blocks.31.act_mlp", -"single_transformer_blocks.31.proj_out", -"single_transformer_blocks.31.attn", -"single_transformer_blocks.31.attn.norm_q", -"single_transformer_blocks.31.attn.norm_k", -"single_transformer_blocks.31.attn.to_q", -"single_transformer_blocks.31.attn.to_k", -"single_transformer_blocks.31.attn.to_v", -"single_transformer_blocks.32", -"single_transformer_blocks.32.norm", -"single_transformer_blocks.32.norm.silu", -"single_transformer_blocks.32.norm.linear", -"single_transformer_blocks.32.norm.norm", -"single_transformer_blocks.32.proj_mlp", -"single_transformer_blocks.32.act_mlp", -"single_transformer_blocks.32.proj_out", -"single_transformer_blocks.32.attn", -"single_transformer_blocks.32.attn.norm_q", -"single_transformer_blocks.32.attn.norm_k", -"single_transformer_blocks.32.attn.to_q", -"single_transformer_blocks.32.attn.to_k", -"single_transformer_blocks.32.attn.to_v", -"single_transformer_blocks.33", -"single_transformer_blocks.33.norm", -"single_transformer_blocks.33.norm.silu", -"single_transformer_blocks.33.norm.linear", -"single_transformer_blocks.33.norm.norm", -"single_transformer_blocks.33.proj_mlp", -"single_transformer_blocks.33.act_mlp", -"single_transformer_blocks.33.proj_out", -"single_transformer_blocks.33.attn", -"single_transformer_blocks.33.attn.norm_q", -"single_transformer_blocks.33.attn.norm_k", -"single_transformer_blocks.33.attn.to_q", -"single_transformer_blocks.33.attn.to_k", -"single_transformer_blocks.33.attn.to_v", -"single_transformer_blocks.34", -"single_transformer_blocks.34.norm", -"single_transformer_blocks.34.norm.silu", -"single_transformer_blocks.34.norm.linear", -"single_transformer_blocks.34.norm.norm", -"single_transformer_blocks.34.proj_mlp", -"single_transformer_blocks.34.act_mlp", -"single_transformer_blocks.34.proj_out", -"single_transformer_blocks.34.attn", -"single_transformer_blocks.34.attn.norm_q", -"single_transformer_blocks.34.attn.norm_k", -"single_transformer_blocks.34.attn.to_q", -"single_transformer_blocks.34.attn.to_k", -"single_transformer_blocks.34.attn.to_v", -"single_transformer_blocks.35", -"single_transformer_blocks.35.norm", -"single_transformer_blocks.35.norm.silu", -"single_transformer_blocks.35.norm.linear", -"single_transformer_blocks.35.norm.norm", -"single_transformer_blocks.35.proj_mlp", -"single_transformer_blocks.35.act_mlp", -"single_transformer_blocks.35.proj_out", -"single_transformer_blocks.35.attn", -"single_transformer_blocks.35.attn.norm_q", -"single_transformer_blocks.35.attn.norm_k", -"single_transformer_blocks.35.attn.to_q", -"single_transformer_blocks.35.attn.to_k", -"single_transformer_blocks.35.attn.to_v", -"single_transformer_blocks.36", -"single_transformer_blocks.36.norm", -"single_transformer_blocks.36.norm.silu", -"single_transformer_blocks.36.norm.linear", -"single_transformer_blocks.36.norm.norm", -"single_transformer_blocks.36.proj_mlp", -"single_transformer_blocks.36.act_mlp", -"single_transformer_blocks.36.proj_out", -"single_transformer_blocks.36.attn", -"single_transformer_blocks.36.attn.norm_q", -"single_transformer_blocks.36.attn.norm_k", -"single_transformer_blocks.36.attn.to_q", -"single_transformer_blocks.36.attn.to_k", -"single_transformer_blocks.36.attn.to_v", -"single_transformer_blocks.37", -"single_transformer_blocks.37.norm", -"single_transformer_blocks.37.norm.silu", -"single_transformer_blocks.37.norm.linear", -"single_transformer_blocks.37.norm.norm", -"single_transformer_blocks.37.proj_mlp", -"single_transformer_blocks.37.act_mlp", -"single_transformer_blocks.37.proj_out", -"single_transformer_blocks.37.attn", -"single_transformer_blocks.37.attn.norm_q", -"single_transformer_blocks.37.attn.norm_k", -"single_transformer_blocks.37.attn.to_q", -"single_transformer_blocks.37.attn.to_k", -"single_transformer_blocks.37.attn.to_v", -"norm_out", -"norm_out.silu", -"norm_out.linear", -"norm_out.norm", -"proj_out"] diff --git a/modules/Experiments/main.py b/modules/Experiments/main.py deleted file mode 100644 index 4a6bdc1..0000000 --- a/modules/Experiments/main.py +++ /dev/null @@ -1,4 +0,0 @@ -from .StableDiffusion3 import * -from .StableDiffusionXL import * -from .VAE import * -from .FLUXKontext import * diff --git a/modules/Experiments/sd3_layers.py b/modules/Experiments/sd3_layers.py deleted file mode 100644 index 85689c1..0000000 --- a/modules/Experiments/sd3_layers.py +++ /dev/null @@ -1,1456 +0,0 @@ -SD3_LAYERS = [ -'pos_embed', -'pos_embed.proj', -'time_text_embed', -'time_text_embed.time_proj', -'time_text_embed.timestep_embedder', -'time_text_embed.timestep_embedder.linear_1', -'time_text_embed.timestep_embedder.act', -'time_text_embed.timestep_embedder.linear_2', -'time_text_embed.text_embedder', -'time_text_embed.text_embedder.linear_1', -'time_text_embed.text_embedder.act_1', -'time_text_embed.text_embedder.linear_2', -'context_embedder', -'transformer_blocks', -'transformer_blocks.0', -'transformer_blocks.0.norm1', -'transformer_blocks.0.norm1.silu', -'transformer_blocks.0.norm1.linear', -'transformer_blocks.0.norm1.norm', -'transformer_blocks.0.norm1_context', -'transformer_blocks.0.norm1_context.silu', -'transformer_blocks.0.norm1_context.linear', -'transformer_blocks.0.norm1_context.norm', -'transformer_blocks.0.attn', -'transformer_blocks.0.attn.norm_q', -'transformer_blocks.0.attn.norm_k', -'transformer_blocks.0.attn.to_q', -'transformer_blocks.0.attn.to_k', -'transformer_blocks.0.attn.to_v', -'transformer_blocks.0.attn.add_k_proj', -'transformer_blocks.0.attn.add_v_proj', -'transformer_blocks.0.attn.add_q_proj', -'transformer_blocks.0.attn.to_out', -'transformer_blocks.0.attn.to_out.0', -'transformer_blocks.0.attn.to_out.1', -'transformer_blocks.0.attn.to_add_out', -'transformer_blocks.0.attn.norm_added_q', -'transformer_blocks.0.attn.norm_added_k', -'transformer_blocks.0.norm2', -'transformer_blocks.0.ff', -'transformer_blocks.0.ff.net', -'transformer_blocks.0.ff.net.0', -'transformer_blocks.0.ff.net.0.proj', -'transformer_blocks.0.ff.net.1', -'transformer_blocks.0.ff.net.2', -'transformer_blocks.0.norm2_context', -'transformer_blocks.0.ff_context', -'transformer_blocks.0.ff_context.net', -'transformer_blocks.0.ff_context.net.0', -'transformer_blocks.0.ff_context.net.0.proj', -'transformer_blocks.0.ff_context.net.1', -'transformer_blocks.0.ff_context.net.2', -'transformer_blocks.1', -'transformer_blocks.1.norm1', -'transformer_blocks.1.norm1.silu', -'transformer_blocks.1.norm1.linear', -'transformer_blocks.1.norm1.norm', -'transformer_blocks.1.norm1_context', -'transformer_blocks.1.norm1_context.silu', -'transformer_blocks.1.norm1_context.linear', -'transformer_blocks.1.norm1_context.norm', -'transformer_blocks.1.attn', -'transformer_blocks.1.attn.norm_q', -'transformer_blocks.1.attn.norm_k', -'transformer_blocks.1.attn.to_q', -'transformer_blocks.1.attn.to_k', -'transformer_blocks.1.attn.to_v', -'transformer_blocks.1.attn.add_k_proj', -'transformer_blocks.1.attn.add_v_proj', -'transformer_blocks.1.attn.add_q_proj', -'transformer_blocks.1.attn.to_out', -'transformer_blocks.1.attn.to_out.0', -'transformer_blocks.1.attn.to_out.1', -'transformer_blocks.1.attn.to_add_out', -'transformer_blocks.1.attn.norm_added_q', -'transformer_blocks.1.attn.norm_added_k', -'transformer_blocks.1.norm2', -'transformer_blocks.1.ff', -'transformer_blocks.1.ff.net', -'transformer_blocks.1.ff.net.0', -'transformer_blocks.1.ff.net.0.proj', -'transformer_blocks.1.ff.net.1', -'transformer_blocks.1.ff.net.2', -'transformer_blocks.1.norm2_context', -'transformer_blocks.1.ff_context', -'transformer_blocks.1.ff_context.net', -'transformer_blocks.1.ff_context.net.0', -'transformer_blocks.1.ff_context.net.0.proj', -'transformer_blocks.1.ff_context.net.1', -'transformer_blocks.1.ff_context.net.2', -'transformer_blocks.2', -'transformer_blocks.2.norm1', -'transformer_blocks.2.norm1.silu', -'transformer_blocks.2.norm1.linear', -'transformer_blocks.2.norm1.norm', -'transformer_blocks.2.norm1_context', -'transformer_blocks.2.norm1_context.silu', -'transformer_blocks.2.norm1_context.linear', -'transformer_blocks.2.norm1_context.norm', -'transformer_blocks.2.attn', -'transformer_blocks.2.attn.norm_q', -'transformer_blocks.2.attn.norm_k', -'transformer_blocks.2.attn.to_q', -'transformer_blocks.2.attn.to_k', -'transformer_blocks.2.attn.to_v', -'transformer_blocks.2.attn.add_k_proj', -'transformer_blocks.2.attn.add_v_proj', -'transformer_blocks.2.attn.add_q_proj', -'transformer_blocks.2.attn.to_out', -'transformer_blocks.2.attn.to_out.0', -'transformer_blocks.2.attn.to_out.1', -'transformer_blocks.2.attn.to_add_out', -'transformer_blocks.2.attn.norm_added_q', -'transformer_blocks.2.attn.norm_added_k', -'transformer_blocks.2.norm2', -'transformer_blocks.2.ff', -'transformer_blocks.2.ff.net', -'transformer_blocks.2.ff.net.0', -'transformer_blocks.2.ff.net.0.proj', -'transformer_blocks.2.ff.net.1', -'transformer_blocks.2.ff.net.2', -'transformer_blocks.2.norm2_context', -'transformer_blocks.2.ff_context', -'transformer_blocks.2.ff_context.net', -'transformer_blocks.2.ff_context.net.0', -'transformer_blocks.2.ff_context.net.0.proj', -'transformer_blocks.2.ff_context.net.1', -'transformer_blocks.2.ff_context.net.2', -'transformer_blocks.3', -'transformer_blocks.3.norm1', -'transformer_blocks.3.norm1.silu', -'transformer_blocks.3.norm1.linear', -'transformer_blocks.3.norm1.norm', -'transformer_blocks.3.norm1_context', -'transformer_blocks.3.norm1_context.silu', -'transformer_blocks.3.norm1_context.linear', -'transformer_blocks.3.norm1_context.norm', -'transformer_blocks.3.attn', -'transformer_blocks.3.attn.norm_q', -'transformer_blocks.3.attn.norm_k', -'transformer_blocks.3.attn.to_q', -'transformer_blocks.3.attn.to_k', -'transformer_blocks.3.attn.to_v', -'transformer_blocks.3.attn.add_k_proj', -'transformer_blocks.3.attn.add_v_proj', -'transformer_blocks.3.attn.add_q_proj', -'transformer_blocks.3.attn.to_out', -'transformer_blocks.3.attn.to_out.0', -'transformer_blocks.3.attn.to_out.1', -'transformer_blocks.3.attn.to_add_out', -'transformer_blocks.3.attn.norm_added_q', -'transformer_blocks.3.attn.norm_added_k', -'transformer_blocks.3.norm2', -'transformer_blocks.3.ff', -'transformer_blocks.3.ff.net', -'transformer_blocks.3.ff.net.0', -'transformer_blocks.3.ff.net.0.proj', -'transformer_blocks.3.ff.net.1', -'transformer_blocks.3.ff.net.2', -'transformer_blocks.3.norm2_context', -'transformer_blocks.3.ff_context', -'transformer_blocks.3.ff_context.net', -'transformer_blocks.3.ff_context.net.0', -'transformer_blocks.3.ff_context.net.0.proj', -'transformer_blocks.3.ff_context.net.1', -'transformer_blocks.3.ff_context.net.2', -'transformer_blocks.4', -'transformer_blocks.4.norm1', -'transformer_blocks.4.norm1.silu', -'transformer_blocks.4.norm1.linear', -'transformer_blocks.4.norm1.norm', -'transformer_blocks.4.norm1_context', -'transformer_blocks.4.norm1_context.silu', -'transformer_blocks.4.norm1_context.linear', -'transformer_blocks.4.norm1_context.norm', -'transformer_blocks.4.attn', -'transformer_blocks.4.attn.norm_q', -'transformer_blocks.4.attn.norm_k', -'transformer_blocks.4.attn.to_q', -'transformer_blocks.4.attn.to_k', -'transformer_blocks.4.attn.to_v', -'transformer_blocks.4.attn.add_k_proj', -'transformer_blocks.4.attn.add_v_proj', -'transformer_blocks.4.attn.add_q_proj', -'transformer_blocks.4.attn.to_out', -'transformer_blocks.4.attn.to_out.0', -'transformer_blocks.4.attn.to_out.1', -'transformer_blocks.4.attn.to_add_out', -'transformer_blocks.4.attn.norm_added_q', -'transformer_blocks.4.attn.norm_added_k', -'transformer_blocks.4.norm2', -'transformer_blocks.4.ff', -'transformer_blocks.4.ff.net', -'transformer_blocks.4.ff.net.0', -'transformer_blocks.4.ff.net.0.proj', -'transformer_blocks.4.ff.net.1', -'transformer_blocks.4.ff.net.2', -'transformer_blocks.4.norm2_context', -'transformer_blocks.4.ff_context', -'transformer_blocks.4.ff_context.net', -'transformer_blocks.4.ff_context.net.0', -'transformer_blocks.4.ff_context.net.0.proj', -'transformer_blocks.4.ff_context.net.1', -'transformer_blocks.4.ff_context.net.2', -'transformer_blocks.5', -'transformer_blocks.5.norm1', -'transformer_blocks.5.norm1.silu', -'transformer_blocks.5.norm1.linear', -'transformer_blocks.5.norm1.norm', -'transformer_blocks.5.norm1_context', -'transformer_blocks.5.norm1_context.silu', -'transformer_blocks.5.norm1_context.linear', -'transformer_blocks.5.norm1_context.norm', -'transformer_blocks.5.attn', -'transformer_blocks.5.attn.norm_q', -'transformer_blocks.5.attn.norm_k', -'transformer_blocks.5.attn.to_q', -'transformer_blocks.5.attn.to_k', -'transformer_blocks.5.attn.to_v', -'transformer_blocks.5.attn.add_k_proj', -'transformer_blocks.5.attn.add_v_proj', -'transformer_blocks.5.attn.add_q_proj', -'transformer_blocks.5.attn.to_out', -'transformer_blocks.5.attn.to_out.0', -'transformer_blocks.5.attn.to_out.1', -'transformer_blocks.5.attn.to_add_out', -'transformer_blocks.5.attn.norm_added_q', -'transformer_blocks.5.attn.norm_added_k', -'transformer_blocks.5.norm2', -'transformer_blocks.5.ff', -'transformer_blocks.5.ff.net', -'transformer_blocks.5.ff.net.0', -'transformer_blocks.5.ff.net.0.proj', -'transformer_blocks.5.ff.net.1', -'transformer_blocks.5.ff.net.2', -'transformer_blocks.5.norm2_context', -'transformer_blocks.5.ff_context', -'transformer_blocks.5.ff_context.net', -'transformer_blocks.5.ff_context.net.0', -'transformer_blocks.5.ff_context.net.0.proj', -'transformer_blocks.5.ff_context.net.1', -'transformer_blocks.5.ff_context.net.2', -'transformer_blocks.6', -'transformer_blocks.6.norm1', -'transformer_blocks.6.norm1.silu', -'transformer_blocks.6.norm1.linear', -'transformer_blocks.6.norm1.norm', -'transformer_blocks.6.norm1_context', -'transformer_blocks.6.norm1_context.silu', -'transformer_blocks.6.norm1_context.linear', -'transformer_blocks.6.norm1_context.norm', -'transformer_blocks.6.attn', -'transformer_blocks.6.attn.norm_q', -'transformer_blocks.6.attn.norm_k', -'transformer_blocks.6.attn.to_q', -'transformer_blocks.6.attn.to_k', -'transformer_blocks.6.attn.to_v', -'transformer_blocks.6.attn.add_k_proj', -'transformer_blocks.6.attn.add_v_proj', -'transformer_blocks.6.attn.add_q_proj', -'transformer_blocks.6.attn.to_out', -'transformer_blocks.6.attn.to_out.0', -'transformer_blocks.6.attn.to_out.1', -'transformer_blocks.6.attn.to_add_out', -'transformer_blocks.6.attn.norm_added_q', -'transformer_blocks.6.attn.norm_added_k', -'transformer_blocks.6.norm2', -'transformer_blocks.6.ff', -'transformer_blocks.6.ff.net', -'transformer_blocks.6.ff.net.0', -'transformer_blocks.6.ff.net.0.proj', -'transformer_blocks.6.ff.net.1', -'transformer_blocks.6.ff.net.2', -'transformer_blocks.6.norm2_context', -'transformer_blocks.6.ff_context', -'transformer_blocks.6.ff_context.net', -'transformer_blocks.6.ff_context.net.0', -'transformer_blocks.6.ff_context.net.0.proj', -'transformer_blocks.6.ff_context.net.1', -'transformer_blocks.6.ff_context.net.2', -'transformer_blocks.7', -'transformer_blocks.7.norm1', -'transformer_blocks.7.norm1.silu', -'transformer_blocks.7.norm1.linear', -'transformer_blocks.7.norm1.norm', -'transformer_blocks.7.norm1_context', -'transformer_blocks.7.norm1_context.silu', -'transformer_blocks.7.norm1_context.linear', -'transformer_blocks.7.norm1_context.norm', -'transformer_blocks.7.attn', -'transformer_blocks.7.attn.norm_q', -'transformer_blocks.7.attn.norm_k', -'transformer_blocks.7.attn.to_q', -'transformer_blocks.7.attn.to_k', -'transformer_blocks.7.attn.to_v', -'transformer_blocks.7.attn.add_k_proj', -'transformer_blocks.7.attn.add_v_proj', -'transformer_blocks.7.attn.add_q_proj', -'transformer_blocks.7.attn.to_out', -'transformer_blocks.7.attn.to_out.0', -'transformer_blocks.7.attn.to_out.1', -'transformer_blocks.7.attn.to_add_out', -'transformer_blocks.7.attn.norm_added_q', -'transformer_blocks.7.attn.norm_added_k', -'transformer_blocks.7.norm2', -'transformer_blocks.7.ff', -'transformer_blocks.7.ff.net', -'transformer_blocks.7.ff.net.0', -'transformer_blocks.7.ff.net.0.proj', -'transformer_blocks.7.ff.net.1', -'transformer_blocks.7.ff.net.2', -'transformer_blocks.7.norm2_context', -'transformer_blocks.7.ff_context', -'transformer_blocks.7.ff_context.net', -'transformer_blocks.7.ff_context.net.0', -'transformer_blocks.7.ff_context.net.0.proj', -'transformer_blocks.7.ff_context.net.1', -'transformer_blocks.7.ff_context.net.2', -'transformer_blocks.8', -'transformer_blocks.8.norm1', -'transformer_blocks.8.norm1.silu', -'transformer_blocks.8.norm1.linear', -'transformer_blocks.8.norm1.norm', -'transformer_blocks.8.norm1_context', -'transformer_blocks.8.norm1_context.silu', -'transformer_blocks.8.norm1_context.linear', -'transformer_blocks.8.norm1_context.norm', -'transformer_blocks.8.attn', -'transformer_blocks.8.attn.norm_q', -'transformer_blocks.8.attn.norm_k', -'transformer_blocks.8.attn.to_q', -'transformer_blocks.8.attn.to_k', -'transformer_blocks.8.attn.to_v', -'transformer_blocks.8.attn.add_k_proj', -'transformer_blocks.8.attn.add_v_proj', -'transformer_blocks.8.attn.add_q_proj', -'transformer_blocks.8.attn.to_out', -'transformer_blocks.8.attn.to_out.0', -'transformer_blocks.8.attn.to_out.1', -'transformer_blocks.8.attn.to_add_out', -'transformer_blocks.8.attn.norm_added_q', -'transformer_blocks.8.attn.norm_added_k', -'transformer_blocks.8.norm2', -'transformer_blocks.8.ff', -'transformer_blocks.8.ff.net', -'transformer_blocks.8.ff.net.0', -'transformer_blocks.8.ff.net.0.proj', -'transformer_blocks.8.ff.net.1', -'transformer_blocks.8.ff.net.2', -'transformer_blocks.8.norm2_context', -'transformer_blocks.8.ff_context', -'transformer_blocks.8.ff_context.net', -'transformer_blocks.8.ff_context.net.0', -'transformer_blocks.8.ff_context.net.0.proj', -'transformer_blocks.8.ff_context.net.1', -'transformer_blocks.8.ff_context.net.2', -'transformer_blocks.9', -'transformer_blocks.9.norm1', -'transformer_blocks.9.norm1.silu', -'transformer_blocks.9.norm1.linear', -'transformer_blocks.9.norm1.norm', -'transformer_blocks.9.norm1_context', -'transformer_blocks.9.norm1_context.silu', -'transformer_blocks.9.norm1_context.linear', -'transformer_blocks.9.norm1_context.norm', -'transformer_blocks.9.attn', -'transformer_blocks.9.attn.norm_q', -'transformer_blocks.9.attn.norm_k', -'transformer_blocks.9.attn.to_q', -'transformer_blocks.9.attn.to_k', -'transformer_blocks.9.attn.to_v', -'transformer_blocks.9.attn.add_k_proj', -'transformer_blocks.9.attn.add_v_proj', -'transformer_blocks.9.attn.add_q_proj', -'transformer_blocks.9.attn.to_out', -'transformer_blocks.9.attn.to_out.0', -'transformer_blocks.9.attn.to_out.1', -'transformer_blocks.9.attn.to_add_out', -'transformer_blocks.9.attn.norm_added_q', -'transformer_blocks.9.attn.norm_added_k', -'transformer_blocks.9.norm2', -'transformer_blocks.9.ff', -'transformer_blocks.9.ff.net', -'transformer_blocks.9.ff.net.0', -'transformer_blocks.9.ff.net.0.proj', -'transformer_blocks.9.ff.net.1', -'transformer_blocks.9.ff.net.2', -'transformer_blocks.9.norm2_context', -'transformer_blocks.9.ff_context', -'transformer_blocks.9.ff_context.net', -'transformer_blocks.9.ff_context.net.0', -'transformer_blocks.9.ff_context.net.0.proj', -'transformer_blocks.9.ff_context.net.1', -'transformer_blocks.9.ff_context.net.2', -'transformer_blocks.10', -'transformer_blocks.10.norm1', -'transformer_blocks.10.norm1.silu', -'transformer_blocks.10.norm1.linear', -'transformer_blocks.10.norm1.norm', -'transformer_blocks.10.norm1_context', -'transformer_blocks.10.norm1_context.silu', -'transformer_blocks.10.norm1_context.linear', -'transformer_blocks.10.norm1_context.norm', -'transformer_blocks.10.attn', -'transformer_blocks.10.attn.norm_q', -'transformer_blocks.10.attn.norm_k', -'transformer_blocks.10.attn.to_q', -'transformer_blocks.10.attn.to_k', -'transformer_blocks.10.attn.to_v', -'transformer_blocks.10.attn.add_k_proj', -'transformer_blocks.10.attn.add_v_proj', -'transformer_blocks.10.attn.add_q_proj', -'transformer_blocks.10.attn.to_out', -'transformer_blocks.10.attn.to_out.0', -'transformer_blocks.10.attn.to_out.1', -'transformer_blocks.10.attn.to_add_out', -'transformer_blocks.10.attn.norm_added_q', -'transformer_blocks.10.attn.norm_added_k', -'transformer_blocks.10.norm2', -'transformer_blocks.10.ff', -'transformer_blocks.10.ff.net', -'transformer_blocks.10.ff.net.0', -'transformer_blocks.10.ff.net.0.proj', -'transformer_blocks.10.ff.net.1', -'transformer_blocks.10.ff.net.2', -'transformer_blocks.10.norm2_context', -'transformer_blocks.10.ff_context', -'transformer_blocks.10.ff_context.net', -'transformer_blocks.10.ff_context.net.0', -'transformer_blocks.10.ff_context.net.0.proj', -'transformer_blocks.10.ff_context.net.1', -'transformer_blocks.10.ff_context.net.2', -'transformer_blocks.11', -'transformer_blocks.11.norm1', -'transformer_blocks.11.norm1.silu', -'transformer_blocks.11.norm1.linear', -'transformer_blocks.11.norm1.norm', -'transformer_blocks.11.norm1_context', -'transformer_blocks.11.norm1_context.silu', -'transformer_blocks.11.norm1_context.linear', -'transformer_blocks.11.norm1_context.norm', -'transformer_blocks.11.attn', -'transformer_blocks.11.attn.norm_q', -'transformer_blocks.11.attn.norm_k', -'transformer_blocks.11.attn.to_q', -'transformer_blocks.11.attn.to_k', -'transformer_blocks.11.attn.to_v', -'transformer_blocks.11.attn.add_k_proj', -'transformer_blocks.11.attn.add_v_proj', -'transformer_blocks.11.attn.add_q_proj', -'transformer_blocks.11.attn.to_out', -'transformer_blocks.11.attn.to_out.0', -'transformer_blocks.11.attn.to_out.1', -'transformer_blocks.11.attn.to_add_out', -'transformer_blocks.11.attn.norm_added_q', -'transformer_blocks.11.attn.norm_added_k', -'transformer_blocks.11.norm2', -'transformer_blocks.11.ff', -'transformer_blocks.11.ff.net', -'transformer_blocks.11.ff.net.0', -'transformer_blocks.11.ff.net.0.proj', -'transformer_blocks.11.ff.net.1', -'transformer_blocks.11.ff.net.2', -'transformer_blocks.11.norm2_context', -'transformer_blocks.11.ff_context', -'transformer_blocks.11.ff_context.net', -'transformer_blocks.11.ff_context.net.0', -'transformer_blocks.11.ff_context.net.0.proj', -'transformer_blocks.11.ff_context.net.1', -'transformer_blocks.11.ff_context.net.2', -'transformer_blocks.12', -'transformer_blocks.12.norm1', -'transformer_blocks.12.norm1.silu', -'transformer_blocks.12.norm1.linear', -'transformer_blocks.12.norm1.norm', -'transformer_blocks.12.norm1_context', -'transformer_blocks.12.norm1_context.silu', -'transformer_blocks.12.norm1_context.linear', -'transformer_blocks.12.norm1_context.norm', -'transformer_blocks.12.attn', -'transformer_blocks.12.attn.norm_q', -'transformer_blocks.12.attn.norm_k', -'transformer_blocks.12.attn.to_q', -'transformer_blocks.12.attn.to_k', -'transformer_blocks.12.attn.to_v', -'transformer_blocks.12.attn.add_k_proj', -'transformer_blocks.12.attn.add_v_proj', -'transformer_blocks.12.attn.add_q_proj', -'transformer_blocks.12.attn.to_out', -'transformer_blocks.12.attn.to_out.0', -'transformer_blocks.12.attn.to_out.1', -'transformer_blocks.12.attn.to_add_out', -'transformer_blocks.12.attn.norm_added_q', -'transformer_blocks.12.attn.norm_added_k', -'transformer_blocks.12.norm2', -'transformer_blocks.12.ff', -'transformer_blocks.12.ff.net', -'transformer_blocks.12.ff.net.0', -'transformer_blocks.12.ff.net.0.proj', -'transformer_blocks.12.ff.net.1', -'transformer_blocks.12.ff.net.2', -'transformer_blocks.12.norm2_context', -'transformer_blocks.12.ff_context', -'transformer_blocks.12.ff_context.net', -'transformer_blocks.12.ff_context.net.0', -'transformer_blocks.12.ff_context.net.0.proj', -'transformer_blocks.12.ff_context.net.1', -'transformer_blocks.12.ff_context.net.2', -'transformer_blocks.13', -'transformer_blocks.13.norm1', -'transformer_blocks.13.norm1.silu', -'transformer_blocks.13.norm1.linear', -'transformer_blocks.13.norm1.norm', -'transformer_blocks.13.norm1_context', -'transformer_blocks.13.norm1_context.silu', -'transformer_blocks.13.norm1_context.linear', -'transformer_blocks.13.norm1_context.norm', -'transformer_blocks.13.attn', -'transformer_blocks.13.attn.norm_q', -'transformer_blocks.13.attn.norm_k', -'transformer_blocks.13.attn.to_q', -'transformer_blocks.13.attn.to_k', -'transformer_blocks.13.attn.to_v', -'transformer_blocks.13.attn.add_k_proj', -'transformer_blocks.13.attn.add_v_proj', -'transformer_blocks.13.attn.add_q_proj', -'transformer_blocks.13.attn.to_out', -'transformer_blocks.13.attn.to_out.0', -'transformer_blocks.13.attn.to_out.1', -'transformer_blocks.13.attn.to_add_out', -'transformer_blocks.13.attn.norm_added_q', -'transformer_blocks.13.attn.norm_added_k', -'transformer_blocks.13.norm2', -'transformer_blocks.13.ff', -'transformer_blocks.13.ff.net', -'transformer_blocks.13.ff.net.0', -'transformer_blocks.13.ff.net.0.proj', -'transformer_blocks.13.ff.net.1', -'transformer_blocks.13.ff.net.2', -'transformer_blocks.13.norm2_context', -'transformer_blocks.13.ff_context', -'transformer_blocks.13.ff_context.net', -'transformer_blocks.13.ff_context.net.0', -'transformer_blocks.13.ff_context.net.0.proj', -'transformer_blocks.13.ff_context.net.1', -'transformer_blocks.13.ff_context.net.2', -'transformer_blocks.14', -'transformer_blocks.14.norm1', -'transformer_blocks.14.norm1.silu', -'transformer_blocks.14.norm1.linear', -'transformer_blocks.14.norm1.norm', -'transformer_blocks.14.norm1_context', -'transformer_blocks.14.norm1_context.silu', -'transformer_blocks.14.norm1_context.linear', -'transformer_blocks.14.norm1_context.norm', -'transformer_blocks.14.attn', -'transformer_blocks.14.attn.norm_q', -'transformer_blocks.14.attn.norm_k', -'transformer_blocks.14.attn.to_q', -'transformer_blocks.14.attn.to_k', -'transformer_blocks.14.attn.to_v', -'transformer_blocks.14.attn.add_k_proj', -'transformer_blocks.14.attn.add_v_proj', -'transformer_blocks.14.attn.add_q_proj', -'transformer_blocks.14.attn.to_out', -'transformer_blocks.14.attn.to_out.0', -'transformer_blocks.14.attn.to_out.1', -'transformer_blocks.14.attn.to_add_out', -'transformer_blocks.14.attn.norm_added_q', -'transformer_blocks.14.attn.norm_added_k', -'transformer_blocks.14.norm2', -'transformer_blocks.14.ff', -'transformer_blocks.14.ff.net', -'transformer_blocks.14.ff.net.0', -'transformer_blocks.14.ff.net.0.proj', -'transformer_blocks.14.ff.net.1', -'transformer_blocks.14.ff.net.2', -'transformer_blocks.14.norm2_context', -'transformer_blocks.14.ff_context', -'transformer_blocks.14.ff_context.net', -'transformer_blocks.14.ff_context.net.0', -'transformer_blocks.14.ff_context.net.0.proj', -'transformer_blocks.14.ff_context.net.1', -'transformer_blocks.14.ff_context.net.2', -'transformer_blocks.15', -'transformer_blocks.15.norm1', -'transformer_blocks.15.norm1.silu', -'transformer_blocks.15.norm1.linear', -'transformer_blocks.15.norm1.norm', -'transformer_blocks.15.norm1_context', -'transformer_blocks.15.norm1_context.silu', -'transformer_blocks.15.norm1_context.linear', -'transformer_blocks.15.norm1_context.norm', -'transformer_blocks.15.attn', -'transformer_blocks.15.attn.norm_q', -'transformer_blocks.15.attn.norm_k', -'transformer_blocks.15.attn.to_q', -'transformer_blocks.15.attn.to_k', -'transformer_blocks.15.attn.to_v', -'transformer_blocks.15.attn.add_k_proj', -'transformer_blocks.15.attn.add_v_proj', -'transformer_blocks.15.attn.add_q_proj', -'transformer_blocks.15.attn.to_out', -'transformer_blocks.15.attn.to_out.0', -'transformer_blocks.15.attn.to_out.1', -'transformer_blocks.15.attn.to_add_out', -'transformer_blocks.15.attn.norm_added_q', -'transformer_blocks.15.attn.norm_added_k', -'transformer_blocks.15.norm2', -'transformer_blocks.15.ff', -'transformer_blocks.15.ff.net', -'transformer_blocks.15.ff.net.0', -'transformer_blocks.15.ff.net.0.proj', -'transformer_blocks.15.ff.net.1', -'transformer_blocks.15.ff.net.2', -'transformer_blocks.15.norm2_context', -'transformer_blocks.15.ff_context', -'transformer_blocks.15.ff_context.net', -'transformer_blocks.15.ff_context.net.0', -'transformer_blocks.15.ff_context.net.0.proj', -'transformer_blocks.15.ff_context.net.1', -'transformer_blocks.15.ff_context.net.2', -'transformer_blocks.16', -'transformer_blocks.16.norm1', -'transformer_blocks.16.norm1.silu', -'transformer_blocks.16.norm1.linear', -'transformer_blocks.16.norm1.norm', -'transformer_blocks.16.norm1_context', -'transformer_blocks.16.norm1_context.silu', -'transformer_blocks.16.norm1_context.linear', -'transformer_blocks.16.norm1_context.norm', -'transformer_blocks.16.attn', -'transformer_blocks.16.attn.norm_q', -'transformer_blocks.16.attn.norm_k', -'transformer_blocks.16.attn.to_q', -'transformer_blocks.16.attn.to_k', -'transformer_blocks.16.attn.to_v', -'transformer_blocks.16.attn.add_k_proj', -'transformer_blocks.16.attn.add_v_proj', -'transformer_blocks.16.attn.add_q_proj', -'transformer_blocks.16.attn.to_out', -'transformer_blocks.16.attn.to_out.0', -'transformer_blocks.16.attn.to_out.1', -'transformer_blocks.16.attn.to_add_out', -'transformer_blocks.16.attn.norm_added_q', -'transformer_blocks.16.attn.norm_added_k', -'transformer_blocks.16.norm2', -'transformer_blocks.16.ff', -'transformer_blocks.16.ff.net', -'transformer_blocks.16.ff.net.0', -'transformer_blocks.16.ff.net.0.proj', -'transformer_blocks.16.ff.net.1', -'transformer_blocks.16.ff.net.2', -'transformer_blocks.16.norm2_context', -'transformer_blocks.16.ff_context', -'transformer_blocks.16.ff_context.net', -'transformer_blocks.16.ff_context.net.0', -'transformer_blocks.16.ff_context.net.0.proj', -'transformer_blocks.16.ff_context.net.1', -'transformer_blocks.16.ff_context.net.2', -'transformer_blocks.17', -'transformer_blocks.17.norm1', -'transformer_blocks.17.norm1.silu', -'transformer_blocks.17.norm1.linear', -'transformer_blocks.17.norm1.norm', -'transformer_blocks.17.norm1_context', -'transformer_blocks.17.norm1_context.silu', -'transformer_blocks.17.norm1_context.linear', -'transformer_blocks.17.norm1_context.norm', -'transformer_blocks.17.attn', -'transformer_blocks.17.attn.norm_q', -'transformer_blocks.17.attn.norm_k', -'transformer_blocks.17.attn.to_q', -'transformer_blocks.17.attn.to_k', -'transformer_blocks.17.attn.to_v', -'transformer_blocks.17.attn.add_k_proj', -'transformer_blocks.17.attn.add_v_proj', -'transformer_blocks.17.attn.add_q_proj', -'transformer_blocks.17.attn.to_out', -'transformer_blocks.17.attn.to_out.0', -'transformer_blocks.17.attn.to_out.1', -'transformer_blocks.17.attn.to_add_out', -'transformer_blocks.17.attn.norm_added_q', -'transformer_blocks.17.attn.norm_added_k', -'transformer_blocks.17.norm2', -'transformer_blocks.17.ff', -'transformer_blocks.17.ff.net', -'transformer_blocks.17.ff.net.0', -'transformer_blocks.17.ff.net.0.proj', -'transformer_blocks.17.ff.net.1', -'transformer_blocks.17.ff.net.2', -'transformer_blocks.17.norm2_context', -'transformer_blocks.17.ff_context', -'transformer_blocks.17.ff_context.net', -'transformer_blocks.17.ff_context.net.0', -'transformer_blocks.17.ff_context.net.0.proj', -'transformer_blocks.17.ff_context.net.1', -'transformer_blocks.17.ff_context.net.2', -'transformer_blocks.18', -'transformer_blocks.18.norm1', -'transformer_blocks.18.norm1.silu', -'transformer_blocks.18.norm1.linear', -'transformer_blocks.18.norm1.norm', -'transformer_blocks.18.norm1_context', -'transformer_blocks.18.norm1_context.silu', -'transformer_blocks.18.norm1_context.linear', -'transformer_blocks.18.norm1_context.norm', -'transformer_blocks.18.attn', -'transformer_blocks.18.attn.norm_q', -'transformer_blocks.18.attn.norm_k', -'transformer_blocks.18.attn.to_q', -'transformer_blocks.18.attn.to_k', -'transformer_blocks.18.attn.to_v', -'transformer_blocks.18.attn.add_k_proj', -'transformer_blocks.18.attn.add_v_proj', -'transformer_blocks.18.attn.add_q_proj', -'transformer_blocks.18.attn.to_out', -'transformer_blocks.18.attn.to_out.0', -'transformer_blocks.18.attn.to_out.1', -'transformer_blocks.18.attn.to_add_out', -'transformer_blocks.18.attn.norm_added_q', -'transformer_blocks.18.attn.norm_added_k', -'transformer_blocks.18.norm2', -'transformer_blocks.18.ff', -'transformer_blocks.18.ff.net', -'transformer_blocks.18.ff.net.0', -'transformer_blocks.18.ff.net.0.proj', -'transformer_blocks.18.ff.net.1', -'transformer_blocks.18.ff.net.2', -'transformer_blocks.18.norm2_context', -'transformer_blocks.18.ff_context', -'transformer_blocks.18.ff_context.net', -'transformer_blocks.18.ff_context.net.0', -'transformer_blocks.18.ff_context.net.0.proj', -'transformer_blocks.18.ff_context.net.1', -'transformer_blocks.18.ff_context.net.2', -'transformer_blocks.19', -'transformer_blocks.19.norm1', -'transformer_blocks.19.norm1.silu', -'transformer_blocks.19.norm1.linear', -'transformer_blocks.19.norm1.norm', -'transformer_blocks.19.norm1_context', -'transformer_blocks.19.norm1_context.silu', -'transformer_blocks.19.norm1_context.linear', -'transformer_blocks.19.norm1_context.norm', -'transformer_blocks.19.attn', -'transformer_blocks.19.attn.norm_q', -'transformer_blocks.19.attn.norm_k', -'transformer_blocks.19.attn.to_q', -'transformer_blocks.19.attn.to_k', -'transformer_blocks.19.attn.to_v', -'transformer_blocks.19.attn.add_k_proj', -'transformer_blocks.19.attn.add_v_proj', -'transformer_blocks.19.attn.add_q_proj', -'transformer_blocks.19.attn.to_out', -'transformer_blocks.19.attn.to_out.0', -'transformer_blocks.19.attn.to_out.1', -'transformer_blocks.19.attn.to_add_out', -'transformer_blocks.19.attn.norm_added_q', -'transformer_blocks.19.attn.norm_added_k', -'transformer_blocks.19.norm2', -'transformer_blocks.19.ff', -'transformer_blocks.19.ff.net', -'transformer_blocks.19.ff.net.0', -'transformer_blocks.19.ff.net.0.proj', -'transformer_blocks.19.ff.net.1', -'transformer_blocks.19.ff.net.2', -'transformer_blocks.19.norm2_context', -'transformer_blocks.19.ff_context', -'transformer_blocks.19.ff_context.net', -'transformer_blocks.19.ff_context.net.0', -'transformer_blocks.19.ff_context.net.0.proj', -'transformer_blocks.19.ff_context.net.1', -'transformer_blocks.19.ff_context.net.2', -'transformer_blocks.20', -'transformer_blocks.20.norm1', -'transformer_blocks.20.norm1.silu', -'transformer_blocks.20.norm1.linear', -'transformer_blocks.20.norm1.norm', -'transformer_blocks.20.norm1_context', -'transformer_blocks.20.norm1_context.silu', -'transformer_blocks.20.norm1_context.linear', -'transformer_blocks.20.norm1_context.norm', -'transformer_blocks.20.attn', -'transformer_blocks.20.attn.norm_q', -'transformer_blocks.20.attn.norm_k', -'transformer_blocks.20.attn.to_q', -'transformer_blocks.20.attn.to_k', -'transformer_blocks.20.attn.to_v', -'transformer_blocks.20.attn.add_k_proj', -'transformer_blocks.20.attn.add_v_proj', -'transformer_blocks.20.attn.add_q_proj', -'transformer_blocks.20.attn.to_out', -'transformer_blocks.20.attn.to_out.0', -'transformer_blocks.20.attn.to_out.1', -'transformer_blocks.20.attn.to_add_out', -'transformer_blocks.20.attn.norm_added_q', -'transformer_blocks.20.attn.norm_added_k', -'transformer_blocks.20.norm2', -'transformer_blocks.20.ff', -'transformer_blocks.20.ff.net', -'transformer_blocks.20.ff.net.0', -'transformer_blocks.20.ff.net.0.proj', -'transformer_blocks.20.ff.net.1', -'transformer_blocks.20.ff.net.2', -'transformer_blocks.20.norm2_context', -'transformer_blocks.20.ff_context', -'transformer_blocks.20.ff_context.net', -'transformer_blocks.20.ff_context.net.0', -'transformer_blocks.20.ff_context.net.0.proj', -'transformer_blocks.20.ff_context.net.1', -'transformer_blocks.20.ff_context.net.2', -'transformer_blocks.21', -'transformer_blocks.21.norm1', -'transformer_blocks.21.norm1.silu', -'transformer_blocks.21.norm1.linear', -'transformer_blocks.21.norm1.norm', -'transformer_blocks.21.norm1_context', -'transformer_blocks.21.norm1_context.silu', -'transformer_blocks.21.norm1_context.linear', -'transformer_blocks.21.norm1_context.norm', -'transformer_blocks.21.attn', -'transformer_blocks.21.attn.norm_q', -'transformer_blocks.21.attn.norm_k', -'transformer_blocks.21.attn.to_q', -'transformer_blocks.21.attn.to_k', -'transformer_blocks.21.attn.to_v', -'transformer_blocks.21.attn.add_k_proj', -'transformer_blocks.21.attn.add_v_proj', -'transformer_blocks.21.attn.add_q_proj', -'transformer_blocks.21.attn.to_out', -'transformer_blocks.21.attn.to_out.0', -'transformer_blocks.21.attn.to_out.1', -'transformer_blocks.21.attn.to_add_out', -'transformer_blocks.21.attn.norm_added_q', -'transformer_blocks.21.attn.norm_added_k', -'transformer_blocks.21.norm2', -'transformer_blocks.21.ff', -'transformer_blocks.21.ff.net', -'transformer_blocks.21.ff.net.0', -'transformer_blocks.21.ff.net.0.proj', -'transformer_blocks.21.ff.net.1', -'transformer_blocks.21.ff.net.2', -'transformer_blocks.21.norm2_context', -'transformer_blocks.21.ff_context', -'transformer_blocks.21.ff_context.net', -'transformer_blocks.21.ff_context.net.0', -'transformer_blocks.21.ff_context.net.0.proj', -'transformer_blocks.21.ff_context.net.1', -'transformer_blocks.21.ff_context.net.2', -'transformer_blocks.22', -'transformer_blocks.22.norm1', -'transformer_blocks.22.norm1.silu', -'transformer_blocks.22.norm1.linear', -'transformer_blocks.22.norm1.norm', -'transformer_blocks.22.norm1_context', -'transformer_blocks.22.norm1_context.silu', -'transformer_blocks.22.norm1_context.linear', -'transformer_blocks.22.norm1_context.norm', -'transformer_blocks.22.attn', -'transformer_blocks.22.attn.norm_q', -'transformer_blocks.22.attn.norm_k', -'transformer_blocks.22.attn.to_q', -'transformer_blocks.22.attn.to_k', -'transformer_blocks.22.attn.to_v', -'transformer_blocks.22.attn.add_k_proj', -'transformer_blocks.22.attn.add_v_proj', -'transformer_blocks.22.attn.add_q_proj', -'transformer_blocks.22.attn.to_out', -'transformer_blocks.22.attn.to_out.0', -'transformer_blocks.22.attn.to_out.1', -'transformer_blocks.22.attn.to_add_out', -'transformer_blocks.22.attn.norm_added_q', -'transformer_blocks.22.attn.norm_added_k', -'transformer_blocks.22.norm2', -'transformer_blocks.22.ff', -'transformer_blocks.22.ff.net', -'transformer_blocks.22.ff.net.0', -'transformer_blocks.22.ff.net.0.proj', -'transformer_blocks.22.ff.net.1', -'transformer_blocks.22.ff.net.2', -'transformer_blocks.22.norm2_context', -'transformer_blocks.22.ff_context', -'transformer_blocks.22.ff_context.net', -'transformer_blocks.22.ff_context.net.0', -'transformer_blocks.22.ff_context.net.0.proj', -'transformer_blocks.22.ff_context.net.1', -'transformer_blocks.22.ff_context.net.2', -'transformer_blocks.23', -'transformer_blocks.23.norm1', -'transformer_blocks.23.norm1.silu', -'transformer_blocks.23.norm1.linear', -'transformer_blocks.23.norm1.norm', -'transformer_blocks.23.norm1_context', -'transformer_blocks.23.norm1_context.silu', -'transformer_blocks.23.norm1_context.linear', -'transformer_blocks.23.norm1_context.norm', -'transformer_blocks.23.attn', -'transformer_blocks.23.attn.norm_q', -'transformer_blocks.23.attn.norm_k', -'transformer_blocks.23.attn.to_q', -'transformer_blocks.23.attn.to_k', -'transformer_blocks.23.attn.to_v', -'transformer_blocks.23.attn.add_k_proj', -'transformer_blocks.23.attn.add_v_proj', -'transformer_blocks.23.attn.add_q_proj', -'transformer_blocks.23.attn.to_out', -'transformer_blocks.23.attn.to_out.0', -'transformer_blocks.23.attn.to_out.1', -'transformer_blocks.23.attn.to_add_out', -'transformer_blocks.23.attn.norm_added_q', -'transformer_blocks.23.attn.norm_added_k', -'transformer_blocks.23.norm2', -'transformer_blocks.23.ff', -'transformer_blocks.23.ff.net', -'transformer_blocks.23.ff.net.0', -'transformer_blocks.23.ff.net.0.proj', -'transformer_blocks.23.ff.net.1', -'transformer_blocks.23.ff.net.2', -'transformer_blocks.23.norm2_context', -'transformer_blocks.23.ff_context', -'transformer_blocks.23.ff_context.net', -'transformer_blocks.23.ff_context.net.0', -'transformer_blocks.23.ff_context.net.0.proj', -'transformer_blocks.23.ff_context.net.1', -'transformer_blocks.23.ff_context.net.2', -'transformer_blocks.24', -'transformer_blocks.24.norm1', -'transformer_blocks.24.norm1.silu', -'transformer_blocks.24.norm1.linear', -'transformer_blocks.24.norm1.norm', -'transformer_blocks.24.norm1_context', -'transformer_blocks.24.norm1_context.silu', -'transformer_blocks.24.norm1_context.linear', -'transformer_blocks.24.norm1_context.norm', -'transformer_blocks.24.attn', -'transformer_blocks.24.attn.norm_q', -'transformer_blocks.24.attn.norm_k', -'transformer_blocks.24.attn.to_q', -'transformer_blocks.24.attn.to_k', -'transformer_blocks.24.attn.to_v', -'transformer_blocks.24.attn.add_k_proj', -'transformer_blocks.24.attn.add_v_proj', -'transformer_blocks.24.attn.add_q_proj', -'transformer_blocks.24.attn.to_out', -'transformer_blocks.24.attn.to_out.0', -'transformer_blocks.24.attn.to_out.1', -'transformer_blocks.24.attn.to_add_out', -'transformer_blocks.24.attn.norm_added_q', -'transformer_blocks.24.attn.norm_added_k', -'transformer_blocks.24.norm2', -'transformer_blocks.24.ff', -'transformer_blocks.24.ff.net', -'transformer_blocks.24.ff.net.0', -'transformer_blocks.24.ff.net.0.proj', -'transformer_blocks.24.ff.net.1', -'transformer_blocks.24.ff.net.2', -'transformer_blocks.24.norm2_context', -'transformer_blocks.24.ff_context', -'transformer_blocks.24.ff_context.net', -'transformer_blocks.24.ff_context.net.0', -'transformer_blocks.24.ff_context.net.0.proj', -'transformer_blocks.24.ff_context.net.1', -'transformer_blocks.24.ff_context.net.2', -'transformer_blocks.25', -'transformer_blocks.25.norm1', -'transformer_blocks.25.norm1.silu', -'transformer_blocks.25.norm1.linear', -'transformer_blocks.25.norm1.norm', -'transformer_blocks.25.norm1_context', -'transformer_blocks.25.norm1_context.silu', -'transformer_blocks.25.norm1_context.linear', -'transformer_blocks.25.norm1_context.norm', -'transformer_blocks.25.attn', -'transformer_blocks.25.attn.norm_q', -'transformer_blocks.25.attn.norm_k', -'transformer_blocks.25.attn.to_q', -'transformer_blocks.25.attn.to_k', -'transformer_blocks.25.attn.to_v', -'transformer_blocks.25.attn.add_k_proj', -'transformer_blocks.25.attn.add_v_proj', -'transformer_blocks.25.attn.add_q_proj', -'transformer_blocks.25.attn.to_out', -'transformer_blocks.25.attn.to_out.0', -'transformer_blocks.25.attn.to_out.1', -'transformer_blocks.25.attn.to_add_out', -'transformer_blocks.25.attn.norm_added_q', -'transformer_blocks.25.attn.norm_added_k', -'transformer_blocks.25.norm2', -'transformer_blocks.25.ff', -'transformer_blocks.25.ff.net', -'transformer_blocks.25.ff.net.0', -'transformer_blocks.25.ff.net.0.proj', -'transformer_blocks.25.ff.net.1', -'transformer_blocks.25.ff.net.2', -'transformer_blocks.25.norm2_context', -'transformer_blocks.25.ff_context', -'transformer_blocks.25.ff_context.net', -'transformer_blocks.25.ff_context.net.0', -'transformer_blocks.25.ff_context.net.0.proj', -'transformer_blocks.25.ff_context.net.1', -'transformer_blocks.25.ff_context.net.2', -'transformer_blocks.26', -'transformer_blocks.26.norm1', -'transformer_blocks.26.norm1.silu', -'transformer_blocks.26.norm1.linear', -'transformer_blocks.26.norm1.norm', -'transformer_blocks.26.norm1_context', -'transformer_blocks.26.norm1_context.silu', -'transformer_blocks.26.norm1_context.linear', -'transformer_blocks.26.norm1_context.norm', -'transformer_blocks.26.attn', -'transformer_blocks.26.attn.norm_q', -'transformer_blocks.26.attn.norm_k', -'transformer_blocks.26.attn.to_q', -'transformer_blocks.26.attn.to_k', -'transformer_blocks.26.attn.to_v', -'transformer_blocks.26.attn.add_k_proj', -'transformer_blocks.26.attn.add_v_proj', -'transformer_blocks.26.attn.add_q_proj', -'transformer_blocks.26.attn.to_out', -'transformer_blocks.26.attn.to_out.0', -'transformer_blocks.26.attn.to_out.1', -'transformer_blocks.26.attn.to_add_out', -'transformer_blocks.26.attn.norm_added_q', -'transformer_blocks.26.attn.norm_added_k', -'transformer_blocks.26.norm2', -'transformer_blocks.26.ff', -'transformer_blocks.26.ff.net', -'transformer_blocks.26.ff.net.0', -'transformer_blocks.26.ff.net.0.proj', -'transformer_blocks.26.ff.net.1', -'transformer_blocks.26.ff.net.2', -'transformer_blocks.26.norm2_context', -'transformer_blocks.26.ff_context', -'transformer_blocks.26.ff_context.net', -'transformer_blocks.26.ff_context.net.0', -'transformer_blocks.26.ff_context.net.0.proj', -'transformer_blocks.26.ff_context.net.1', -'transformer_blocks.26.ff_context.net.2', -'transformer_blocks.27', -'transformer_blocks.27.norm1', -'transformer_blocks.27.norm1.silu', -'transformer_blocks.27.norm1.linear', -'transformer_blocks.27.norm1.norm', -'transformer_blocks.27.norm1_context', -'transformer_blocks.27.norm1_context.silu', -'transformer_blocks.27.norm1_context.linear', -'transformer_blocks.27.norm1_context.norm', -'transformer_blocks.27.attn', -'transformer_blocks.27.attn.norm_q', -'transformer_blocks.27.attn.norm_k', -'transformer_blocks.27.attn.to_q', -'transformer_blocks.27.attn.to_k', -'transformer_blocks.27.attn.to_v', -'transformer_blocks.27.attn.add_k_proj', -'transformer_blocks.27.attn.add_v_proj', -'transformer_blocks.27.attn.add_q_proj', -'transformer_blocks.27.attn.to_out', -'transformer_blocks.27.attn.to_out.0', -'transformer_blocks.27.attn.to_out.1', -'transformer_blocks.27.attn.to_add_out', -'transformer_blocks.27.attn.norm_added_q', -'transformer_blocks.27.attn.norm_added_k', -'transformer_blocks.27.norm2', -'transformer_blocks.27.ff', -'transformer_blocks.27.ff.net', -'transformer_blocks.27.ff.net.0', -'transformer_blocks.27.ff.net.0.proj', -'transformer_blocks.27.ff.net.1', -'transformer_blocks.27.ff.net.2', -'transformer_blocks.27.norm2_context', -'transformer_blocks.27.ff_context', -'transformer_blocks.27.ff_context.net', -'transformer_blocks.27.ff_context.net.0', -'transformer_blocks.27.ff_context.net.0.proj', -'transformer_blocks.27.ff_context.net.1', -'transformer_blocks.27.ff_context.net.2', -'transformer_blocks.28', -'transformer_blocks.28.norm1', -'transformer_blocks.28.norm1.silu', -'transformer_blocks.28.norm1.linear', -'transformer_blocks.28.norm1.norm', -'transformer_blocks.28.norm1_context', -'transformer_blocks.28.norm1_context.silu', -'transformer_blocks.28.norm1_context.linear', -'transformer_blocks.28.norm1_context.norm', -'transformer_blocks.28.attn', -'transformer_blocks.28.attn.norm_q', -'transformer_blocks.28.attn.norm_k', -'transformer_blocks.28.attn.to_q', -'transformer_blocks.28.attn.to_k', -'transformer_blocks.28.attn.to_v', -'transformer_blocks.28.attn.add_k_proj', -'transformer_blocks.28.attn.add_v_proj', -'transformer_blocks.28.attn.add_q_proj', -'transformer_blocks.28.attn.to_out', -'transformer_blocks.28.attn.to_out.0', -'transformer_blocks.28.attn.to_out.1', -'transformer_blocks.28.attn.to_add_out', -'transformer_blocks.28.attn.norm_added_q', -'transformer_blocks.28.attn.norm_added_k', -'transformer_blocks.28.norm2', -'transformer_blocks.28.ff', -'transformer_blocks.28.ff.net', -'transformer_blocks.28.ff.net.0', -'transformer_blocks.28.ff.net.0.proj', -'transformer_blocks.28.ff.net.1', -'transformer_blocks.28.ff.net.2', -'transformer_blocks.28.norm2_context', -'transformer_blocks.28.ff_context', -'transformer_blocks.28.ff_context.net', -'transformer_blocks.28.ff_context.net.0', -'transformer_blocks.28.ff_context.net.0.proj', -'transformer_blocks.28.ff_context.net.1', -'transformer_blocks.28.ff_context.net.2', -'transformer_blocks.29', -'transformer_blocks.29.norm1', -'transformer_blocks.29.norm1.silu', -'transformer_blocks.29.norm1.linear', -'transformer_blocks.29.norm1.norm', -'transformer_blocks.29.norm1_context', -'transformer_blocks.29.norm1_context.silu', -'transformer_blocks.29.norm1_context.linear', -'transformer_blocks.29.norm1_context.norm', -'transformer_blocks.29.attn', -'transformer_blocks.29.attn.norm_q', -'transformer_blocks.29.attn.norm_k', -'transformer_blocks.29.attn.to_q', -'transformer_blocks.29.attn.to_k', -'transformer_blocks.29.attn.to_v', -'transformer_blocks.29.attn.add_k_proj', -'transformer_blocks.29.attn.add_v_proj', -'transformer_blocks.29.attn.add_q_proj', -'transformer_blocks.29.attn.to_out', -'transformer_blocks.29.attn.to_out.0', -'transformer_blocks.29.attn.to_out.1', -'transformer_blocks.29.attn.to_add_out', -'transformer_blocks.29.attn.norm_added_q', -'transformer_blocks.29.attn.norm_added_k', -'transformer_blocks.29.norm2', -'transformer_blocks.29.ff', -'transformer_blocks.29.ff.net', -'transformer_blocks.29.ff.net.0', -'transformer_blocks.29.ff.net.0.proj', -'transformer_blocks.29.ff.net.1', -'transformer_blocks.29.ff.net.2', -'transformer_blocks.29.norm2_context', -'transformer_blocks.29.ff_context', -'transformer_blocks.29.ff_context.net', -'transformer_blocks.29.ff_context.net.0', -'transformer_blocks.29.ff_context.net.0.proj', -'transformer_blocks.29.ff_context.net.1', -'transformer_blocks.29.ff_context.net.2', -'transformer_blocks.30', -'transformer_blocks.30.norm1', -'transformer_blocks.30.norm1.silu', -'transformer_blocks.30.norm1.linear', -'transformer_blocks.30.norm1.norm', -'transformer_blocks.30.norm1_context', -'transformer_blocks.30.norm1_context.silu', -'transformer_blocks.30.norm1_context.linear', -'transformer_blocks.30.norm1_context.norm', -'transformer_blocks.30.attn', -'transformer_blocks.30.attn.norm_q', -'transformer_blocks.30.attn.norm_k', -'transformer_blocks.30.attn.to_q', -'transformer_blocks.30.attn.to_k', -'transformer_blocks.30.attn.to_v', -'transformer_blocks.30.attn.add_k_proj', -'transformer_blocks.30.attn.add_v_proj', -'transformer_blocks.30.attn.add_q_proj', -'transformer_blocks.30.attn.to_out', -'transformer_blocks.30.attn.to_out.0', -'transformer_blocks.30.attn.to_out.1', -'transformer_blocks.30.attn.to_add_out', -'transformer_blocks.30.attn.norm_added_q', -'transformer_blocks.30.attn.norm_added_k', -'transformer_blocks.30.norm2', -'transformer_blocks.30.ff', -'transformer_blocks.30.ff.net', -'transformer_blocks.30.ff.net.0', -'transformer_blocks.30.ff.net.0.proj', -'transformer_blocks.30.ff.net.1', -'transformer_blocks.30.ff.net.2', -'transformer_blocks.30.norm2_context', -'transformer_blocks.30.ff_context', -'transformer_blocks.30.ff_context.net', -'transformer_blocks.30.ff_context.net.0', -'transformer_blocks.30.ff_context.net.0.proj', -'transformer_blocks.30.ff_context.net.1', -'transformer_blocks.30.ff_context.net.2', -'transformer_blocks.31', -'transformer_blocks.31.norm1', -'transformer_blocks.31.norm1.silu', -'transformer_blocks.31.norm1.linear', -'transformer_blocks.31.norm1.norm', -'transformer_blocks.31.norm1_context', -'transformer_blocks.31.norm1_context.silu', -'transformer_blocks.31.norm1_context.linear', -'transformer_blocks.31.norm1_context.norm', -'transformer_blocks.31.attn', -'transformer_blocks.31.attn.norm_q', -'transformer_blocks.31.attn.norm_k', -'transformer_blocks.31.attn.to_q', -'transformer_blocks.31.attn.to_k', -'transformer_blocks.31.attn.to_v', -'transformer_blocks.31.attn.add_k_proj', -'transformer_blocks.31.attn.add_v_proj', -'transformer_blocks.31.attn.add_q_proj', -'transformer_blocks.31.attn.to_out', -'transformer_blocks.31.attn.to_out.0', -'transformer_blocks.31.attn.to_out.1', -'transformer_blocks.31.attn.to_add_out', -'transformer_blocks.31.attn.norm_added_q', -'transformer_blocks.31.attn.norm_added_k', -'transformer_blocks.31.norm2', -'transformer_blocks.31.ff', -'transformer_blocks.31.ff.net', -'transformer_blocks.31.ff.net.0', -'transformer_blocks.31.ff.net.0.proj', -'transformer_blocks.31.ff.net.1', -'transformer_blocks.31.ff.net.2', -'transformer_blocks.31.norm2_context', -'transformer_blocks.31.ff_context', -'transformer_blocks.31.ff_context.net', -'transformer_blocks.31.ff_context.net.0', -'transformer_blocks.31.ff_context.net.0.proj', -'transformer_blocks.31.ff_context.net.1', -'transformer_blocks.31.ff_context.net.2', -'transformer_blocks.32', -'transformer_blocks.32.norm1', -'transformer_blocks.32.norm1.silu', -'transformer_blocks.32.norm1.linear', -'transformer_blocks.32.norm1.norm', -'transformer_blocks.32.norm1_context', -'transformer_blocks.32.norm1_context.silu', -'transformer_blocks.32.norm1_context.linear', -'transformer_blocks.32.norm1_context.norm', -'transformer_blocks.32.attn', -'transformer_blocks.32.attn.norm_q', -'transformer_blocks.32.attn.norm_k', -'transformer_blocks.32.attn.to_q', -'transformer_blocks.32.attn.to_k', -'transformer_blocks.32.attn.to_v', -'transformer_blocks.32.attn.add_k_proj', -'transformer_blocks.32.attn.add_v_proj', -'transformer_blocks.32.attn.add_q_proj', -'transformer_blocks.32.attn.to_out', -'transformer_blocks.32.attn.to_out.0', -'transformer_blocks.32.attn.to_out.1', -'transformer_blocks.32.attn.to_add_out', -'transformer_blocks.32.attn.norm_added_q', -'transformer_blocks.32.attn.norm_added_k', -'transformer_blocks.32.norm2', -'transformer_blocks.32.ff', -'transformer_blocks.32.ff.net', -'transformer_blocks.32.ff.net.0', -'transformer_blocks.32.ff.net.0.proj', -'transformer_blocks.32.ff.net.1', -'transformer_blocks.32.ff.net.2', -'transformer_blocks.32.norm2_context', -'transformer_blocks.32.ff_context', -'transformer_blocks.32.ff_context.net', -'transformer_blocks.32.ff_context.net.0', -'transformer_blocks.32.ff_context.net.0.proj', -'transformer_blocks.32.ff_context.net.1', -'transformer_blocks.32.ff_context.net.2', -'transformer_blocks.33', -'transformer_blocks.33.norm1', -'transformer_blocks.33.norm1.silu', -'transformer_blocks.33.norm1.linear', -'transformer_blocks.33.norm1.norm', -'transformer_blocks.33.norm1_context', -'transformer_blocks.33.norm1_context.silu', -'transformer_blocks.33.norm1_context.linear', -'transformer_blocks.33.norm1_context.norm', -'transformer_blocks.33.attn', -'transformer_blocks.33.attn.norm_q', -'transformer_blocks.33.attn.norm_k', -'transformer_blocks.33.attn.to_q', -'transformer_blocks.33.attn.to_k', -'transformer_blocks.33.attn.to_v', -'transformer_blocks.33.attn.add_k_proj', -'transformer_blocks.33.attn.add_v_proj', -'transformer_blocks.33.attn.add_q_proj', -'transformer_blocks.33.attn.to_out', -'transformer_blocks.33.attn.to_out.0', -'transformer_blocks.33.attn.to_out.1', -'transformer_blocks.33.attn.to_add_out', -'transformer_blocks.33.attn.norm_added_q', -'transformer_blocks.33.attn.norm_added_k', -'transformer_blocks.33.norm2', -'transformer_blocks.33.ff', -'transformer_blocks.33.ff.net', -'transformer_blocks.33.ff.net.0', -'transformer_blocks.33.ff.net.0.proj', -'transformer_blocks.33.ff.net.1', -'transformer_blocks.33.ff.net.2', -'transformer_blocks.33.norm2_context', -'transformer_blocks.33.ff_context', -'transformer_blocks.33.ff_context.net', -'transformer_blocks.33.ff_context.net.0', -'transformer_blocks.33.ff_context.net.0.proj', -'transformer_blocks.33.ff_context.net.1', -'transformer_blocks.33.ff_context.net.2', -'transformer_blocks.34', -'transformer_blocks.34.norm1', -'transformer_blocks.34.norm1.silu', -'transformer_blocks.34.norm1.linear', -'transformer_blocks.34.norm1.norm', -'transformer_blocks.34.norm1_context', -'transformer_blocks.34.norm1_context.silu', -'transformer_blocks.34.norm1_context.linear', -'transformer_blocks.34.norm1_context.norm', -'transformer_blocks.34.attn', -'transformer_blocks.34.attn.norm_q', -'transformer_blocks.34.attn.norm_k', -'transformer_blocks.34.attn.to_q', -'transformer_blocks.34.attn.to_k', -'transformer_blocks.34.attn.to_v', -'transformer_blocks.34.attn.add_k_proj', -'transformer_blocks.34.attn.add_v_proj', -'transformer_blocks.34.attn.add_q_proj', -'transformer_blocks.34.attn.to_out', -'transformer_blocks.34.attn.to_out.0', -'transformer_blocks.34.attn.to_out.1', -'transformer_blocks.34.attn.to_add_out', -'transformer_blocks.34.attn.norm_added_q', -'transformer_blocks.34.attn.norm_added_k', -'transformer_blocks.34.norm2', -'transformer_blocks.34.ff', -'transformer_blocks.34.ff.net', -'transformer_blocks.34.ff.net.0', -'transformer_blocks.34.ff.net.0.proj', -'transformer_blocks.34.ff.net.1', -'transformer_blocks.34.ff.net.2', -'transformer_blocks.34.norm2_context', -'transformer_blocks.34.ff_context', -'transformer_blocks.34.ff_context.net', -'transformer_blocks.34.ff_context.net.0', -'transformer_blocks.34.ff_context.net.0.proj', -'transformer_blocks.34.ff_context.net.1', -'transformer_blocks.34.ff_context.net.2', -'transformer_blocks.35', -'transformer_blocks.35.norm1', -'transformer_blocks.35.norm1.silu', -'transformer_blocks.35.norm1.linear', -'transformer_blocks.35.norm1.norm', -'transformer_blocks.35.norm1_context', -'transformer_blocks.35.norm1_context.silu', -'transformer_blocks.35.norm1_context.linear', -'transformer_blocks.35.norm1_context.norm', -'transformer_blocks.35.attn', -'transformer_blocks.35.attn.norm_q', -'transformer_blocks.35.attn.norm_k', -'transformer_blocks.35.attn.to_q', -'transformer_blocks.35.attn.to_k', -'transformer_blocks.35.attn.to_v', -'transformer_blocks.35.attn.add_k_proj', -'transformer_blocks.35.attn.add_v_proj', -'transformer_blocks.35.attn.add_q_proj', -'transformer_blocks.35.attn.to_out', -'transformer_blocks.35.attn.to_out.0', -'transformer_blocks.35.attn.to_out.1', -'transformer_blocks.35.attn.to_add_out', -'transformer_blocks.35.attn.norm_added_q', -'transformer_blocks.35.attn.norm_added_k', -'transformer_blocks.35.norm2', -'transformer_blocks.35.ff', -'transformer_blocks.35.ff.net', -'transformer_blocks.35.ff.net.0', -'transformer_blocks.35.ff.net.0.proj', -'transformer_blocks.35.ff.net.1', -'transformer_blocks.35.ff.net.2', -'transformer_blocks.35.norm2_context', -'transformer_blocks.35.ff_context', -'transformer_blocks.35.ff_context.net', -'transformer_blocks.35.ff_context.net.0', -'transformer_blocks.35.ff_context.net.0.proj', -'transformer_blocks.35.ff_context.net.1', -'transformer_blocks.35.ff_context.net.2', -'transformer_blocks.36', -'transformer_blocks.36.norm1', -'transformer_blocks.36.norm1.silu', -'transformer_blocks.36.norm1.linear', -'transformer_blocks.36.norm1.norm', -'transformer_blocks.36.norm1_context', -'transformer_blocks.36.norm1_context.silu', -'transformer_blocks.36.norm1_context.linear', -'transformer_blocks.36.norm1_context.norm', -'transformer_blocks.36.attn', -'transformer_blocks.36.attn.norm_q', -'transformer_blocks.36.attn.norm_k', -'transformer_blocks.36.attn.to_q', -'transformer_blocks.36.attn.to_k', -'transformer_blocks.36.attn.to_v', -'transformer_blocks.36.attn.add_k_proj', -'transformer_blocks.36.attn.add_v_proj', -'transformer_blocks.36.attn.add_q_proj', -'transformer_blocks.36.attn.to_out', -'transformer_blocks.36.attn.to_out.0', -'transformer_blocks.36.attn.to_out.1', -'transformer_blocks.36.attn.to_add_out', -'transformer_blocks.36.attn.norm_added_q', -'transformer_blocks.36.attn.norm_added_k', -'transformer_blocks.36.norm2', -'transformer_blocks.36.ff', -'transformer_blocks.36.ff.net', -'transformer_blocks.36.ff.net.0', -'transformer_blocks.36.ff.net.0.proj', -'transformer_blocks.36.ff.net.1', -'transformer_blocks.36.ff.net.2', -'transformer_blocks.36.norm2_context', -'transformer_blocks.36.ff_context', -'transformer_blocks.36.ff_context.net', -'transformer_blocks.36.ff_context.net.0', -'transformer_blocks.36.ff_context.net.0.proj', -'transformer_blocks.36.ff_context.net.1', -'transformer_blocks.36.ff_context.net.2', -'transformer_blocks.37', -'transformer_blocks.37.norm1', -'transformer_blocks.37.norm1.silu', -'transformer_blocks.37.norm1.linear', -'transformer_blocks.37.norm1.norm', -'transformer_blocks.37.norm1_context', -'transformer_blocks.37.norm1_context.silu', -'transformer_blocks.37.norm1_context.linear', -'transformer_blocks.37.norm1_context.norm', -'transformer_blocks.37.attn', -'transformer_blocks.37.attn.norm_q', -'transformer_blocks.37.attn.norm_k', -'transformer_blocks.37.attn.to_q', -'transformer_blocks.37.attn.to_k', -'transformer_blocks.37.attn.to_v', -'transformer_blocks.37.attn.add_k_proj', -'transformer_blocks.37.attn.add_v_proj', -'transformer_blocks.37.attn.add_q_proj', -'transformer_blocks.37.attn.to_out', -'transformer_blocks.37.attn.to_out.0', -'transformer_blocks.37.attn.to_out.1', -'transformer_blocks.37.attn.norm_added_q', -'transformer_blocks.37.attn.norm_added_k', -'transformer_blocks.37.norm2', -'transformer_blocks.37.ff', -'transformer_blocks.37.ff.net', -'transformer_blocks.37.ff.net.0', -'transformer_blocks.37.ff.net.0.proj', -'transformer_blocks.37.ff.net.1', -'transformer_blocks.37.ff.net.2', -'norm_out', -'norm_out.silu', -'norm_out.linear', -'norm_out.norm', -'proj_out'] \ No newline at end of file diff --git a/modules/Experiments/t5_layers.py b/modules/Experiments/t5_layers.py deleted file mode 100644 index 41c1d1f..0000000 --- a/modules/Experiments/t5_layers.py +++ /dev/null @@ -1,393 +0,0 @@ -T5_LAYERS = [ -'text_model', -'text_model.embeddings', -'text_model.embeddings.token_embedding', -'text_model.embeddings.position_embedding', -'text_model.encoder', -'text_model.encoder.layers', -'text_model.encoder.layers.0', -'text_model.encoder.layers.0.self_attn', -'text_model.encoder.layers.0.self_attn.k_proj', -'text_model.encoder.layers.0.self_attn.v_proj', -'text_model.encoder.layers.0.self_attn.q_proj', -'text_model.encoder.layers.0.self_attn.out_proj', -'text_model.encoder.layers.0.layer_norm1', -'text_model.encoder.layers.0.mlp', -'text_model.encoder.layers.0.mlp.activation_fn', -'text_model.encoder.layers.0.mlp.fc1', -'text_model.encoder.layers.0.mlp.fc2', -'text_model.encoder.layers.0.layer_norm2', -'text_model.encoder.layers.1', -'text_model.encoder.layers.1.self_attn', -'text_model.encoder.layers.1.self_attn.k_proj', -'text_model.encoder.layers.1.self_attn.v_proj', -'text_model.encoder.layers.1.self_attn.q_proj', -'text_model.encoder.layers.1.self_attn.out_proj', -'text_model.encoder.layers.1.layer_norm1', -'text_model.encoder.layers.1.mlp', -'text_model.encoder.layers.1.mlp.activation_fn', -'text_model.encoder.layers.1.mlp.fc1', -'text_model.encoder.layers.1.mlp.fc2', -'text_model.encoder.layers.1.layer_norm2', -'text_model.encoder.layers.2', -'text_model.encoder.layers.2.self_attn', -'text_model.encoder.layers.2.self_attn.k_proj', -'text_model.encoder.layers.2.self_attn.v_proj', -'text_model.encoder.layers.2.self_attn.q_proj', -'text_model.encoder.layers.2.self_attn.out_proj', -'text_model.encoder.layers.2.layer_norm1', -'text_model.encoder.layers.2.mlp', -'text_model.encoder.layers.2.mlp.activation_fn', -'text_model.encoder.layers.2.mlp.fc1', -'text_model.encoder.layers.2.mlp.fc2', -'text_model.encoder.layers.2.layer_norm2', -'text_model.encoder.layers.3', -'text_model.encoder.layers.3.self_attn', -'text_model.encoder.layers.3.self_attn.k_proj', -'text_model.encoder.layers.3.self_attn.v_proj', -'text_model.encoder.layers.3.self_attn.q_proj', -'text_model.encoder.layers.3.self_attn.out_proj', -'text_model.encoder.layers.3.layer_norm1', -'text_model.encoder.layers.3.mlp', -'text_model.encoder.layers.3.mlp.activation_fn', -'text_model.encoder.layers.3.mlp.fc1', -'text_model.encoder.layers.3.mlp.fc2', -'text_model.encoder.layers.3.layer_norm2', -'text_model.encoder.layers.4', -'text_model.encoder.layers.4.self_attn', -'text_model.encoder.layers.4.self_attn.k_proj', -'text_model.encoder.layers.4.self_attn.v_proj', -'text_model.encoder.layers.4.self_attn.q_proj', -'text_model.encoder.layers.4.self_attn.out_proj', -'text_model.encoder.layers.4.layer_norm1', -'text_model.encoder.layers.4.mlp', -'text_model.encoder.layers.4.mlp.activation_fn', -'text_model.encoder.layers.4.mlp.fc1', -'text_model.encoder.layers.4.mlp.fc2', -'text_model.encoder.layers.4.layer_norm2', -'text_model.encoder.layers.5', -'text_model.encoder.layers.5.self_attn', -'text_model.encoder.layers.5.self_attn.k_proj', -'text_model.encoder.layers.5.self_attn.v_proj', -'text_model.encoder.layers.5.self_attn.q_proj', -'text_model.encoder.layers.5.self_attn.out_proj', -'text_model.encoder.layers.5.layer_norm1', -'text_model.encoder.layers.5.mlp', -'text_model.encoder.layers.5.mlp.activation_fn', -'text_model.encoder.layers.5.mlp.fc1', -'text_model.encoder.layers.5.mlp.fc2', -'text_model.encoder.layers.5.layer_norm2', -'text_model.encoder.layers.6', -'text_model.encoder.layers.6.self_attn', -'text_model.encoder.layers.6.self_attn.k_proj', -'text_model.encoder.layers.6.self_attn.v_proj', -'text_model.encoder.layers.6.self_attn.q_proj', -'text_model.encoder.layers.6.self_attn.out_proj', -'text_model.encoder.layers.6.layer_norm1', -'text_model.encoder.layers.6.mlp', -'text_model.encoder.layers.6.mlp.activation_fn', -'text_model.encoder.layers.6.mlp.fc1', -'text_model.encoder.layers.6.mlp.fc2', -'text_model.encoder.layers.6.layer_norm2', -'text_model.encoder.layers.7', -'text_model.encoder.layers.7.self_attn', -'text_model.encoder.layers.7.self_attn.k_proj', -'text_model.encoder.layers.7.self_attn.v_proj', -'text_model.encoder.layers.7.self_attn.q_proj', -'text_model.encoder.layers.7.self_attn.out_proj', -'text_model.encoder.layers.7.layer_norm1', -'text_model.encoder.layers.7.mlp', -'text_model.encoder.layers.7.mlp.activation_fn', -'text_model.encoder.layers.7.mlp.fc1', -'text_model.encoder.layers.7.mlp.fc2', -'text_model.encoder.layers.7.layer_norm2', -'text_model.encoder.layers.8', -'text_model.encoder.layers.8.self_attn', -'text_model.encoder.layers.8.self_attn.k_proj', -'text_model.encoder.layers.8.self_attn.v_proj', -'text_model.encoder.layers.8.self_attn.q_proj', -'text_model.encoder.layers.8.self_attn.out_proj', -'text_model.encoder.layers.8.layer_norm1', -'text_model.encoder.layers.8.mlp', -'text_model.encoder.layers.8.mlp.activation_fn', -'text_model.encoder.layers.8.mlp.fc1', -'text_model.encoder.layers.8.mlp.fc2', -'text_model.encoder.layers.8.layer_norm2', -'text_model.encoder.layers.9', -'text_model.encoder.layers.9.self_attn', -'text_model.encoder.layers.9.self_attn.k_proj', -'text_model.encoder.layers.9.self_attn.v_proj', -'text_model.encoder.layers.9.self_attn.q_proj', -'text_model.encoder.layers.9.self_attn.out_proj', -'text_model.encoder.layers.9.layer_norm1', -'text_model.encoder.layers.9.mlp', -'text_model.encoder.layers.9.mlp.activation_fn', -'text_model.encoder.layers.9.mlp.fc1', -'text_model.encoder.layers.9.mlp.fc2', -'text_model.encoder.layers.9.layer_norm2', -'text_model.encoder.layers.10', -'text_model.encoder.layers.10.self_attn', -'text_model.encoder.layers.10.self_attn.k_proj', -'text_model.encoder.layers.10.self_attn.v_proj', -'text_model.encoder.layers.10.self_attn.q_proj', -'text_model.encoder.layers.10.self_attn.out_proj', -'text_model.encoder.layers.10.layer_norm1', -'text_model.encoder.layers.10.mlp', -'text_model.encoder.layers.10.mlp.activation_fn', -'text_model.encoder.layers.10.mlp.fc1', -'text_model.encoder.layers.10.mlp.fc2', -'text_model.encoder.layers.10.layer_norm2', -'text_model.encoder.layers.11', -'text_model.encoder.layers.11.self_attn', -'text_model.encoder.layers.11.self_attn.k_proj', -'text_model.encoder.layers.11.self_attn.v_proj', -'text_model.encoder.layers.11.self_attn.q_proj', -'text_model.encoder.layers.11.self_attn.out_proj', -'text_model.encoder.layers.11.layer_norm1', -'text_model.encoder.layers.11.mlp', -'text_model.encoder.layers.11.mlp.activation_fn', -'text_model.encoder.layers.11.mlp.fc1', -'text_model.encoder.layers.11.mlp.fc2', -'text_model.encoder.layers.11.layer_norm2', -'text_model.encoder.layers.12', -'text_model.encoder.layers.12.self_attn', -'text_model.encoder.layers.12.self_attn.k_proj', -'text_model.encoder.layers.12.self_attn.v_proj', -'text_model.encoder.layers.12.self_attn.q_proj', -'text_model.encoder.layers.12.self_attn.out_proj', -'text_model.encoder.layers.12.layer_norm1', -'text_model.encoder.layers.12.mlp', -'text_model.encoder.layers.12.mlp.activation_fn', -'text_model.encoder.layers.12.mlp.fc1', -'text_model.encoder.layers.12.mlp.fc2', -'text_model.encoder.layers.12.layer_norm2', -'text_model.encoder.layers.13', -'text_model.encoder.layers.13.self_attn', -'text_model.encoder.layers.13.self_attn.k_proj', -'text_model.encoder.layers.13.self_attn.v_proj', -'text_model.encoder.layers.13.self_attn.q_proj', -'text_model.encoder.layers.13.self_attn.out_proj', -'text_model.encoder.layers.13.layer_norm1', -'text_model.encoder.layers.13.mlp', -'text_model.encoder.layers.13.mlp.activation_fn', -'text_model.encoder.layers.13.mlp.fc1', -'text_model.encoder.layers.13.mlp.fc2', -'text_model.encoder.layers.13.layer_norm2', -'text_model.encoder.layers.14', -'text_model.encoder.layers.14.self_attn', -'text_model.encoder.layers.14.self_attn.k_proj', -'text_model.encoder.layers.14.self_attn.v_proj', -'text_model.encoder.layers.14.self_attn.q_proj', -'text_model.encoder.layers.14.self_attn.out_proj', -'text_model.encoder.layers.14.layer_norm1', -'text_model.encoder.layers.14.mlp', -'text_model.encoder.layers.14.mlp.activation_fn', -'text_model.encoder.layers.14.mlp.fc1', -'text_model.encoder.layers.14.mlp.fc2', -'text_model.encoder.layers.14.layer_norm2', -'text_model.encoder.layers.15', -'text_model.encoder.layers.15.self_attn', -'text_model.encoder.layers.15.self_attn.k_proj', -'text_model.encoder.layers.15.self_attn.v_proj', -'text_model.encoder.layers.15.self_attn.q_proj', -'text_model.encoder.layers.15.self_attn.out_proj', -'text_model.encoder.layers.15.layer_norm1', -'text_model.encoder.layers.15.mlp', -'text_model.encoder.layers.15.mlp.activation_fn', -'text_model.encoder.layers.15.mlp.fc1', -'text_model.encoder.layers.15.mlp.fc2', -'text_model.encoder.layers.15.layer_norm2', -'text_model.encoder.layers.16', -'text_model.encoder.layers.16.self_attn', -'text_model.encoder.layers.16.self_attn.k_proj', -'text_model.encoder.layers.16.self_attn.v_proj', -'text_model.encoder.layers.16.self_attn.q_proj', -'text_model.encoder.layers.16.self_attn.out_proj', -'text_model.encoder.layers.16.layer_norm1', -'text_model.encoder.layers.16.mlp', -'text_model.encoder.layers.16.mlp.activation_fn', -'text_model.encoder.layers.16.mlp.fc1', -'text_model.encoder.layers.16.mlp.fc2', -'text_model.encoder.layers.16.layer_norm2', -'text_model.encoder.layers.17', -'text_model.encoder.layers.17.self_attn', -'text_model.encoder.layers.17.self_attn.k_proj', -'text_model.encoder.layers.17.self_attn.v_proj', -'text_model.encoder.layers.17.self_attn.q_proj', -'text_model.encoder.layers.17.self_attn.out_proj', -'text_model.encoder.layers.17.layer_norm1', -'text_model.encoder.layers.17.mlp', -'text_model.encoder.layers.17.mlp.activation_fn', -'text_model.encoder.layers.17.mlp.fc1', -'text_model.encoder.layers.17.mlp.fc2', -'text_model.encoder.layers.17.layer_norm2', -'text_model.encoder.layers.18', -'text_model.encoder.layers.18.self_attn', -'text_model.encoder.layers.18.self_attn.k_proj', -'text_model.encoder.layers.18.self_attn.v_proj', -'text_model.encoder.layers.18.self_attn.q_proj', -'text_model.encoder.layers.18.self_attn.out_proj', -'text_model.encoder.layers.18.layer_norm1', -'text_model.encoder.layers.18.mlp', -'text_model.encoder.layers.18.mlp.activation_fn', -'text_model.encoder.layers.18.mlp.fc1', -'text_model.encoder.layers.18.mlp.fc2', -'text_model.encoder.layers.18.layer_norm2', -'text_model.encoder.layers.19', -'text_model.encoder.layers.19.self_attn', -'text_model.encoder.layers.19.self_attn.k_proj', -'text_model.encoder.layers.19.self_attn.v_proj', -'text_model.encoder.layers.19.self_attn.q_proj', -'text_model.encoder.layers.19.self_attn.out_proj', -'text_model.encoder.layers.19.layer_norm1', -'text_model.encoder.layers.19.mlp', -'text_model.encoder.layers.19.mlp.activation_fn', -'text_model.encoder.layers.19.mlp.fc1', -'text_model.encoder.layers.19.mlp.fc2', -'text_model.encoder.layers.19.layer_norm2', -'text_model.encoder.layers.20', -'text_model.encoder.layers.20.self_attn', -'text_model.encoder.layers.20.self_attn.k_proj', -'text_model.encoder.layers.20.self_attn.v_proj', -'text_model.encoder.layers.20.self_attn.q_proj', -'text_model.encoder.layers.20.self_attn.out_proj', -'text_model.encoder.layers.20.layer_norm1', -'text_model.encoder.layers.20.mlp', -'text_model.encoder.layers.20.mlp.activation_fn', -'text_model.encoder.layers.20.mlp.fc1', -'text_model.encoder.layers.20.mlp.fc2', -'text_model.encoder.layers.20.layer_norm2', -'text_model.encoder.layers.21', -'text_model.encoder.layers.21.self_attn', -'text_model.encoder.layers.21.self_attn.k_proj', -'text_model.encoder.layers.21.self_attn.v_proj', -'text_model.encoder.layers.21.self_attn.q_proj', -'text_model.encoder.layers.21.self_attn.out_proj', -'text_model.encoder.layers.21.layer_norm1', -'text_model.encoder.layers.21.mlp', -'text_model.encoder.layers.21.mlp.activation_fn', -'text_model.encoder.layers.21.mlp.fc1', -'text_model.encoder.layers.21.mlp.fc2', -'text_model.encoder.layers.21.layer_norm2', -'text_model.encoder.layers.22', -'text_model.encoder.layers.22.self_attn', -'text_model.encoder.layers.22.self_attn.k_proj', -'text_model.encoder.layers.22.self_attn.v_proj', -'text_model.encoder.layers.22.self_attn.q_proj', -'text_model.encoder.layers.22.self_attn.out_proj', -'text_model.encoder.layers.22.layer_norm1', -'text_model.encoder.layers.22.mlp', -'text_model.encoder.layers.22.mlp.activation_fn', -'text_model.encoder.layers.22.mlp.fc1', -'text_model.encoder.layers.22.mlp.fc2', -'text_model.encoder.layers.22.layer_norm2', -'text_model.encoder.layers.23', -'text_model.encoder.layers.23.self_attn', -'text_model.encoder.layers.23.self_attn.k_proj', -'text_model.encoder.layers.23.self_attn.v_proj', -'text_model.encoder.layers.23.self_attn.q_proj', -'text_model.encoder.layers.23.self_attn.out_proj', -'text_model.encoder.layers.23.layer_norm1', -'text_model.encoder.layers.23.mlp', -'text_model.encoder.layers.23.mlp.activation_fn', -'text_model.encoder.layers.23.mlp.fc1', -'text_model.encoder.layers.23.mlp.fc2', -'text_model.encoder.layers.23.layer_norm2', -'text_model.encoder.layers.24', -'text_model.encoder.layers.24.self_attn', -'text_model.encoder.layers.24.self_attn.k_proj', -'text_model.encoder.layers.24.self_attn.v_proj', -'text_model.encoder.layers.24.self_attn.q_proj', -'text_model.encoder.layers.24.self_attn.out_proj', -'text_model.encoder.layers.24.layer_norm1', -'text_model.encoder.layers.24.mlp', -'text_model.encoder.layers.24.mlp.activation_fn', -'text_model.encoder.layers.24.mlp.fc1', -'text_model.encoder.layers.24.mlp.fc2', -'text_model.encoder.layers.24.layer_norm2', -'text_model.encoder.layers.25', -'text_model.encoder.layers.25.self_attn', -'text_model.encoder.layers.25.self_attn.k_proj', -'text_model.encoder.layers.25.self_attn.v_proj', -'text_model.encoder.layers.25.self_attn.q_proj', -'text_model.encoder.layers.25.self_attn.out_proj', -'text_model.encoder.layers.25.layer_norm1', -'text_model.encoder.layers.25.mlp', -'text_model.encoder.layers.25.mlp.activation_fn', -'text_model.encoder.layers.25.mlp.fc1', -'text_model.encoder.layers.25.mlp.fc2', -'text_model.encoder.layers.25.layer_norm2', -'text_model.encoder.layers.26', -'text_model.encoder.layers.26.self_attn', -'text_model.encoder.layers.26.self_attn.k_proj', -'text_model.encoder.layers.26.self_attn.v_proj', -'text_model.encoder.layers.26.self_attn.q_proj', -'text_model.encoder.layers.26.self_attn.out_proj', -'text_model.encoder.layers.26.layer_norm1', -'text_model.encoder.layers.26.mlp', -'text_model.encoder.layers.26.mlp.activation_fn', -'text_model.encoder.layers.26.mlp.fc1', -'text_model.encoder.layers.26.mlp.fc2', -'text_model.encoder.layers.26.layer_norm2', -'text_model.encoder.layers.27', -'text_model.encoder.layers.27.self_attn', -'text_model.encoder.layers.27.self_attn.k_proj', -'text_model.encoder.layers.27.self_attn.v_proj', -'text_model.encoder.layers.27.self_attn.q_proj', -'text_model.encoder.layers.27.self_attn.out_proj', -'text_model.encoder.layers.27.layer_norm1', -'text_model.encoder.layers.27.mlp', -'text_model.encoder.layers.27.mlp.activation_fn', -'text_model.encoder.layers.27.mlp.fc1', -'text_model.encoder.layers.27.mlp.fc2', -'text_model.encoder.layers.27.layer_norm2', -'text_model.encoder.layers.28', -'text_model.encoder.layers.28.self_attn', -'text_model.encoder.layers.28.self_attn.k_proj', -'text_model.encoder.layers.28.self_attn.v_proj', -'text_model.encoder.layers.28.self_attn.q_proj', -'text_model.encoder.layers.28.self_attn.out_proj', -'text_model.encoder.layers.28.layer_norm1', -'text_model.encoder.layers.28.mlp', -'text_model.encoder.layers.28.mlp.activation_fn', -'text_model.encoder.layers.28.mlp.fc1', -'text_model.encoder.layers.28.mlp.fc2', -'text_model.encoder.layers.28.layer_norm2', -'text_model.encoder.layers.29', -'text_model.encoder.layers.29.self_attn', -'text_model.encoder.layers.29.self_attn.k_proj', -'text_model.encoder.layers.29.self_attn.v_proj', -'text_model.encoder.layers.29.self_attn.q_proj', -'text_model.encoder.layers.29.self_attn.out_proj', -'text_model.encoder.layers.29.layer_norm1', -'text_model.encoder.layers.29.mlp', -'text_model.encoder.layers.29.mlp.activation_fn', -'text_model.encoder.layers.29.mlp.fc1', -'text_model.encoder.layers.29.mlp.fc2', -'text_model.encoder.layers.29.layer_norm2', -'text_model.encoder.layers.30', -'text_model.encoder.layers.30.self_attn', -'text_model.encoder.layers.30.self_attn.k_proj', -'text_model.encoder.layers.30.self_attn.v_proj', -'text_model.encoder.layers.30.self_attn.q_proj', -'text_model.encoder.layers.30.self_attn.out_proj', -'text_model.encoder.layers.30.layer_norm1', -'text_model.encoder.layers.30.mlp', -'text_model.encoder.layers.30.mlp.activation_fn', -'text_model.encoder.layers.30.mlp.fc1', -'text_model.encoder.layers.30.mlp.fc2', -'text_model.encoder.layers.30.layer_norm2', -'text_model.encoder.layers.31', -'text_model.encoder.layers.31.self_attn', -'text_model.encoder.layers.31.self_attn.k_proj', -'text_model.encoder.layers.31.self_attn.v_proj', -'text_model.encoder.layers.31.self_attn.q_proj', -'text_model.encoder.layers.31.self_attn.out_proj', -'text_model.encoder.layers.31.layer_norm1', -'text_model.encoder.layers.31.mlp', -'text_model.encoder.layers.31.mlp.activation_fn', -'text_model.encoder.layers.31.mlp.fc1', -'text_model.encoder.layers.31.mlp.fc2', -'text_model.encoder.layers.31.layer_norm2', -'text_model.final_layer_norm', -'text_projection'] \ No newline at end of file diff --git a/modules/Experiments/utils.py b/modules/Experiments/utils.py deleted file mode 100644 index 6df7c0c..0000000 --- a/modules/Experiments/utils.py +++ /dev/null @@ -1,200 +0,0 @@ -import torch -from PIL import Image - -def get_clip_prompt_embeds(prompt, tokenizer, text_encoder, clip_skip=None, noise=0.0, scale=1.0): - max_length = tokenizer.model_max_length - device = text_encoder.device - bos = torch.tensor([tokenizer.bos_token_id], device=device).unsqueeze(0) - eos = torch.tensor([tokenizer.eos_token_id], device=device).unsqueeze(0) - one = torch.tensor([1], device=device).unsqueeze(0) - pad = tokenizer.pad_token_id - - text_input_ids = tokenizer(prompt, truncation=False, return_tensors="pt").input_ids.to(device) - - # remove start and end tokens - text_input_ids = text_input_ids[:, 1:-1] - - # we create chunks of max_length-2, we add start and end tokens back later - chunks = text_input_ids.split(max_length-2, dim=-1) - - concat_embeds = [] - pooled_prompt_embeds = None - for chunk in chunks: - mask = torch.ones_like(chunk) - - # add start and end tokens to each chunk - chunk = torch.cat([bos, chunk, eos], dim=-1) - mask = torch.cat([one, mask, one], dim=-1) - - # pad the chunk to the max length - if chunk.shape[-1] < max_length: - mask = torch.nn.functional.pad(mask, (0, max_length - mask.shape[-1]), value=0) - chunk = torch.nn.functional.pad(chunk, (0, max_length - chunk.shape[-1]), value=pad) - - # encode the tokenized text - prompt_embeds = text_encoder(chunk, attention_mask=mask, output_hidden_states=True) - - if pooled_prompt_embeds is None: - pooled_prompt_embeds = prompt_embeds[0] - - if clip_skip is None: - prompt_embeds = prompt_embeds.hidden_states[-2] - else: - prompt_embeds = prompt_embeds.hidden_states[-(clip_skip + 2)] - - concat_embeds.append(prompt_embeds) - - prompt_embeds = torch.cat(concat_embeds, dim=1) - del text_encoder, bos, eos, one, pad, text_input_ids, chunks, concat_embeds, mask, chunk - - if scale != 1.0: - prompt_embeds = prompt_embeds * scale - pooled_prompt_embeds = pooled_prompt_embeds * scale - - if noise > 0.0: - generator_state = torch.get_rng_state() - - seed = int(prompt_embeds.mean().item() * 1e6) % (2**32 - 1) - torch.manual_seed(seed) - embed_noise = torch.randn_like(prompt_embeds) * prompt_embeds.abs().mean() * noise - #embed_noise = torch.randn_like(prompt_embeds) * noise - prompt_embeds = prompt_embeds + embed_noise - - seed = int(pooled_prompt_embeds.mean().item() * 1e6) % (2**32 - 1) - torch.manual_seed(seed) - embed_noise = torch.randn_like(pooled_prompt_embeds) * pooled_prompt_embeds.abs().mean() * noise - #embed_noise = torch.randn_like(pooled_prompt_embeds) * noise - pooled_prompt_embeds = pooled_prompt_embeds + embed_noise - - torch.set_rng_state(generator_state) - - prompt_embeds = prompt_embeds.to('cpu').detach().clone() - pooled_prompt_embeds = pooled_prompt_embeds.to('cpu').detach().clone() - - return prompt_embeds, pooled_prompt_embeds - - -def get_t5_prompt_embeds(prompt, tokenizer, text_encoder, max_sequence_length=256, noise=0.0): - prompt = [prompt] if isinstance(prompt, str) else prompt - - # could be tokenizer.model_max_length but we are using a more conservative value (256) - max_length = max_sequence_length - device = text_encoder.device - eos = torch.tensor([1], device=device).unsqueeze(0) - pad = 0 # pad token is 0 - - text_inputs_ids = tokenizer(prompt, truncation = False, add_special_tokens=True, return_tensors="pt").input_ids.to(device) - - # remove end token - text_inputs_ids = text_inputs_ids[:, :-1] - - chunks = text_inputs_ids.split(max_length-1, dim=-1) - - concat_embeds = [] - for chunk in chunks: - mask = torch.ones_like(chunk) - - # add end token back - chunk = torch.cat([chunk, eos], dim=-1) - mask = torch.cat([mask, eos], dim=-1) - - # pad the chunk to the max length - if chunk.shape[-1] < max_length: - mask = torch.nn.functional.pad(mask, (0, max_length - mask.shape[-1]), value=0) - chunk = torch.nn.functional.pad(chunk, (0, max_length - chunk.shape[-1]), value=pad) - - # encode the tokenized text - prompt_embeds = text_encoder(chunk)[0] - concat_embeds.append(prompt_embeds) - - prompt_embeds = torch.cat(concat_embeds, dim=1) - del text_encoder, eos, pad, text_inputs_ids, chunks, concat_embeds, mask, chunk - - if noise > 0.0: - generator_state = torch.get_rng_state() - seed = int(prompt_embeds.mean().item() * 1e6) % (2**32 - 1) - torch.manual_seed(seed) - embed_noise = torch.randn_like(prompt_embeds) * prompt_embeds.abs().mean() * noise - prompt_embeds = prompt_embeds + embed_noise - torch.set_rng_state(generator_state) - - prompt_embeds = prompt_embeds.to('cpu').detach().clone() - - return prompt_embeds - -def upcast_vae(model): - from diffusers.models.attention_processor import AttnProcessor2_0, XFormersAttnProcessor - - dtype = model.dtype - if torch.cuda.is_available() and torch.cuda.is_bf16_supported(): - new_dtype = torch.bfloat16 - else: - new_dtype = torch.float32 - - model = model.to(dtype=new_dtype) - use_torch_2_0_or_xformers = isinstance( - model.decoder.mid_block.attentions[0].processor, - ( - AttnProcessor2_0, - XFormersAttnProcessor, - ), - ) - # if xformers or torch_2_0 is used attention block does not need - # to be in float32 which can save lots of memory - if use_torch_2_0_or_xformers: - model.post_quant_conv.to(dtype) - model.decoder.conv_in.to(dtype) - model.decoder.mid_block.to(dtype) - - return model - -def sd3_latents_to_rgb(latents: torch.Tensor): - if latents is None: - return None - - if latents.dim() == 4: - latents = latents[0] - - scale_factor = 1.5305 - shift_factor = 0.0609 - - # The SD3 latent_rgb_factors matrix - latent_rgb_factors = torch.tensor([ - [-0.0645, 0.0177, 0.1052], - [ 0.0028, 0.0312, 0.0650], - [ 0.1848, 0.0762, 0.0360], - [ 0.0944, 0.0360, 0.0889], - [ 0.0897, 0.0506, -0.0364], - [-0.0020, 0.1203, 0.0284], - [ 0.0855, 0.0118, 0.0283], - [-0.0539, 0.0658, 0.1047], - [-0.0057, 0.0116, 0.0700], - [-0.0412, 0.0281, -0.0039], - [ 0.1106, 0.1171, 0.1220], - [-0.0248, 0.0682, -0.0481], - [ 0.0815, 0.0846, 0.1207], - [-0.0120, -0.0055, -0.0867], - [-0.0749, -0.0634, -0.0456], - [-0.1418, -0.1457, -0.1259] - ], dtype=latents.dtype, device=latents.device) - - latents = latents.permute(1, 2, 0) - latents = (latents - shift_factor) / scale_factor - - # Perform the linear transformation - rgb_pixels = latents @ latent_rgb_factors - - rgb_pixels = rgb_pixels.permute(2, 0, 1) - - rgb_pixels = rgb_pixels.float() - - # Clamp values to the global percentile range and normalize - q_005 = torch.quantile(rgb_pixels, 0.005) - q_995 = torch.quantile(rgb_pixels, 0.995) - image_tensor = torch.clamp(rgb_pixels, q_005, q_995) - image_tensor = (image_tensor - q_005) / (q_995 - q_005).add(1e-6) - - image = image_tensor.mul(255).byte().cpu().numpy().transpose(1, 2, 0) - - image = Image.fromarray(image) - return image \ No newline at end of file diff --git a/modules/Image/main.py b/modules/Image/main.py index f2f2495..bc94f17 100644 --- a/modules/Image/main.py +++ b/modules/Image/main.py @@ -1,16 +1,50 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. from modiff.NodeBase import NodeBase -from PIL import Image +from PIL import Image, ImageColor, ImageOps from modiff.config import CONFIG -from pathlib import Path +from modiff.path_identifiers import resolve_runtime_input_path import logging from utils.torch_utils import DEVICE_LIST, DEFAULT_DEVICE from utils.paths import parse_filename import hashlib import json -import nanoid logger = logging.getLogger('modiff') + +def unpack_packed_latents(latents, height, width, vae_scale_factor): + """Unpack FLUX-style latent patches for direct VAE previews.""" + + batch_size, _num_patches, channels = latents.shape + height = 2 * (int(height) // int(vae_scale_factor * 2)) + width = 2 * (int(width) // int(vae_scale_factor * 2)) + latents = latents.view(batch_size, height // 2, width // 2, channels // 4, 2, 2) + latents = latents.permute(0, 3, 1, 4, 2, 5) + return latents.reshape(batch_size, channels // 4, height, width) + + +def decode_vae_latents(model, latents, size=None): + """Decode standard or packed image latents without an experimental dependency.""" + + from utils.torch_utils import TensorToImage + + if hasattr(model, 'post_quant_conv') and hasattr(model.post_quant_conv, 'parameters'): + latents = latents.to(dtype=next(iter(model.post_quant_conv.parameters())).dtype) + else: + latents = latents.to(dtype=model.dtype) + if size is not None: + latents = unpack_packed_latents( + latents, + size[0], + size[1], + 2 ** (len(model.config.block_out_channels) - 1), + ) + shift_factor = getattr(model.config, 'shift_factor', 0) or 0 + latents = (latents / model.config.scaling_factor) + shift_factor + images = model.decode(latents.to(model.device), return_dict=False)[0] + images = images / 2 + 0.5 + return TensorToImage(images.to('cpu').detach().clone()) + def collapse_single(values): return values[0] if len(values) == 1 else values @@ -73,20 +107,18 @@ def execute(self, **kwargs): try: if f.startswith("http://") or f.startswith("https://"): - import requests - from io import BytesIO - response = requests.get(f) - response.raise_for_status() - content = response.content - image = Image.open(BytesIO(content)) + from modiff.media_import import import_web_media + + imported = import_web_media(f) + content = imported.read_bytes() + image = ImageOps.exif_transpose(Image.open(imported)) source_hash = hashlib.sha256(content).hexdigest() else: - if not Path(f).is_absolute(): - f = Path(CONFIG.paths['work_dir']) / f - if not Path(f).exists(): + f = resolve_runtime_input_path(f) + if not f.exists(): continue - content = Path(f).read_bytes() - image = Image.open(f) + content = f.read_bytes() + image = ImageOps.exif_transpose(Image.open(f)) source_hash = hashlib.sha256(content).hexdigest() image.load() @@ -174,7 +206,6 @@ class Save(NodeBase): def execute(self, **kwargs): from pathlib import Path - from PIL import Image image = kwargs.get("image") if not isinstance(image, list): @@ -268,18 +299,20 @@ def execute(self, **kwargs): # if image is an Image or an array of Images, pass it to the preview if not (isinstance(image, Image.Image) or (isinstance(image, list) and len(image) > 0 and isinstance(image[0], Image.Image))): - from modules.Experiments.VAE import VAEDecode pipeline = kwargs["vae"] device = kwargs["device"] if pipeline is None: logger.error("VAE is required to decode latents") - return {"output": None} + return {"output": None, "filtered": None} pipeline = pipeline.vae if hasattr(pipeline, 'vae') else pipeline - vae = VAEDecode() if isinstance(image, list) or isinstance(image, tuple): - output = self.mm_exec(lambda: vae.decode(pipeline, image[0], image[1]), device, models=[pipeline]) + output = self.mm_exec( + lambda: decode_vae_latents(pipeline, image[0], image[1]), + device, + models=[pipeline], + ) else: - output = self.mm_exec(lambda: vae.decode(pipeline, image), device, models=[pipeline]) + output = self.mm_exec(lambda: decode_vae_latents(pipeline, image), device, models=[pipeline]) filtered = output if export != "": @@ -511,3 +544,92 @@ def extend_list(lst, target_len): output.append(blended) return {"output": output} + + +class ImageGrid(NodeBase): + label = "Image Grid" + category = "image" + resizable = True + params = { + "images": {"label": "Images", "display": "input", "type": "image"}, + "columns": {"label": "Columns", "type": "int", "default": 2, "min": 1, "max": 100}, + "cell_width": {"label": "Cell Width (0 = auto)", "type": "int", "default": 0, "min": 0}, + "cell_height": {"label": "Cell Height (0 = auto)", "type": "int", "default": 0, "min": 0}, + "gap": {"label": "Gap", "type": "int", "default": 8, "min": 0, "max": 256}, + "background": {"label": "Background", "type": "string", "default": "#111111"}, + "fit": {"label": "Fit", "type": "string", "options": ["contain", "cover", "stretch"], "default": "contain"}, + "grid": {"label": "Grid", "display": "output", "type": "image"}, + "rows": {"label": "Rows", "display": "output", "type": "int"}, + "column_count": {"label": "Columns", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + images = kwargs.get("images") + images = images if isinstance(images, (list, tuple)) else [images] + images = [image for image in images if isinstance(image, Image.Image)] + if not images: + raise ValueError("Image Grid needs at least one image.") + columns = max(1, min(len(images), int(kwargs.get("columns") or 1))) + rows = (len(images) + columns - 1) // columns + cell_width = int(kwargs.get("cell_width") or 0) or max(image.width for image in images) + cell_height = int(kwargs.get("cell_height") or 0) or max(image.height for image in images) + gap = max(0, int(kwargs.get("gap") or 0)) + try: + background = ImageColor.getcolor(str(kwargs.get("background") or "#111111"), "RGBA") + except ValueError as exc: + raise ValueError("Image Grid background must be a CSS color such as #111111.") from exc + canvas = Image.new( + "RGBA", + (columns * cell_width + (columns - 1) * gap, rows * cell_height + (rows - 1) * gap), + background, + ) + fit = str(kwargs.get("fit") or "contain") + for index, image in enumerate(images): + source = image.convert("RGBA") + if fit == "stretch": + tile = source.resize((cell_width, cell_height), Image.Resampling.LANCZOS) + elif fit == "cover": + tile = ImageOps.fit(source, (cell_width, cell_height), method=Image.Resampling.LANCZOS) + else: + tile = ImageOps.contain(source, (cell_width, cell_height), method=Image.Resampling.LANCZOS) + column = index % columns + row = index // columns + x = column * (cell_width + gap) + (cell_width - tile.width) // 2 + y = row * (cell_height + gap) + (cell_height - tile.height) // 2 + canvas.alpha_composite(tile, (x, y)) + return {"grid": canvas, "rows": rows, "column_count": columns} + + +class SplitImageGrid(NodeBase): + label = "Split Image Grid" + category = "image" + params = { + "image": {"label": "Grid", "display": "input", "type": "image"}, + "rows": {"label": "Rows", "type": "int", "default": 2, "min": 1, "max": 100}, + "columns": {"label": "Columns", "type": "int", "default": 2, "min": 1, "max": 100}, + "gap": {"label": "Gap", "type": "int", "default": 0, "min": 0, "max": 256}, + "images": {"label": "Images", "display": "output", "type": "image"}, + } + + def execute(self, **kwargs): + image = kwargs.get("image") + if isinstance(image, list): + image = image[0] if image else None + if not isinstance(image, Image.Image): + raise ValueError("Split Image Grid needs one image.") + rows = max(1, int(kwargs.get("rows") or 1)) + columns = max(1, int(kwargs.get("columns") or 1)) + gap = max(0, int(kwargs.get("gap") or 0)) + content_width = image.width - gap * (columns - 1) + content_height = image.height - gap * (rows - 1) + if content_width < columns or content_height < rows: + raise ValueError("Grid rows, columns, and gap leave no crop area.") + cell_width = content_width // columns + cell_height = content_height // rows + output = [] + for row in range(rows): + for column in range(columns): + left = column * (cell_width + gap) + top = row * (cell_height + gap) + output.append(image.crop((left, top, left + cell_width, top + cell_height))) + return {"images": output} diff --git a/modules/ImageFilters/__init__.py b/modules/ImageFilters/__init__.py index cdd24a5..f9ecc8f 100644 --- a/modules/ImageFilters/__init__.py +++ b/modules/ImageFilters/__init__.py @@ -1,4 +1,5 @@ -from utils.torch_utils import DEVICE_LIST, DEFAULT_DEVICE, CPU_DEVICE +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. +from utils.torch_utils import DEVICE_LIST, CPU_DEVICE MODULE_MAP = { "Canny": { @@ -68,4 +69,4 @@ "output": { "label": "Image", "type": "image", "display": "output" }, } } -} \ No newline at end of file +} diff --git a/modules/ImageFilters/main.py b/modules/ImageFilters/main.py index 03409bd..de0f60f 100644 --- a/modules/ImageFilters/main.py +++ b/modules/ImageFilters/main.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. from modiff.NodeBase import NodeBase from utils.torch_utils import ImageToTensor, TensorToImage @@ -127,7 +128,6 @@ def execute(self, **kwargs): @staticmethod def sharpen(image, sharpness): import torch - import torch.nn.functional as F epsilon = 1e-5 @@ -213,4 +213,4 @@ def execute(self, **kwargs): output = TensorToImage(output) - return { "output": output } \ No newline at end of file + return { "output": output } diff --git a/modules/MediaSource/__init__.py b/modules/MediaSource/__init__.py new file mode 100644 index 0000000..3a3890e --- /dev/null +++ b/modules/MediaSource/__init__.py @@ -0,0 +1 @@ +"""Reusable local and remote media-source nodes.""" diff --git a/modules/MediaSource/main.py b/modules/MediaSource/main.py new file mode 100644 index 0000000..ddfcd1c --- /dev/null +++ b/modules/MediaSource/main.py @@ -0,0 +1,73 @@ +from modiff.NodeBase import NodeBase +from modiff.media_import import import_web_media, import_youtube_media +from modiff.path_identifiers import resolve_runtime_input_path + + +class LocalMedia(NodeBase): + """Select a backend-managed image, audio file, or video file.""" + + label = "Local Media" + category = "Media sources" + params = { + "file": { + "label": "Media file", + "display": "filebrowser", + "type": "str", + "fieldOptions": {"fileTypes": ["image", "audio", "video"], "multiple": False}, + }, + "path": {"label": "Local path", "display": "output", "type": "str"}, + } + + def execute(self, **kwargs): + value = kwargs.get("file") + value = value[0] if isinstance(value, list) and value else value + path = resolve_runtime_input_path(str(value or "")).resolve() + if not path.is_file(): + raise ValueError("Local Media needs an existing image, audio file, or video file.") + return {"path": str(path)} + + +class WebMedia(NodeBase): + """Download one public HTTP(S) media file into the backend import cache.""" + + label = "Web Media" + category = "Media sources" + params = { + "url": {"label": "Media URL", "type": "str", "default": "https://"}, + "max_size_mb": {"label": "Maximum size", "type": "int", "default": 256, "min": 1, "max": 2048}, + "path": {"label": "Cached path", "display": "output", "type": "str"}, + } + + def execute(self, **kwargs): + limit = int(kwargs.get("max_size_mb") or 256) * 1024 * 1024 + return {"path": str(import_web_media(kwargs.get("url"), max_bytes=limit))} + + +class YouTubeMedia(NodeBase): + """Download one user-authorized YouTube item into the backend import cache.""" + + label = "YouTube Media" + category = "Media sources" + params = { + "url": {"label": "YouTube URL", "type": "str", "default": "https://www.youtube.com/watch?v="}, + "media_kind": {"label": "Import as", "type": "string", "options": ["video", "audio"], "default": "video"}, + "max_duration_seconds": {"label": "Maximum duration", "type": "int", "default": 1800, "min": 1, "max": 21600}, + "max_size_mb": {"label": "Maximum size", "type": "int", "default": 512, "min": 1, "max": 4096}, + "rights_confirmed": { + "label": "I have permission to use this media", + "type": "bool", + "default": False, + }, + "path": {"label": "Cached path", "display": "output", "type": "str"}, + } + + def execute(self, **kwargs): + if not kwargs.get("rights_confirmed"): + raise ValueError("Confirm that you have permission to download and use this media.") + path = import_youtube_media( + kwargs.get("url"), + media_kind=str(kwargs.get("media_kind") or "video"), + max_duration_seconds=int(kwargs.get("max_duration_seconds") or 1800), + max_bytes=int(kwargs.get("max_size_mb") or 512) * 1024 * 1024, + ) + return {"path": str(path)} diff --git a/modules/ModelArtifact/__init__.py b/modules/ModelArtifact/__init__.py index 15b6a64..216bacc 100644 --- a/modules/ModelArtifact/__init__.py +++ b/modules/ModelArtifact/__init__.py @@ -1 +1 @@ -from .main import * +from .main import * # noqa: F403 diff --git a/modules/ModelArtifact/main.py b/modules/ModelArtifact/main.py index 0a8e82b..b90ea5f 100644 --- a/modules/ModelArtifact/main.py +++ b/modules/ModelArtifact/main.py @@ -1,17 +1,124 @@ import json import logging +import gc +import hashlib +import os +import shutil +import tempfile from pathlib import Path from typing import Any from modiff.NodeBase import NodeBase from modiff.config import CONFIG -from modules.DiffusersImage.main import build_pipeline_quantization_config, normalize_component_list, pipeline_class_from_name, repo_value +from modules.DiffusersImage.main import normalize_component_list, pipeline_class_from_name, repo_value +from modules.DiffusersRuntime.main import ( + assert_runtime_quantization_full_residency, + build_quantization_config_v2, +) from utils.huggingface import local_files_only from utils.torch_utils import str_to_dtype logger = logging.getLogger("modiff") +BLACKWELL_MODES = {"torchao_mxfp8", "torchao_nvfp4"} + + +def _json_object(value: Any, label: str) -> dict[str, Any]: + if value in (None, ""): + return {} + if isinstance(value, dict): + return dict(value) + try: + parsed = json.loads(str(value)) + except json.JSONDecodeError as exc: + raise ValueError(f"{label} must be valid JSON: {exc.msg}.") from exc + if not isinstance(parsed, dict): + raise ValueError(f"{label} must be a JSON object.") + return parsed + + +def _string_list(value: Any) -> list[str]: + if value in (None, ""): + return [] + values = value.replace("\n", ",").split(",") if isinstance(value, str) else value + return list(dict.fromkeys(str(item).strip() for item in values if str(item).strip())) + + +def _selective_exclusions(model_id: str) -> list[str]: + lowered = model_id.lower() + common = ["embed", "norm_out", "proj_out"] + if "qwen" in lowered: + return [ + *common, + "img_in", + "txt_in", + "img_mod", + "txt_mod", + "add_q_proj", + "add_k_proj", + "add_v_proj", + "to_add_out", + "txt_mlp", + ] + if "ltx" in lowered: + return [ + *common, + "patch_embed", + "proj_in", + "caption_projection", + "adaln_single", + "add_q_proj", + "add_k_proj", + "add_v_proj", + "to_add_out", + ] + return common + + +def _resolve_source(model_id: str, revision: str | None) -> tuple[str | None, dict[str, Any]]: + source = Path(model_id).expanduser() + if source.exists(): + return None, {"source": "local", "license": None, "requested_revision": revision} + from huggingface_hub import HfApi + + info = HfApi(token=CONFIG.hf.get("token"), library_name="MoDiff").model_info( + model_id, + revision=revision, + files_metadata=True, + ) + sha = str(getattr(info, "sha", "") or "").strip() + if not sha: + raise RuntimeError("Hugging Face did not return an immutable source revision.") + card = getattr(info, "card_data", None) + license_name = getattr(card, "license", None) if card is not None else None + return sha, {"source": "hub", "license": license_name} + + +def _file_checksums(root: Path) -> dict[str, str]: + checksums: dict[str, str] = {} + for path in sorted(root.rglob("*")): + if not path.is_file() or path.name == "modiff_quantization_manifest.json": + continue + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""): + digest.update(chunk) + checksums[path.relative_to(root).as_posix()] = digest.hexdigest() + return checksums + + +def _blackwell_admission(mode: str) -> None: + if mode not in BLACKWELL_MODES: + return + import torch + + if not torch.cuda.is_available() or tuple(torch.cuda.get_device_capability(0)) < (10, 0): + raise ValueError(f"{mode.removeprefix('torchao_').upper()} artifact creation requires NVIDIA Blackwell (SM 10.0+).") + if torch.are_deterministic_algorithms_enabled(): + raise ValueError("Blackwell artifact creation is disabled during deterministic execution.") + + class QuantizeDiffusersComponents(NodeBase): """Create a local Diffusers artifact with selected quantized components.""" @@ -32,6 +139,7 @@ class QuantizeDiffusersComponents(NodeBase): "default": "FluxPipeline", "fieldOptions": {"noValidation": True}, }, + "source_revision": {"label": "Source Revision", "type": "string", "default": "main"}, "dtype": { "label": "DType", "type": "string", @@ -41,8 +149,29 @@ class QuantizeDiffusersComponents(NodeBase): "quantization_mode": { "label": "Quantization", "type": "string", - "options": ["bnb_4bit", "bnb_8bit", "quanto_float8", "torchao_float8"], - "default": "quanto_float8", + "options": [ + "bnb_4bit", + "bnb_8bit", + "quanto_float8", + "quanto_int8", + "torchao_float8", + "torchao_int8_weight_only", + "torchao_mxfp8", + "torchao_nvfp4", + ], + "default": "", + }, + "excluded_modules": { + "label": "Preserved Modules", + "type": "string", + "default": "", + "description": "Comma-separated module names kept at source precision.", + }, + "component_overrides": { + "label": "Component Policies (JSON)", + "display": "textarea", + "type": "text", + "default": "{}", }, "quantized_components": { "label": "Components", @@ -58,6 +187,20 @@ class QuantizeDiffusersComponents(NodeBase): "default": "{PATH:models}/quantized/{MODEL}_{QUANT}", }, "safe_serialization": {"label": "Safe serialization", "type": "bool", "default": True}, + "smoke_generation": { + "label": "Smoke Generation (JSON)", + "display": "textarea", + "type": "text", + "default": "{}", + "description": "Optional small deterministic pipeline call used before qualification.", + }, + "smoke_seed": {"label": "Smoke Seed", "type": "int", "default": 0}, + "quality_comparison": { + "label": "Quality Comparison (JSON)", + "display": "textarea", + "type": "text", + "default": "{}", + }, "artifact_path": {"label": "Artifact Path", "display": "output", "type": "str"}, "manifest": {"label": "Manifest", "display": "output", "type": "str"}, } @@ -67,16 +210,40 @@ def execute(self, **kwargs): if not model_id: raise ValueError("Quantize Diffusers Components needs a source model.") pipeline_class_name = str(kwargs.get("pipeline_class") or "FluxPipeline") + quantization_mode = str(kwargs.get("quantization_mode") or "").strip() + if not quantization_mode: + raise ValueError("Select an installed quantization backend before creating an optimized artifact.") pipeline_class = pipeline_class_from_name(pipeline_class_name) dtype = str_to_dtype(kwargs.get("dtype") or "bfloat16") - quantization_mode = str(kwargs.get("quantization_mode") or "quanto_float8") + _blackwell_admission(quantization_mode) quantized_components = normalize_component_list(kwargs.get("quantized_components")) if not quantized_components: raise ValueError("Select at least one quantized component.") - quant_config = build_pipeline_quantization_config(quantization_mode, quantized_components, dtype) + requested_revision = str(kwargs.get("source_revision") or "main").strip() or None + pinned_revision, provenance = _resolve_source(model_id, requested_revision) + excluded_modules = _string_list(kwargs.get("excluded_modules")) + if quantization_mode in BLACKWELL_MODES: + excluded_modules = list(dict.fromkeys([*excluded_modules, *_selective_exclusions(model_id)])) + component_overrides = _json_object(kwargs.get("component_overrides"), "Component policies") + quant_config, quantization_summary = build_quantization_config_v2( + backend=quantization_mode, + components=quantized_components, + dtype=dtype, + excluded_modules=excluded_modules, + component_overrides=component_overrides, + ) if quant_config is None: raise ValueError(f"Quantization mode {quantization_mode} is not available.") + residency = assert_runtime_quantization_full_residency( + model_id=model_id, + revision=pinned_revision, + quantization_config=quant_config, + device="cuda:0", + offload_mode="none", + device_map="cuda", + ) + output_template = str(kwargs.get("output_dir") or "{PATH:models}/quantized/{MODEL}_{QUANT}") safe_name = model_id.replace("/", "--") output_text = output_template.replace("{MODEL}", safe_name).replace("{QUANT}", quantization_mode) @@ -84,25 +251,73 @@ def execute(self, **kwargs): output_path = Path(output_text) if not output_path.is_absolute(): output_path = Path(CONFIG.paths["data"]) / output_path - output_path.mkdir(parents=True, exist_ok=True) + output_path.parent.mkdir(parents=True, exist_ok=True) + if output_path.exists(): + raise FileExistsError(f"Artifact output already exists: {output_path}. Choose a new versioned folder.") + staging_path = Path(tempfile.mkdtemp(prefix=f".{output_path.name}.partial-", dir=output_path.parent)) + pipeline = None + try: + self.progress(-1, phase="loading", message="Loading source model with quantization config") + pipeline = pipeline_class.from_pretrained( + model_id, + revision=pinned_revision, + torch_dtype=dtype, + quantization_config=quant_config, + device_map="cuda", + local_files_only=local_files_only(model_id), + ) + smoke_kwargs = _json_object(kwargs.get("smoke_generation"), "Smoke generation") + smoke = {"status": "not_requested", "seed": int(kwargs.get("smoke_seed") or 0)} + if smoke_kwargs: + import torch - self.progress(-1, phase="loading", message="Loading source model with quantization config") - pipeline = pipeline_class.from_pretrained( - model_id, - torch_dtype=dtype, - quantization_config=quant_config, - local_files_only=local_files_only(model_id), - ) - self.progress(-1, phase="saving", message="Saving quantized Diffusers artifact") - pipeline.save_pretrained(output_path, safe_serialization=bool(kwargs.get("safe_serialization", True))) - manifest = { - "source_model": model_id, - "pipeline_class": pipeline_class_name, - "dtype": str(dtype), - "quantization_mode": quantization_mode, - "quantized_components": quantized_components, - "artifact_path": str(output_path), - } + smoke_kwargs["generator"] = torch.Generator(device="cuda").manual_seed(smoke["seed"]) + self.progress(-1, phase="validating", message="Running deterministic smoke generation") + result = pipeline(**smoke_kwargs) + if result is None: + raise RuntimeError("Smoke generation returned no result.") + smoke["status"] = "passed" + + self.progress(-1, phase="saving", message="Saving quantized Diffusers artifact") + pipeline.save_pretrained(staging_path, safe_serialization=bool(kwargs.get("safe_serialization", True))) + checksums = _file_checksums(staging_path) + quality = _json_object(kwargs.get("quality_comparison"), "Quality comparison") + manifest = { + "schema_version": 1, + "source_model": model_id, + "source_requested_revision": requested_revision, + "source_revision": pinned_revision, + "source": provenance, + "pipeline_class": pipeline_class_name, + "dtype": str(dtype), + "quantization_mode": quantization_mode, + "quantized_components": quantized_components, + "quantization_summary": quantization_summary, + "preserved_modules": excluded_modules, + "full_residency_admission": residency, + "smoke_generation": smoke, + "quality_comparison": quality or {"status": "not_provided"}, + "qualified": smoke["status"] == "passed" and bool(quality), + "checksums": checksums, + "artifact_path": str(output_path), + } + (staging_path / "modiff_quantization_manifest.json").write_text( + json.dumps(manifest, indent=2, sort_keys=True), + encoding="utf-8", + ) + os.replace(staging_path, output_path) + finally: + if pipeline is not None: + del pipeline + gc.collect() + try: + import torch + + if torch.cuda.is_available(): + torch.cuda.empty_cache() + except Exception: + pass + if staging_path.exists(): + shutil.rmtree(staging_path) manifest_path = output_path / "modiff_quantization_manifest.json" - manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8") return {"artifact_path": str(output_path), "manifest": str(manifest_path)} diff --git a/modules/ModularDiffusers/README.md b/modules/ModularDiffusers/README.md index 1c25b09..5459759 100644 --- a/modules/ModularDiffusers/README.md +++ b/modules/ModularDiffusers/README.md @@ -1,3 +1,5 @@ + + # Modular Diffusers in MoDiff MoDiff integrates the experimental [Diffusers Modular Pipelines](https://huggingface.co/docs/diffusers/main/en/modular_diffusers/overview) APIs with its node graph. A small set of dynamic nodes can expose different model pipelines without creating a separate hardcoded node class for every model family. @@ -13,21 +15,26 @@ MoDiff integrates the experimental [Diffusers Modular Pipelines](https://hugging - **Hub-backed blocks:** supported repositories can provide Modular Diffusers configuration/code used to construct a node interface. - **Resource controls:** loaders expose supported quantization and offload modes, subject to package, model, and hardware compatibility. -MoDiff owns the pipeline configuration schema that supplies dynamic node fields and defaults. Upstream Diffusers remains responsible for model components and Modular Pipeline execution. +MoDiff adapts Diffusers' Mellon node-metadata helper to supply MoDiff dynamic fields and configuration names; the +derivation is recorded in `pipeline_schema.py` and `THIRD_PARTY_NOTICES.md`. Upstream Diffusers remains responsible +for model components and Modular Pipeline execution. ## Setup Install and validate the backend from the repository root: ```bash -uv sync --frozen -uv run python -m modiff.preflight --json --check-port 8088 --fail-on-error -uv run python main.py +./install.sh --accelerator auto +./.venv/bin/python -m modiff.preflight --json --check-port 8088 --fail-on-error +./run.sh ``` +On Windows, use `install.ps1`, `.venv\Scripts\python.exe`, and `run.ps1` as shown in the root quick start. The +managed installer owns the executable Torch profile; `uv sync` and `uv run` are intentionally unsupported. + Open . Keep the server on loopback; it has no authentication or remote-code sandbox. -Optional quantization, Nunchaku, or other acceleration paths require the matching extras described in the [root README](../../README.md#installation-profiles). +Optional quantization, Nunchaku, or other acceleration paths require the matching profiles described in the [root README](../../README.md#managed-installation-profiles). ## Start with a bundled graph @@ -73,7 +80,7 @@ Component reuse depends on compatible pipeline contracts and current cache state `Dynamic Block` combines a compatible Modular Diffusers block configuration into one graph node. Enter a supported repository ID, load its definition, inspect the generated fields, and connect any required shared components or media inputs. -For example, repositories such as `YiYiXu/FLUX.2-klein-4B-modular` have been used to demonstrate a compact prompt-to-image block. Repository availability and code can change; pin/review the intended revision before trusting it. +The shipped example uses `diffusers/FLUX.2-klein-4B-modular` at the immutable revision recorded in `data/model-artifact-catalog.json`. Repository availability and code can change; custom repositories still require an explicitly reviewed 40-character commit revision. Dynamic blocks are not arbitrary no-code plugins. They must expose a structure understood by the current Diffusers/MoDiff integration, may require remote Python code, and can fail when upstream APIs or model files change. @@ -91,7 +98,7 @@ Custom block repositories must publish MoDiff's current `modiff_pipeline_config. back to earlier extension schemas or filenames. 1. Review the repository, owner, dependencies, license, and exact commit. -2. Prefer immutable revisions rather than a moving branch. +2. Enter the reviewed 40-character commit revision; moving branches and tags are rejected. 3. Enable `trust_remote_code` only when the repository requires it and you accept that its Python executes with backend-process permissions. 4. Test on a dedicated local environment without sensitive files in `work_dir`. diff --git a/modules/ModularDiffusers/__init__.py b/modules/ModularDiffusers/__init__.py index 760b4e9..a6ffcd4 100644 --- a/modules/ModularDiffusers/__init__.py +++ b/modules/ModularDiffusers/__init__.py @@ -1,6 +1,7 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. from diffusers import ComponentsManager -from modiff.diffusers_offload import ( +from modiff.diffusers_offload import ( # noqa: F401 - preloaded for AST registry evaluation OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK, OFFLOAD_MODE_MODEL_CPU, @@ -31,138 +32,24 @@ ] SDXL_BLOCKS = [ - "down_blocks.1.attentions.0", - "down_blocks.1.attentions.1", - "down_blocks.2.attentions.0", - "down_blocks.2.attentions.1", - "mid_block.attentions.0", - "up_blocks.0.attentions.0", - "up_blocks.0.attentions.1", - "up_blocks.0.attentions.2", - "up_blocks.1.attentions.0", - "up_blocks.1.attentions.1", - "up_blocks.1.attentions.2 ", + "down_blocks.1.attentions.0.transformer_blocks", + "down_blocks.1.attentions.1.transformer_blocks", + "down_blocks.2.attentions.0.transformer_blocks", + "down_blocks.2.attentions.1.transformer_blocks", + "mid_block.attentions.0.transformer_blocks", + "up_blocks.0.attentions.0.transformer_blocks", + "up_blocks.0.attentions.1.transformer_blocks", + "up_blocks.0.attentions.2.transformer_blocks", + "up_blocks.1.attentions.0.transformer_blocks", + "up_blocks.1.attentions.1.transformer_blocks", + "up_blocks.1.attentions.2.transformer_blocks", ] -QWEN_IMAGE_BLOCKS = [ - "transformer_blocks.0.attn", - "transformer_blocks.1.attn", - "transformer_blocks.2.attn", - "transformer_blocks.3.attn", - "transformer_blocks.4.attn", - "transformer_blocks.5.attn", - "transformer_blocks.6.attn", - "transformer_blocks.7.attn", - "transformer_blocks.8.attn", - "transformer_blocks.9.attn", - "transformer_blocks.10.attn", - "transformer_blocks.11.attn", - "transformer_blocks.12.attn", - "transformer_blocks.13.attn", - "transformer_blocks.14.attn", - "transformer_blocks.15.attn", - "transformer_blocks.16.attn", - "transformer_blocks.17.attn", - "transformer_blocks.18.attn", - "transformer_blocks.19.attn", - "transformer_blocks.20.attn", - "transformer_blocks.21.attn", - "transformer_blocks.22.attn", - "transformer_blocks.23.attn", - "transformer_blocks.24.attn", - "transformer_blocks.25.attn", - "transformer_blocks.26.attn", - "transformer_blocks.27.attn", - "transformer_blocks.28.attn", - "transformer_blocks.29.attn", - "transformer_blocks.30.attn", - "transformer_blocks.31.attn", - "transformer_blocks.32.attn", - "transformer_blocks.33.attn", - "transformer_blocks.34.attn", - "transformer_blocks.35.attn", - "transformer_blocks.36.attn", - "transformer_blocks.37.attn", - "transformer_blocks.38.attn", - "transformer_blocks.39.attn", - "transformer_blocks.40.attn", - "transformer_blocks.41.attn", - "transformer_blocks.42.attn", - "transformer_blocks.43.attn", - "transformer_blocks.44.attn", - "transformer_blocks.45.attn", - "transformer_blocks.46.attn", - "transformer_blocks.47.attn", - "transformer_blocks.48.attn", - "transformer_blocks.49.attn", - "transformer_blocks.50.attn", - "transformer_blocks.51.attn", - "transformer_blocks.52.attn", - "transformer_blocks.53.attn", - "transformer_blocks.54.attn", - "transformer_blocks.55.attn", - "transformer_blocks.56.attn", - "transformer_blocks.57.attn", - "transformer_blocks.58.attn", - "transformer_blocks.59.attn", -] +QWEN_IMAGE_BLOCKS = ["transformer_blocks"] -FLUX_BLOCKS = [ - "transformer_blocks.0.attn", - "transformer_blocks.1.attn", - "transformer_blocks.2.attn", - "transformer_blocks.3.attn", - "transformer_blocks.4.attn", - "transformer_blocks.5.attn", - "transformer_blocks.6.attn", - "transformer_blocks.7.attn", - "transformer_blocks.8.attn", - "transformer_blocks.9.attn", - "transformer_blocks.10.attn", - "transformer_blocks.11.attn", - "transformer_blocks.12.attn", - "transformer_blocks.13.attn", - "transformer_blocks.14.attn", - "transformer_blocks.15.attn", - "transformer_blocks.16.attn", - "transformer_blocks.17.attn", - "transformer_blocks.18.attn", - "single_transformer_blocks.0.attn", - "single_transformer_blocks.1.attn", - "single_transformer_blocks.2.attn", - "single_transformer_blocks.3.attn", - "single_transformer_blocks.4.attn", - "single_transformer_blocks.5.attn", - "single_transformer_blocks.6.attn", - "single_transformer_blocks.7.attn", - "single_transformer_blocks.8.attn", - "single_transformer_blocks.9.attn", - "single_transformer_blocks.10.attn", - "single_transformer_blocks.11.attn", - "single_transformer_blocks.12.attn", - "single_transformer_blocks.13.attn", - "single_transformer_blocks.14.attn", - "single_transformer_blocks.15.attn", - "single_transformer_blocks.16.attn", - "single_transformer_blocks.17.attn", - "single_transformer_blocks.18.attn", - "single_transformer_blocks.19.attn", - "single_transformer_blocks.20.attn", - "single_transformer_blocks.21.attn", - "single_transformer_blocks.22.attn", - "single_transformer_blocks.23.attn", - "single_transformer_blocks.24.attn", - "single_transformer_blocks.25.attn", - "single_transformer_blocks.26.attn", - "single_transformer_blocks.27.attn", - "single_transformer_blocks.28.attn", - "single_transformer_blocks.29.attn", - "single_transformer_blocks.30.attn", - "single_transformer_blocks.31.attn", - "single_transformer_blocks.32.attn", - "single_transformer_blocks.33.attn", - "single_transformer_blocks.34.attn", - "single_transformer_blocks.35.attn", - "single_transformer_blocks.36.attn", - "single_transformer_blocks.37.attn", -] +FLUX_BLOCKS = ["transformer_blocks", "single_transformer_blocks"] + +# The static node-registry parser resolves schema constants against this +# package object. Export the Guider options so the public /nodes contract +# contains the actual mapping instead of the unresolved identifier string. +from .guiders import GUIDER_OPTIONS as GUIDER_OPTIONS # noqa: E402,F401 diff --git a/modules/ModularDiffusers/adapters.py b/modules/ModularDiffusers/adapters.py index c8812b1..10d1c17 100644 --- a/modules/ModularDiffusers/adapters.py +++ b/modules/ModularDiffusers/adapters.py @@ -1,4 +1,8 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. +import json +import hashlib import os +from pathlib import Path from modiff.NodeBase import NodeBase @@ -26,6 +30,12 @@ class Lora(NodeBase): "label": "Weight Name", "type": "string", }, + "expected_sha256": { + "label": "Expected SHA-256", + "type": "string", + "default": "", + "description": "Optional immutable hash for the selected adapter weight file.", + }, "scale": { "label": "Scale", "type": "float", @@ -35,6 +45,17 @@ class Lora(NodeBase): "max": 20, "step": 0.1, }, + "scheduler_class": { + "label": "Scheduler Class", + "type": "string", + "value": "", + }, + "scheduler_config": { + "label": "Scheduler Config (JSON)", + "type": "text", + "display": "textarea", + "value": "{}", + }, "lora": { "label": "Lora", "type": "custom_lora", @@ -42,22 +63,73 @@ class Lora(NodeBase): }, } - def execute(self, model, scale, weight_name=None): + def execute( + self, + model, + scale, + weight_name=None, + expected_sha256="", + scheduler_class="", + scheduler_config="{}", + ): if isinstance(model, dict): - lora_path = model.get("value", None) + lora_path = model.get("value") + if not lora_path: + raise ValueError("A LoRA model is required.") filename = os.path.splitext(os.path.basename(lora_path))[0] + if model.get("source") == "hub" and lora_path: + from utils.huggingface import cached_file_path + + repo_id = lora_path + if not weight_name: + parts = lora_path.split("/") + if len(parts) >= 3: + repo_id, weight_name = "/".join(parts[:2]), "/".join(parts[2:]) + if not weight_name: + raise ValueError("A Hub LoRA requires a pinned weight_name for app-managed installation.") + cached = cached_file_path(repo_id, weight_name) + if not cached: + raise FileNotFoundError( + f"LoRA {repo_id}/{weight_name} is not installed. Install the pinned file through Model Manager first." + ) + cached_path = Path(cached) + lora_path = str(cached_path.parent) + weight_name = cached_path.name + expected_sha256 = str(expected_sha256 or "").strip().lower().removeprefix("sha256:") + if expected_sha256: + digest = hashlib.sha256() + with cached_path.open("rb") as handle: + for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""): + digest.update(chunk) + if digest.hexdigest() != expected_sha256: + raise ValueError( + f"LoRA {repo_id}/{weight_name} failed its pinned SHA-256 verification. " + "Repair the adapter through Model Manager before running this graph." + ) else: lora_path = None filename = "" adapter_name = f"{filename}_{self.node_id}" - # Return the lora configuration directly, not wrapped in another dict + if isinstance(scheduler_config, str): + try: + scheduler_config = json.loads(scheduler_config or "{}") + except json.JSONDecodeError as exc: + raise ValueError(f"LoRA scheduler config must be valid JSON: {exc}") from exc + if not isinstance(scheduler_config, dict): + raise TypeError("LoRA scheduler config must decode to a JSON object.") + + # Return the LoRA configuration directly, including optional generic + # inference metadata for distilled adapters. Models without that + # metadata continue to use the repository scheduler unchanged. return { "lora": { "lora_path": lora_path, "weight_name": weight_name, "adapter_name": adapter_name, "scale": scale, + "scheduler_class": scheduler_class or None, + "scheduler_config": scheduler_config, } } diff --git a/modules/ModularDiffusers/controlnet.py b/modules/ModularDiffusers/controlnet.py index 0792db3..db452ec 100644 --- a/modules/ModularDiffusers/controlnet.py +++ b/modules/ModularDiffusers/controlnet.py @@ -1,10 +1,15 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import importlib import logging from modiff.NodeBase import NodeBase from . import components -from .modular_utils import DummyCustomPipeline, pipeline_class_to_modiff_node_config +from .modular_utils import ( + DummyCustomPipeline, + pipeline_class_from_runtime_inputs, + pipeline_class_to_modiff_node_config, +) from .utils import collect_model_ids @@ -177,7 +182,6 @@ def __init__(self, node_id=None): self._pipeline_class = None def update_node(self, values, ref): - node_params = { "model_type": { "label": "Model Type", @@ -235,6 +239,7 @@ def update_node(self, values, ref): def execute(self, **kwargs): kwargs = dict(kwargs) + self._pipeline_class = pipeline_class_from_runtime_inputs(self._pipeline_class, kwargs) # 1. Get node config blocks, node_config = pipeline_class_to_modiff_node_config(self._pipeline_class, self.node_type) @@ -243,7 +248,7 @@ def execute(self, **kwargs): return # 2. Cast parameters to the types expected by the modular pipeline. - # YiYi notes: should fix and remove in the future + # Preserve the graph compatibility cast until the upstream schema exposes exact types. for param_name, param_config in node_config["params"].items(): if param_name in kwargs and kwargs[param_name] is not None: param_type = param_config.get("type", None) @@ -300,7 +305,6 @@ def execute(self, **kwargs): elif name in kwargs and kwargs[name] is not None: controlnet_inputs.update({name: kwargs.pop(name)}) - # YiYi TODO: list controlnet as required/static model input for controlnet node controlnet_out = { "controlnet": kwargs.get("controlnet"), **controlnet_inputs, diff --git a/modules/ModularDiffusers/denoise.py b/modules/ModularDiffusers/denoise.py index 75d917d..3bc2a71 100644 --- a/modules/ModularDiffusers/denoise.py +++ b/modules/ModularDiffusers/denoise.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import importlib import inspect import logging @@ -6,13 +7,17 @@ from typing import Any, List, Tuple import torch -from diffusers import ComponentsManager -from diffusers.modular_pipelines import BlockState, InputParam, LoopSequentialPipelineBlocks, ModularPipelineBlocks +from diffusers import BaseGuidance, ComponentsManager +from diffusers.modular_pipelines import BlockState, LoopSequentialPipelineBlocks, ModularPipelineBlocks from modiff.NodeBase import NodeBase from . import MESSAGE_DURATION, components -from .modular_utils import DummyCustomPipeline, pipeline_class_to_modiff_node_config +from .modular_utils import ( + DummyCustomPipeline, + pipeline_class_from_runtime_inputs, + pipeline_class_to_modiff_node_config, +) from .utils import collect_model_ids @@ -108,9 +113,6 @@ def insert_preview_block_recursive(blocks, blocks_name, preview_block): insert_preview_block_recursive(blocks, "root", preview_block) -# SIGNAL_DATA = get_model_type_signal_data() - - class Denoise(NodeBase): label = "Denoise" category = "sampler" @@ -122,6 +124,7 @@ class Denoise(NodeBase): "label": "Denoise Model *", "display": "input", "type": "diffusers_auto_model", + "required": True, "onSignal": [ "update_node", {"action": "signal", "target": "guider"}, @@ -162,8 +165,27 @@ def __init__(self, node_id=None): self._model_type = "" self._pipeline_class = None + def _raise_if_interrupted(self): + if self._interrupt: + raise InterruptedError("Execution interrupted by the user.") + + def _publish_initial_denoise_progress(self, num_inference_steps: int): + if num_inference_steps <= 0: + return + self.progress( + 0, + phase="denoising", + message=f"Denoising 0/{num_inference_steps}", + current_step=0, + total_steps=num_inference_steps, + elapsed_seconds=0.0, + average_step_seconds=None, + eta_seconds=None, + ) + def execute(self, **kwargs): kwargs = dict(kwargs) + self._pipeline_class = pipeline_class_from_runtime_inputs(self._pipeline_class, kwargs) if not ((unet := kwargs.get("unet")) and isinstance(unet, dict)): self.notify( @@ -195,6 +217,13 @@ def execute(self, **kwargs): progress_started_at = time.monotonic() def preview_callback(_latents, step_index: int, scheduler_order: int): + # Modular pipelines do not expose the standard Diffusers + # ``callback_on_step_end`` contract used by NodeBase.pipe_callback. + # This block runs at the denoise step boundary, so it is the safe + # place to honor an app stop request without interrupting a GPU + # kernel and poisoning the accelerator context. + self._raise_if_interrupted() + if num_inference_steps <= 0 or (step_index + 1) % scheduler_order != 0: return current_step = min(num_inference_steps, (step_index + 1) // scheduler_order) @@ -217,7 +246,6 @@ def preview_callback(_latents, step_index: int, scheduler_order: int): insert_preview_block(runtime_blocks, preview_callback) self._pipeline = runtime_blocks.init_pipeline(repo_id, components_manager=components) - # YiYi Notes: take an extra step to cast the params to the correct type. # Preserve the graph compatibility cast until the upstream schema exposes exact types. for param_name, param_config in node_config["params"].items(): if param_name in kwargs and kwargs[param_name] is not None: @@ -236,10 +264,31 @@ def preview_callback(_latents, step_index: int, scheduler_order: int): target_model_names=expected_component_names, ) + component_updates = {} + explicit_guider = kwargs.get("guider") + if explicit_guider is not None: + if not isinstance(explicit_guider, BaseGuidance): + guider_type = f"{type(explicit_guider).__module__}.{type(explicit_guider).__qualname__}" + raise TypeError( + "Connected guider must be a Diffusers BaseGuidance instance; " + f"received {guider_type}." + ) + if "guider" not in self._pipeline.component_names: + raise ValueError( + f"{type(self._pipeline).__name__} does not expose a 'guider' component, " + "so the connected Diffusers guider cannot be installed." + ) + if model_ids: - components_to_update = components.get_components_by_ids(ids=model_ids, return_dict_with_names=True) - if components_to_update: - self._pipeline.update_components(**components_to_update) + managed_components = components.get_components_by_ids(ids=model_ids, return_dict_with_names=True) + if managed_components: + component_updates.update(managed_components) + + if explicit_guider is not None: + component_updates["guider"] = explicit_guider + + if component_updates: + self._pipeline.update_components(**component_updates) device = self._pipeline._execution_device @@ -260,7 +309,7 @@ def preview_callback(_latents, step_index: int, scheduler_order: int): # special case #2: passed `guidance_scale` but pipeline does not accept it # -> potentially create a new guider if pipeline support it elif name == "guidance_scale" and "guidance_scale" not in blocks.input_names: - if "guider" in self._pipeline.component_names and "guider" not in components_to_update: + if "guider" in self._pipeline.component_names and "guider" not in component_updates: guider_spec = self._pipeline.get_component_spec("guider") guider = guider_spec.create(guidance_scale=value) self._pipeline.update_components(guider=guider) @@ -308,6 +357,8 @@ def preview_callback(_latents, step_index: int, scheduler_order: int): # 6. run the pipeline and update the outputs dict with the pipeline outputs transformer = getattr(self._pipeline, "transformer", None) signature_state = restore_wrapped_forward_signature(transformer) if transformer is not None else None + self._active_pipeline = self._pipeline + self._publish_initial_denoise_progress(num_inference_steps) try: node_outputs = self._pipeline(**node_kwargs, output=output_names) except ValueError as e: @@ -338,6 +389,7 @@ def preview_callback(_latents, step_index: int, scheduler_order: int): self.notify(str(e), variant="error", persist=False, autoHideDuration=MESSAGE_DURATION) raise finally: + self._active_pipeline = None reset_wrapped_forward_signature(signature_state) outputs.update(node_outputs) diff --git a/modules/ModularDiffusers/dynamic_node.py b/modules/ModularDiffusers/dynamic_node.py index 91432c1..0c8ac59 100644 --- a/modules/ModularDiffusers/dynamic_node.py +++ b/modules/ModularDiffusers/dynamic_node.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging from diffusers import ModularPipeline @@ -11,13 +12,17 @@ OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_NONE, apply_component_group_offload, + configure_components_manager_offload, normalize_offload_mode, offload_mode_param, ) +from modiff.model_artifact_catalog import resolve_model_revision from utils.torch_utils import DEFAULT_DEVICE, DEVICE_LIST, str_to_dtype from . import MESSAGE_DURATION, components +from .loaders import record_pipeline_component_runtime_policy, reusable_component_ids from .utils import collect_model_ids +from .modular_utils import pin_modular_component_revisions, require_immutable_hub_revision logger = logging.getLogger("modiff") @@ -47,16 +52,18 @@ def send_node_definition_with_meta(self, params, label=None, header_color=None): if not self._sid or not self.node_id: return + current_server = _server() + describe = getattr(current_server, "describe_node_params", None) message = { "type": "node_definition", "node": self.node_id, - "params": params, + "params": describe(params) if callable(describe) else params, } if label: message["label"] = label if header_color: message["style"] = {"headerColor": header_color} - _server().queue_message(message, self._sid) + current_server.queue_message(message, self._sid) params = { "repo_id": { @@ -67,7 +74,7 @@ def send_node_definition_with_meta(self, params, label=None, header_color=None): "value": "", "options": { "": "", - "YiYiXu/FLUX.2-klein-4B-modular": "FLUX.2-klein-4B", + "diffusers/FLUX.2-klein-4B-modular": "FLUX.2-klein-4B", }, "fieldOptions": {"noValidation": True}, }, @@ -81,6 +88,12 @@ def send_node_definition_with_meta(self, params, label=None, header_color=None): "auto_offload": {"label": "Enable Auto Offload", "type": "boolean", "value": False}, "offload_mode": offload_mode_param(modes=[OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK]), "trust_remote_code": {"label": "Trust Remote Code", "type": "boolean", "value": False}, + "revision": { + "label": "Revision", + "type": "string", + "value": "", + "description": "Required immutable 40-character Hugging Face commit hash.", + }, "doc": { "label": "Doc", "type": "string", @@ -98,8 +111,10 @@ def __del__(self): components.remove_from_collection(comp_id, self.node_id) super().__del__() - def _get_custom_config(self, repo_id): - custom_config = PipelineConfig.load(repo_id) + def _get_custom_config(self, repo_id, revision=None): + revision = resolve_model_revision(repo_id, revision) + revision = require_immutable_hub_revision(repo_id, revision, required=True) + custom_config = PipelineConfig.load(repo_id, revision=revision) return custom_config def update_node(self, values, ref): @@ -108,7 +123,8 @@ def update_node(self, values, ref): return repo_id = values.get("repo_id", "") - custom_config = self._get_custom_config(repo_id) + revision = values.get("revision") + custom_config = self._get_custom_config(repo_id, revision) node_config = custom_config.node_params["custom"] custom_params = node_config["params"] @@ -126,8 +142,19 @@ def update_node(self, values, ref): header_color=node_color, ) - def execute(self, repo_id, device, auto_offload, trust_remote_code, offload_mode=OFFLOAD_MODE_MODEL_CPU, **kwargs): - offload_mode = normalize_offload_mode(offload_mode, auto_offload=auto_offload) + def execute( + self, + repo_id, + device, + auto_offload, + trust_remote_code, + offload_mode=OFFLOAD_MODE_MODEL_CPU, + revision=None, + **kwargs, + ): + revision = resolve_model_revision(repo_id, revision) + revision = require_immutable_hub_revision(repo_id, revision, required=True) + offload_mode = normalize_offload_mode(offload_mode, auto_offload=auto_offload, device=device) if offload_mode not in [OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK]: self.notify( f"Dynamic Modular Diffusers blocks do not support {offload_mode} offload.", @@ -145,7 +172,12 @@ def execute(self, repo_id, device, auto_offload, trust_remote_code, offload_mode try: pipeline = ModularPipeline.from_pretrained( - repo_id, trust_remote_code, components_manager=components, collection=self.node_id + repo_id, + trust_remote_code=bool(trust_remote_code), + revision=revision, + components_manager=components, + collection=self.node_id, + local_files_only=True, ) except ValueError as e: self.notify(f"{str(e)}", variant="error", persist=False, autoHideDuration=MESSAGE_DURATION) @@ -159,8 +191,10 @@ def execute(self, repo_id, device, auto_offload, trust_remote_code, offload_mode ) raise e + pin_modular_component_revisions(pipeline, repo_id, revision) + # Load config to get input/output names and dtype - custom_config = self._get_custom_config(repo_id) + custom_config = self._get_custom_config(repo_id, revision) node_config = custom_config.node_params["custom"] # Get dtype from config @@ -171,11 +205,9 @@ def execute(self, repo_id, device, auto_offload, trust_remote_code, offload_mode use_group_offload = offload_mode in [OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK] - # Enable component-manager offload before component load when requested. - if offload_mode == OFFLOAD_MODE_MODEL_CPU and (not components._auto_offload_enabled or components._auto_offload_device != device): - components.enable_auto_cpu_offload(device=device) - elif components._auto_offload_enabled: - components.disable_auto_cpu_offload() + # Configure component-manager residency before component load. CPU and + # MPS execution deliberately bypass accelerator offload hooks. + configure_components_manager_offload(components, mode=offload_mode, device=device) # Cast parameters to the types expected by the modular pipeline. for param_name, param_config in node_config["params"].items(): @@ -207,10 +239,19 @@ def execute(self, repo_id, device, auto_offload, trust_remote_code, offload_mode continue # Already provided externally comp_spec = pipeline.get_component_spec(comp_name) - comp_with_same_load_id = components._lookup_ids(load_id=comp_spec.load_id) - if comp_with_same_load_id: + comp_ids_to_reuse = reusable_component_ids( + components, + name=comp_name, + load_id=comp_spec.load_id, + dtype=torch_dtype, + requested_quantization=None, + offload_mode=offload_mode, + device=device, + node_id=self.node_id, + ) + if comp_ids_to_reuse: # Reuse existing component - comp_id = list(comp_with_same_load_id)[0] + comp_id = comp_ids_to_reuse[0] components_update_dict[comp_name] = components.get_one(component_id=comp_id) else: components_to_load.append(comp_name) @@ -237,6 +278,13 @@ def execute(self, repo_id, device, auto_offload, trust_remote_code, offload_mode elif offload_mode == "none": pipeline.to(device) + record_pipeline_component_runtime_policy( + pipeline, + offload_mode=offload_mode, + device=device, + node_id=self.node_id, + ) + # Build inputs dict inputs_dict = {} for input_name in node_config["input_names"]: diff --git a/modules/ModularDiffusers/embeddings.py b/modules/ModularDiffusers/embeddings.py index 70d77dc..a610afc 100644 --- a/modules/ModularDiffusers/embeddings.py +++ b/modules/ModularDiffusers/embeddings.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import importlib import logging @@ -6,7 +7,11 @@ from modiff.NodeBase import NodeBase from . import MESSAGE_DURATION, components -from .modular_utils import DummyCustomPipeline, pipeline_class_to_modiff_node_config +from .modular_utils import ( + DummyCustomPipeline, + pipeline_class_from_runtime_inputs, + pipeline_class_to_modiff_node_config, +) from .utils import collect_model_ids @@ -47,15 +52,8 @@ def extract_prompt_embeddings(state): if embeddings: return embeddings - fallback = { - field: _state_get(state, field) - for field in PROMPT_EMBEDDING_FIELDS - } - fallback = { - field: value - for field, value in fallback.items() - if value is not None - } + fallback = {field: _state_get(state, field) for field in PROMPT_EMBEDDING_FIELDS} + fallback = {field: value for field, value in fallback.items() if value is not None} if fallback: return fallback @@ -102,7 +100,7 @@ def update_node(self, values, ref): node_params_to_update.pop("text_encoders", None) node_params.update(**node_params_to_update) - # YiYi TODO: can we perserve the current user values in the UI for "string"/"float"/"int" params? + # The client merges this refreshed definition with the current field values. self.send_node_definition(node_params) def __init__(self, node_id=None): @@ -112,6 +110,7 @@ def __init__(self, node_id=None): def execute(self, **kwargs): kwargs = dict(kwargs) + self._pipeline_class = pipeline_class_from_runtime_inputs(self._pipeline_class, kwargs) # 1. Get node config blocks, node_config = pipeline_class_to_modiff_node_config(self._pipeline_class, self.node_type) @@ -131,7 +130,6 @@ def execute(self, **kwargs): self._pipeline = blocks.init_pipeline(repo_id, components_manager=components) - # YiYi Notes: take an extra step to cast the params to the correct type. # Preserve the graph compatibility cast until the upstream schema exposes exact types. for param_name, param_config in node_config["params"].items(): if param_name in kwargs and kwargs[param_name] is not None: @@ -262,14 +260,17 @@ def update_node(self, values, ref): def execute(self, **kwargs): kwargs = dict(kwargs) + self._pipeline_class = pipeline_class_from_runtime_inputs(self._pipeline_class, kwargs) # 1. Get node config blocks, node_config = pipeline_class_to_modiff_node_config(self._pipeline_class, self.node_type) # 2. Create pipeline repo_id = None + revision = None if (image_encoder := kwargs.get("image_encoder")) and "repo_id" in image_encoder: repo_id = image_encoder["repo_id"] + revision = image_encoder.get("revision") if repo_id is None: self.notify( @@ -300,9 +301,17 @@ def execute(self, **kwargs): target_model_names=expected_component_names, ) - # TODO: quick hack to load the image processor - spec = ComponentSpec(name="image_processor", repo=repo_id, subfolder="image_processor", variant="") - comp = spec.load() + # The image encoder contract does not currently expose its processor as + # a model input, so load the matching Diffusers component explicitly. + # Network writes remain owned by the app's download flow. + spec = ComponentSpec( + name="image_processor", + repo=repo_id, + subfolder="image_processor", + variant="", + revision=revision, + ) + comp = spec.load(local_files_only=True) comp_id = components.add("image_processor", comp, collection=self.node_id) model_ids.append(comp_id) diff --git a/modules/ModularDiffusers/guiders.py b/modules/ModularDiffusers/guiders.py index f2fd3be..fd24c57 100644 --- a/modules/ModularDiffusers/guiders.py +++ b/modules/ModularDiffusers/guiders.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging from diffusers import LayerSkipConfig, SmoothedEnergyGuidanceConfig @@ -15,34 +16,15 @@ "SmoothedEnergyGuidance": "seg_guidance_config", } -# TODO: not sure if these defaults make sense and it would be more complex with each model -DEFAULT_CONFIGS = { - "SkipLayerGuidance": { - "skip_layer_config": LayerSkipConfig( - indices=[0], - fqn="mid_block.attentions.0.transformer_blocks", - dropout=1.0, - skip_attention=False, - skip_attention_scores=True, - skip_ff=False, - ) - }, - "AutoGuidance": { - "auto_guidance_config": LayerSkipConfig( - indices=[0], - fqn="mid_block.attentions.0.transformer_blocks", - dropout=1.0, - skip_attention=False, - skip_attention_scores=True, - skip_ff=False, - ) - }, - "SmoothedEnergyGuidance": { - "seg_guidance_config": SmoothedEnergyGuidanceConfig( - indices=[0], - fqn="mid_block.attentions.0.transformer_blocks", - ) - }, +GUIDER_OPTIONS = { + "ClassifierFreeGuidance": "Classifier Free Guidance", + "SkipLayerGuidance": "Skip Layer Guidance", + "AdaptiveProjectedGuidance": "Adaptive Projected Guidance", + "ClassifierFreeZeroStarGuidance": "Classifier Free Zero Star Guidance", + "AutoGuidance": "Auto Guidance", + "SmoothedEnergyGuidance": "Smoothed Energy Guidance", + "TangentialClassifierFreeGuidance": "Tangential Classifier Free Guidance", + "FrequencyDecoupledGuidance": "Frequency Decoupled Guidance", } GUIDER_CONFIGS = { @@ -103,7 +85,8 @@ "type": "float", "value": 1.0, "min": 0.0, - "max": 10.0, + "max": 1.0, + "step": 0.01, } }, } @@ -119,16 +102,7 @@ class Guider(NodeBase): "label": "Guider", "fieldOptions": {"loading": True}, "type": "string", - "options": { - "ClassifierFreeGuidance": "Classifier Free Guidance", - "SkipLayerGuidance": "Skip Layer Guidance", - "AdaptiveProjectedGuidance": "Adaptive Projected Guidance", - "ClassifierFreeZeroStarGuidance": "Classifier Free Zero Star Guidance", - "AutoGuidance": "Auto Guidance", - "SmoothedEnergyGuidance": "Smoothed Energy Guidance", - "TangentialClassifierFreeGuidance": "Tangential Classifier Free Guidance", - "FrequencyDecoupledGuidance": "Frequency Decoupled Guidance", - }, + "options": GUIDER_OPTIONS, "value": "ClassifierFreeGuidance", "onChange": [ "updateNode", @@ -213,35 +187,74 @@ def execute(self, guider, layers_config=None, **kwargs): logger.debug(f" - guider options: {guider_options}") + if guider not in GUIDER_OPTIONS: + raise ValueError(f"Unsupported Diffusers guider: {guider!r}.") + guider_cls = getattr(__import__("diffusers", fromlist=[guider]), guider) configs = {} if guider in LAYER_CONFIG_MAPPING: - if layers_config is None: - configs[guider] = DEFAULT_CONFIGS.get(guider, {}) - else: - config_arg_name = LAYER_CONFIG_MAPPING[guider] - - if isinstance(layers_config, list): - layer_configs = [] - - for config_dict in layers_config: - if guider == "SmoothedEnergyGuidance": - layer_config = SmoothedEnergyGuidanceConfig( - indices=config_dict["indices"], fqn=config_dict["fqn"] + if layers_config is None or layers_config == []: + raise ValueError( + f"{guider} requires a non-empty Layers connection. " + "Select a transformer-block stack and at least one layer index." + ) + config_arg_name = LAYER_CONFIG_MAPPING[guider] + + if isinstance(layers_config, dict): + layers_config = [layers_config] + + if isinstance(layers_config, list): + layer_configs = [] + + for config_dict in layers_config: + if not isinstance(config_dict, dict): + expected_type = ( + SmoothedEnergyGuidanceConfig if guider == "SmoothedEnergyGuidance" else LayerSkipConfig + ) + if not isinstance(config_dict, expected_type): + raise TypeError( + f"{guider} layer entries must be mappings or {expected_type.__name__} instances." ) - else: - layer_config = LayerSkipConfig(**config_dict) - - layer_configs.append(layer_config) - - configs[guider] = {config_arg_name: layer_configs} - else: - configs[guider] = {config_arg_name: layers_config} + layer_configs.append(config_dict) + continue + + indices = config_dict.get("indices") + fqn = config_dict.get("fqn") + if ( + not isinstance(indices, list) + or not indices + or any(isinstance(index, bool) or not isinstance(index, int) or index < 0 for index in indices) + ): + raise ValueError(f"{guider} layer indices must be a non-empty list of non-negative integers.") + if not isinstance(fqn, str) or not fqn or fqn != fqn.strip(): + raise ValueError(f"{guider} requires a non-empty layer FQN without surrounding whitespace.") + + if guider == "SmoothedEnergyGuidance": + layer_config = SmoothedEnergyGuidanceConfig(indices=indices, fqn=fqn) + else: + layer_config = LayerSkipConfig(**config_dict) + + layer_configs.append(layer_config) + + configs[guider] = {config_arg_name: layer_configs} + else: + expected_type = SmoothedEnergyGuidanceConfig if guider == "SmoothedEnergyGuidance" else LayerSkipConfig + if not isinstance(layers_config, expected_type): + raise TypeError( + f"{guider} Layers input must be a mapping, list, or {expected_type.__name__} instance." + ) + configs[guider] = {config_arg_name: layers_config} options = {**guider_options} + if guider == "FrequencyDecoupledGuidance" and "guidance_scale" in options: + # The upstream constructor intentionally accepts one scale per + # frequency level under the plural ``guidance_scales`` name. Keep + # MoDiff's existing single-value control as a one-level list. + options["guidance_scales"] = [options.pop("guidance_scale")] + if guider in configs: options.update(configs[guider]) @@ -306,16 +319,24 @@ def execute(self, **kwargs): config = kwargs.get(block, {}) - indices_str = config.get("indices", "") - indices = [] - if indices_str.strip(): - indices = [int(x.strip()) for x in indices_str.split(",")] - else: + if not isinstance(block, str) or not block or block != block.strip(): + raise ValueError("Layer block names must be non-empty FQNs without surrounding whitespace.") + if not isinstance(config, dict): + raise TypeError(f"Layer configuration for {block!r} must be a mapping.") + + indices_str = str(config.get("indices", "")) + try: + indices = [int(value.strip()) for value in indices_str.split(",") if value.strip()] + except ValueError as error: + raise ValueError(f"Layer indices for {block!r} must be comma-separated integers.") from error + if not indices: indices = [0] + if any(index < 0 for index in indices): + raise ValueError(f"Layer indices for {block!r} must be non-negative.") layer_config = { "indices": indices, - "fqn": f"{block}.transformer_blocks", + "fqn": block, "dropout": config.get("dropout", 1.0), "skip_attention": config.get("skip_attention", False), "skip_attention_scores": config.get("skip_attention_scores", False), diff --git a/modules/ModularDiffusers/latents.py b/modules/ModularDiffusers/latents.py index e78dfb4..88b03c2 100644 --- a/modules/ModularDiffusers/latents.py +++ b/modules/ModularDiffusers/latents.py @@ -1,5 +1,8 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import importlib +import json import logging +import time import torch from PIL import Image @@ -7,13 +10,76 @@ from modiff.NodeBase import NodeBase from . import MESSAGE_DURATION, components -from .modular_utils import DummyCustomPipeline, pipeline_class_to_modiff_node_config +from .modular_utils import ( + DummyCustomPipeline, + pipeline_class_from_runtime_inputs, + pipeline_class_to_modiff_node_config, +) from .utils import collect_model_ids logger = logging.getLogger("modiff") +def sanitized_tensor_summary(value): + """Return JSON-safe tensor metadata without retaining or serializing data.""" + if isinstance(value, torch.Tensor): + return { + "shape": list(value.shape), + "dtype": str(value.dtype).replace("torch.", ""), + "device": str(value.device), + } + if isinstance(value, dict): + for item in value.values(): + summary = sanitized_tensor_summary(item) + if summary: + return summary + if isinstance(value, (list, tuple)): + for item in value: + summary = sanitized_tensor_summary(item) + if summary: + return summary + return None + + +def flatten_pil_images(value): + """Flatten nested Diffusers image batches without changing non-image outputs.""" + if isinstance(value, Image.Image): + return [value] + if isinstance(value, (list, tuple)): + images = [] + for item in value: + flattened = flatten_pil_images(item) + if flattened is None: + return None + images.extend(flattened) + return images + return None + + +def prepare_image_for_vae_pipeline(image, pipeline_class): + """Apply pipeline-specific source-channel contracts before VAE encoding. + + Qwen Image Layered's VAE is trained for RGBA input (`in_channels=4`) and + the upstream Diffusers example explicitly converts source media to RGBA. + MoDiff's shared image loader normally returns RGB PIL images, so preserving + that generic value here would fail only after the expensive model load. + Keep the adaptation at the VAE boundary and leave every other pipeline + unchanged. + """ + + pipeline_name = getattr(pipeline_class, "__name__", "") + if pipeline_name not in {"QwenImageLayeredModularPipeline", "QwenImageLayeredPipeline"}: + return image + if isinstance(image, Image.Image): + return image if image.mode == "RGBA" else image.convert("RGBA") + if isinstance(image, list): + return [prepare_image_for_vae_pipeline(item, pipeline_class) for item in image] + if isinstance(image, tuple): + return tuple(prepare_image_for_vae_pipeline(item, pipeline_class) for item in image) + return image + + # YiYi Notes: this is not working for qwen/flux as latents needs to be unpacked first class LatentsPreview(NodeBase): label = "Latents Preview" @@ -94,6 +160,7 @@ def update_node(self, values, ref): def execute(self, **kwargs): kwargs = dict(kwargs) + self._pipeline_class = pipeline_class_from_runtime_inputs(self._pipeline_class, kwargs) # 1. Get node config blocks, node_config = pipeline_class_to_modiff_node_config(self._pipeline_class, self.node_type) @@ -168,7 +235,12 @@ def execute(self, **kwargs): if name == "doc": outputs["doc"] = self._pipeline.blocks.doc else: - outputs[name] = node_output_state.get(name) + value = node_output_state.get(name) + if name == "images": + flattened = flatten_pil_images(value) + if flattened is not None: + value = flattened[0] if len(flattened) == 1 else flattened + outputs[name] = value return outputs @@ -181,6 +253,19 @@ class ImageEncode(NodeBase): node_type = "vae_encoder" params = { "vae": {"label": "VAE *", "display": "input", "type": "diffusers_auto_model", "onSignal": "update_node"}, + "encode_summary_data": { + "label": "Encode summary", + "display": "output", + "type": "str", + "hidden": True, + }, + "encode_summary": { + "label": "Encode summary", + "display": "ui_text", + "type": "text", + "dataSource": "encode_summary_data", + "hidden": True, + }, } def __init__(self, node_id=None): @@ -215,7 +300,17 @@ def update_node(self, values, ref): self.send_node_definition(node_params) def execute(self, **kwargs): + encode_started_at = time.perf_counter() + self.progress( + 0, + phase="encoding", + message="Encoding source image into latent representation", + current_step=0, + total_steps=1, + elapsed_seconds=0.0, + ) kwargs = dict(kwargs) + self._pipeline_class = pipeline_class_from_runtime_inputs(self._pipeline_class, kwargs) # 1. Get node config blocks, node_config = pipeline_class_to_modiff_node_config(self._pipeline_class, self.node_type) @@ -275,12 +370,15 @@ def execute(self, **kwargs): elif name in blocks.input_names: node_kwargs[name] = value + if "image" in node_kwargs: + node_kwargs["image"] = prepare_image_for_vae_pipeline(node_kwargs["image"], self._pipeline_class) + # 6. Run the pipeline try: node_output_state = self._pipeline(**node_kwargs) except ValueError as e: self.notify(str(e), variant="error", persist=False, autoHideDuration=MESSAGE_DURATION) - return None + raise # 7. Prepare outputs based on node_config["output_names"] output_names = node_config["output_names"].copy() @@ -291,4 +389,26 @@ def execute(self, **kwargs): else: outputs[name] = node_output_state.get(name) + tensor_summary = None + for name in output_names: + tensor_summary = sanitized_tensor_summary(outputs.get(name)) + if tensor_summary: + break + elapsed_seconds = round(max(0.0, time.perf_counter() - encode_started_at), 4) + outputs["encode_summary_data"] = json.dumps({ + "schemaVersion": 1, + "status": "encoded", + "updatedAt": time.time(), + "elapsedSeconds": elapsed_seconds, + **(tensor_summary or {}), + }, separators=(",", ":")) + self.progress( + 100, + phase="encoding", + message="Encoded source image", + current_step=1, + total_steps=1, + elapsed_seconds=elapsed_seconds, + ) + return outputs diff --git a/modules/ModularDiffusers/loaders.py b/modules/ModularDiffusers/loaders.py index 2760da2..cc5b622 100644 --- a/modules/ModularDiffusers/loaders.py +++ b/modules/ModularDiffusers/loaders.py @@ -1,9 +1,11 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging import traceback from collections.abc import Mapping import torch from diffusers import ComponentSpec, ModularPipeline +from diffusers.utils import logging as diffusers_logging from .pipeline_schema import MoDiffPipelineConfig as PipelineConfig from modiff.NodeBase import NodeBase @@ -15,9 +17,11 @@ OFFLOAD_MODE_NONE, apply_component_group_offload, apply_model_offload, + configure_components_manager_offload, normalize_offload_mode, offload_mode_param, ) +from modiff.model_artifact_catalog import resolve_model_revision from utils.torch_utils import DEFAULT_DEVICE, DEVICE_LIST, str_to_dtype from . import MESSAGE_DURATION, MODULAR_REGISTRY, components @@ -26,6 +30,8 @@ DummyCustomPipeline, get_all_model_types, get_model_type_metadata, + pin_modular_component_revisions, + require_immutable_hub_revision, ) @@ -58,6 +64,13 @@ def component_quant_config_summary(config): return {name: quant_config_to_info(value) for name, value in config.items()} +def should_incrementally_group_offload(*, use_group_offload, quant_config): + """Select the low-peak loader path from component capabilities, not a pipeline name.""" + return bool( + use_group_offload and quant_config and QWEN_LOW_RESOURCE_COMPONENTS.intersection(set(quant_config.keys())) + ) + + def safe_diagnostic_value(value): if value is None or isinstance(value, (str, int, float, bool)): return value @@ -77,7 +90,11 @@ def __init__(self, component_name, model_id, dtype, offload_mode, quantization, self.quantization = safe_diagnostic_value(quantization) self.original_error = original_error self.traceback_text = traceback_text - quantized_components = ", ".join(sorted(self.quantization.keys())) if isinstance(self.quantization, dict) else str(self.quantization) + quantized_components = ( + ", ".join(sorted(self.quantization.keys())) + if isinstance(self.quantization, dict) + else str(self.quantization) + ) super().__init__( f"Required Diffusers component '{component_name}' failed to load for {model_id} " f"(dtype={self.dtype}, offload={self.offload_mode}, quantized={quantized_components or 'none'}): " @@ -97,6 +114,164 @@ def component_load_kwargs_for(name, kwargs): return component_load_kwargs +def place_pipeline_components(pipeline, device, progress_callback=None): + """Place resident model components one at a time with truthful progress. + + Diffusers' pipeline-level ``to`` call iterates these same modules but gives + callers no indication which multi-gigabyte component is being copied. On + unified-memory accelerators an individual copy can take minutes, so retain + the normal module placement semantics while exposing the component name. + """ + + resident_components = [ + (name, component) for name, component in pipeline.components.items() if isinstance(component, torch.nn.Module) + ] + total = len(resident_components) + for index, (name, component) in enumerate(resident_components, start=1): + if progress_callback: + progress_callback(name, index, total) + component.to(device) + return [name for name, _component in resident_components] + + +def component_reuse_compatible( + component, + *, + dtype, + requested_quantization, + offload_mode, + device, + node_id=None, +): + """Return whether a shared component matches the complete runtime policy.""" + + if not isinstance(component, torch.nn.Module): + return True + + component_dtype = getattr(component, "dtype", None) + if component_dtype is None: + try: + component_dtype = next(component.parameters()).dtype + except StopIteration: + component_dtype = None + if component_dtype != dtype: + return False + + existing_quantizer = getattr(component, "hf_quantizer", None) + if requested_quantization is None: + if existing_quantizer is not None: + return False + elif existing_quantizer is None: + return False + else: + existing_config = getattr(existing_quantizer, "quantization_config", None) + existing_info = quant_config_to_info(existing_config) if existing_config is not None else None + if existing_info != quant_config_to_info(requested_quantization): + return False + + target_device = str(torch.device(device)) + recorded_mode = getattr(component, "_modiff_offload_mode", None) + recorded_device = getattr(component, "_modiff_execution_device", None) + if recorded_mode is not None or recorded_device is not None: + if recorded_mode != offload_mode or recorded_device != target_device: + return False + if offload_mode == OFFLOAD_MODE_GROUP_DISK: + recorded_node_id = getattr(component, "_modiff_offload_node_id", None) + if node_id is None or recorded_node_id != str(node_id): + return False + return True + + # Legacy resident components have no explicit policy metadata. Their + # current device is enough to prove compatibility only for a hook-free + # resident run; never infer compatibility for an offloaded component. + if offload_mode != OFFLOAD_MODE_NONE: + return False + try: + component_device = torch.device(component.device) + except (AttributeError, RuntimeError, TypeError, ValueError): + try: + component_device = next(component.parameters()).device + except StopIteration: + return False + return component_device == torch.device(device) + + +def reusable_component_ids( + manager, + *, + name, + load_id, + dtype, + requested_quantization, + offload_mode, + device, + node_id=None, +): + """Find deterministic, name-scoped shared components safe for this run.""" + + if not load_id or load_id == "null": + return [] + compatible_ids = [] + for component_id in sorted(manager._lookup_ids(name=name, load_id=load_id)): + component = manager.get_one(component_id=component_id) + if component_reuse_compatible( + component, + dtype=dtype, + requested_quantization=requested_quantization, + offload_mode=offload_mode, + device=device, + node_id=node_id, + ): + compatible_ids.append(component_id) + return compatible_ids + + +def record_pipeline_component_runtime_policy(pipeline, *, offload_mode, device, node_id=None): + """Annotate model components after their placement/hooks have been applied.""" + + try: + pipeline_components = pipeline.components + except (AttributeError, RuntimeError): + pipeline_components = {} + for component in pipeline_components.values(): + if not isinstance(component, torch.nn.Module): + continue + component._modiff_offload_mode = offload_mode + component._modiff_execution_device = str(torch.device(device)) + component._modiff_offload_node_id = str(node_id) if offload_mode == OFFLOAD_MODE_GROUP_DISK else None + + +def reusable_standalone_component( + manager, + *, + name, + load_id, + dtype, + offload_mode, + device, + node_id=None, +): + """Return a compatible resident standalone model, if one already exists.""" + + if not load_id or load_id == "null": + return None + for component_id in sorted(manager._lookup_ids(name=name, load_id=load_id)): + component = manager.get_one(component_id=component_id) + if not isinstance(component, torch.nn.Module): + continue + if component_reuse_compatible( + component, + dtype=dtype, + requested_quantization=None, + offload_mode=offload_mode, + device=device, + node_id=node_id, + ): + return component_id, component + + return None + + def load_components_strict( pipeline, names, @@ -133,7 +308,15 @@ def load_components_strict( raise RuntimeError(f"Required Diffusers component specs are missing: {', '.join(missing)}") logger.warning("Unknown components will be ignored: %s", unknown_names) - for name in components_to_load: + # Keep an explicit outer component bar around individually loaded modular + # components. Nested Diffusers/Transformers shard and weight bars then + # inherit the component rank, while a component with a silent placement + # phase still leaves a truthful "which component" status in the queue. + for name in diffusers_logging.tqdm( + components_to_load, + desc="Loading model components", + disable=True, + ): spec = pipeline._component_specs[name] load_kwargs = component_load_kwargs_for(name, component_load_kwargs) if ( @@ -144,7 +327,9 @@ def load_components_strict( load_kwargs.pop("trust_remote_code", None) if not spec.pretrained_model_name_or_path: - diagnostics.setdefault("components_skipped", []).append({"name": name, "reason": "no pretrained model path"}) + diagnostics.setdefault("components_skipped", []).append( + {"name": name, "reason": "no pretrained model path"} + ) continue try: @@ -257,6 +442,30 @@ def update_lora_adapters(lora_node, lora_list): lora_node.set_adapters(list(scales.keys()), list(scales.values())) +def apply_lora_scheduler_override(pipeline, lora_list): + """Apply one explicit scheduler contract supplied by distilled LoRAs.""" + if not isinstance(lora_list, list): + lora_list = [lora_list] + overrides = [ + (item.get("scheduler_class"), item.get("scheduler_config") or {}) + for item in lora_list + if item.get("scheduler_class") + ] + if not overrides: + return None + if any(override != overrides[0] for override in overrides[1:]): + raise ValueError("Connected LoRAs declare incompatible scheduler contracts.") + + scheduler_class_name, scheduler_config = overrides[0] + scheduler_class = getattr(__import__("diffusers", fromlist=[scheduler_class_name]), scheduler_class_name) + current_scheduler = getattr(pipeline, "scheduler", None) + if current_scheduler is None: + raise ValueError("The selected LoRA requires a scheduler, but the pipeline does not expose one.") + scheduler = scheduler_class.from_config(current_scheduler.config, **scheduler_config) + pipeline.update_components(scheduler=scheduler) + return scheduler + + class QuantizationConfigNode(NodeBase): label = "Quantization Config" category = "loader" @@ -366,28 +575,18 @@ def _get_model_layers(self, model_id, subfolder): import torch.nn as nn from accelerate import init_empty_weights from diffusers import AutoModel - from diffusers.pipelines.pipeline_loading_utils import ALL_IMPORTABLE_CLASSES, get_class_obj_and_candidates config = AutoModel.load_config(model_id, subfolder=subfolder) if "_class_name" not in config: raise ValueError(f"Config at {model_id}/{subfolder} doesn't contain '_class_name'") - orig_class_name = config["_class_name"] - - model_cls, _ = get_class_obj_and_candidates( - library_name="diffusers", - class_name=orig_class_name, - importable_classes=ALL_IMPORTABLE_CLASSES, - pipelines=None, - is_pipeline_module=False, - ) - - if model_cls is None: - raise ValueError(f"Could not find model class: {orig_class_name}") - with init_empty_weights(): - model = model_cls.from_config(config) + # AutoModel is the public model boundary. Reaching into the + # standard-pipeline loader internals from a Modular adapter + # couples the two pipeline systems and breaks the upstream + # separation contract. + model = AutoModel.from_config(config) # Get all Linear layer names linear_layers = [name for name, module in model.named_modules() if isinstance(module, nn.Linear)] @@ -583,6 +782,12 @@ class AutoModelLoader(NodeBase): "subfolder": {"label": "Subfolder", "type": "string", "value": ""}, "variant": {"type": "string", "value": "", "options": ["", "fp16", "bf16"]}, "trust_remote_code": {"label": "Trust Remote Code", "type": "boolean", "value": False}, + "revision": { + "label": "Revision", + "type": "string", + "value": "", + "description": "Required 40-character commit hash when Trust Remote Code is enabled.", + }, "device": {"label": "Device", "type": "string", "value": DEFAULT_DEVICE, "options": DEVICE_LIST}, "auto_offload": {"label": "Enable Auto Offload", "type": "boolean", "value": True}, "offload_mode": offload_mode_param(), @@ -613,19 +818,9 @@ def set_filters(self, values, ref): filters = ["ControlNetModel", "QwenImageControlNetModel", "FluxControlNetModel"] self.set_field_params("subfolder", {"value": ""}) - default_values = { - "": "", - "unet": "stabilityai/stable-diffusion-xl-base-1.0", - "transformer": "black-forest-labs/FLUX.1-dev", - "vae": "stabilityai/stable-diffusion-xl-base-1.0", - "controlnet": "diffusers/controlnet-depth-sdxl-1.0", - } - self.set_field_params( "model_id", { - "default": {"source": "hub", "value": default_values[model_type]}, - "value": {"source": "hub", "value": default_values[model_type]}, "fieldOptions": { "filter": { "hub": {"className": filters}, @@ -645,6 +840,7 @@ def execute( offload_mode=OFFLOAD_MODE_MODEL_CPU, variant=None, subfolder=None, + revision=None, ): logger.debug(f"AutoModelLoader ({self.node_id}) received parameters:") logger.debug(f" model_type: '{model_type}'") @@ -657,9 +853,16 @@ def execute( logger.debug(f" auto_offload: '{auto_offload}'") logger.debug(f" offload_mode: '{offload_mode}'") + supported_model_types = {"unet", "transformer", "vae", "controlnet"} + if model_type not in supported_model_types: + raise ValueError( + "AutoModelLoader requires a component type of unet, transformer, vae, or controlnet; " + f"received {model_type!r}. Rebuild or repair the managed graph before loading model weights." + ) + if isinstance(model_id, dict): real_model_id = model_id.get("value", model_id) - _source = model_id.get("source", "hub") # TODO: do something when is local? + _source = model_id.get("source", "hub") else: real_model_id = "" @@ -672,26 +875,77 @@ def execute( ) return None + revision = resolve_model_revision(real_model_id, revision, source=_source) + revision = require_immutable_hub_revision( + real_model_id, + revision, + required=bool(trust_remote_code), + ) + # Normalize parameters variant = None if variant == "" else variant subfolder = None if subfolder == "" else subfolder - spec = ComponentSpec(name=model_type, repo=real_model_id, subfolder=subfolder, variant=variant) - model = spec.load(torch_dtype=dtype, trust_remote_code=trust_remote_code) - normalized_offload_mode = normalize_offload_mode(offload_mode, auto_offload=bool(auto_offload)) - offload_result = apply_model_offload( - model, - component_name=model_type, - mode=normalized_offload_mode, + normalized_offload_mode = normalize_offload_mode( + offload_mode, + auto_offload=bool(auto_offload), + device=device, + ) + spec = ComponentSpec( + name=model_type, + repo=real_model_id, + subfolder=subfolder, + variant=variant, + revision=revision, + ) + reusable = reusable_standalone_component( + components, + name=model_type, + load_id=spec.load_id, + dtype=dtype, + offload_mode=normalized_offload_mode, device=device, node_id=self.node_id, - scope="modular-auto-model", ) + if reusable: + _existing_id, model = reusable + self.progress( + 99, + phase="loading", + message=f"Reusing resident {model_type} from {real_model_id}", + ) + offload_result = None + else: + self.progress( + 0, + phase="loading", + message=f"Loading {model_type} weights from {real_model_id}", + ) + with self.diffusers_loading_progress(): + model = spec.load(torch_dtype=dtype, trust_remote_code=trust_remote_code) + self.progress( + 99, + phase="component_placement", + message=f"Placing {model_type} on {device}; this one-time accelerator copy can take several minutes", + ) + offload_result = apply_model_offload( + model, + component_name=model_type, + mode=normalized_offload_mode, + device=device, + node_id=self.node_id, + scope="modular-auto-model", + ) + model._modiff_offload_mode = normalized_offload_mode + model._modiff_execution_device = str(torch.device(device)) + model._modiff_offload_node_id = ( + str(self.node_id) if normalized_offload_mode == OFFLOAD_MODE_GROUP_DISK else None + ) logger.debug( " AutoModelLoader: applied %s via %s to %s", - offload_result.mode, - offload_result.method, - offload_result.components, + offload_result.mode if offload_result else normalized_offload_mode, + offload_result.method if offload_result else "resident_reuse", + offload_result.components if offload_result else [model_type], ) comp_id = components.add(model_type, model, collection=self.node_id) logger.debug(f" AutoModelLoader: comp_id added: {comp_id}") @@ -699,6 +953,8 @@ def execute( model = components.get_model_info(comp_id) model["repo_id"] = real_model_id + model["revision"] = revision + model["trust_remote_code"] = bool(trust_remote_code) return {"model": model} @@ -745,8 +1001,16 @@ class ModelsLoader(NodeBase): }, "device": {"label": "Device", "type": "string", "value": DEFAULT_DEVICE, "options": DEVICE_LIST}, "trust_remote_code": {"label": "Trust Remote Code", "type": "boolean", "value": False}, + "revision": { + "label": "Revision", + "type": "string", + "value": "", + "description": "Required 40-character commit hash for custom or trusted remote code.", + }, "auto_offload": {"label": "Enable Auto Offload", "type": "boolean", "value": True}, - "offload_mode": offload_mode_param(modes=[OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK]), + "offload_mode": offload_mode_param( + modes=[OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK] + ), "unet": {"label": "Denoise Model", "display": "input", "type": "diffusers_auto_model"}, "vae": {"label": "VAE", "display": "input", "type": "diffusers_auto_model"}, "lora_list": {"label": "Lora", "display": "input", "type": "custom_lora"}, @@ -780,19 +1044,15 @@ def set_filters(self, values, ref): metadata = get_model_type_metadata(model_type) if metadata: - default_repo = metadata["default_repo"] default_dtype = metadata["default_dtype"] else: # Fallback for empty or unknown model types - default_repo = "" default_dtype = "float16" - filters = [model_type] # YiYi Notes: 1:1 between model_type <-> modular pipeline class + filters = [model_type] # Model types map one-to-one to modular pipeline classes. self.set_field_params( "repo_id", { - "default": {"source": "hub", "value": default_repo}, - "value": {"source": "hub", "value": default_repo}, "fieldOptions": { "filter": {"hub": {"className": filters}}, }, @@ -813,9 +1073,14 @@ def execute( auto_offload=True, offload_mode=OFFLOAD_MODE_MODEL_CPU, quant_config=None, + revision=None, ): requested_offload_mode = offload_mode - offload_mode = normalize_offload_mode(offload_mode, auto_offload=auto_offload) + offload_mode = normalize_offload_mode( + offload_mode, + auto_offload=auto_offload, + device=device, + ) self._loader_diagnostics = { "node_id": self.node_id, "loader": "ModelsLoader", @@ -839,7 +1104,12 @@ def execute( "components": [], }, } - if offload_mode not in [OFFLOAD_MODE_NONE, OFFLOAD_MODE_MODEL_CPU, OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK]: + if offload_mode not in [ + OFFLOAD_MODE_NONE, + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ]: self.notify( f"Modular Diffusers ModelsLoader does not support {offload_mode} offload.", variant="error", @@ -871,7 +1141,6 @@ def execute( - model_type: {model_type} """) - # TODO: add custom text encoders (depending on architecture) components_to_update = {} if unet: @@ -885,7 +1154,7 @@ def execute( if isinstance(repo_id, dict): real_repo_id = repo_id.get("value", repo_id) - _source = repo_id.get("source", "hub") # TODO: do something when is local? + _source = repo_id.get("source", "hub") else: real_repo_id = "" @@ -898,31 +1167,53 @@ def execute( ) return None self._loader_diagnostics["repo_id"] = real_repo_id + revision = resolve_model_revision( + real_repo_id, + revision, + model_type=model_type, + source=_source, + ) + revision = require_immutable_hub_revision( + real_repo_id, + revision, + required=bool(trust_remote_code) or model_type == "DummyCustomPipeline", + ) + self._loader_diagnostics["revision"] = revision use_group_offload = offload_mode in [OFFLOAD_MODE_GROUP_CPU, OFFLOAD_MODE_GROUP_DISK] - if offload_mode == OFFLOAD_MODE_MODEL_CPU: - if not components._auto_offload_enabled or components._auto_offload_device != device: - components.enable_auto_cpu_offload(device=device) - elif components._auto_offload_enabled: - components.disable_auto_cpu_offload() + configure_components_manager_offload(components, mode=offload_mode, device=device) self.loader = ModularPipeline.from_pretrained( - real_repo_id, components_manager=components, collection=self.node_id, trust_remote_code=trust_remote_code + real_repo_id, + components_manager=components, + collection=self.node_id, + trust_remote_code=trust_remote_code, + revision=revision, + local_files_only=True, + ) + self._loader_diagnostics["component_revision_pins"] = pin_modular_component_revisions( + self.loader, + real_repo_id, + revision, ) if model_type == "DummyCustomPipeline": # update node param - custom_config = PipelineConfig.load(real_repo_id) + custom_config = PipelineConfig.load(real_repo_id, revision=revision, local_files_only=True) custom_config.label = "Custom" # update repo_id for DummyCustomPipeline DummyCustomPipeline.repo_id = real_repo_id + DummyCustomPipeline.revision = revision + DummyCustomPipeline.trust_remote_code = bool(trust_remote_code) # register DummyCustomPipeline to MODULAR_REGISTRY MODULAR_REGISTRY.register(DummyCustomPipeline, custom_config) else: DummyCustomPipeline.repo_id = None + DummyCustomPipeline.revision = None + DummyCustomPipeline.trust_remote_code = False MODULAR_REGISTRY.register(DummyCustomPipeline, DUMMY_CUSTOM_PIPELINE_CONFIG) ALL_COMPONENTS = self.loader.pretrained_component_names @@ -939,38 +1230,19 @@ def execute( for comp_name in components_to_load: comp_spec = self.loader.get_component_spec(comp_name) if comp_spec.load_id != "null": - comp_with_same_load_id = components._lookup_ids(load_id=comp_spec.load_id) - # for components with same load_id, e.g. repo/subfolder/variant/revison - # if we can find one with same dtype and quantization config, we reuse it - # otherwise, we reload it - comp_ids_to_reuse = [] - for comp_id in comp_with_same_load_id: - comp = components.get_one(component_id=comp_id) - if isinstance(comp, torch.nn.Module): - comp_dtype = comp.dtype - - # Check if quantization config matches - existing_quant = getattr(comp, "hf_quantizer", None) - requested_quant = quant_config.get(comp_name) if quant_config else None - - quant_matches = True - if requested_quant is not None: - if existing_quant is None: - quant_matches = False - else: - # Compare configs - existing_config = getattr(existing_quant, "quantization_config", None) - existing_dict = quant_config_to_info(existing_config) if existing_config is not None else None - requested_dict = quant_config_to_info(requested_quant) - quant_matches = existing_dict == requested_dict - elif existing_quant is not None: - quant_matches = False - - if comp_dtype == dtype and quant_matches: - comp_ids_to_reuse.append(comp_id) - else: - # always reuse non-nn.Module components, e.g. scheduler, tokenizer, etc. - comp_ids_to_reuse.append(comp_id) + # Reuse requires the same model identity, dtype, quantization, + # execution device, and offload policy. In particular, never + # carry a group-offload hook into a differently configured run. + comp_ids_to_reuse = reusable_component_ids( + components, + name=comp_name, + load_id=comp_spec.load_id, + dtype=dtype, + requested_quantization=quant_config.get(comp_name) if quant_config else None, + offload_mode=offload_mode, + device=device, + node_id=self.node_id, + ) if not comp_ids_to_reuse: components_to_reload.append(comp_name) @@ -988,33 +1260,34 @@ def execute( self._loader_diagnostics["required_components"] = sorted(required_components) self._loader_diagnostics["components_to_load"] = list(components_to_reload) - incremental_qwen_group_offload = ( - model_type == "QwenImageModularPipeline" - and use_group_offload - and bool(quant_config) - and bool(QWEN_LOW_RESOURCE_COMPONENTS.intersection(set(quant_config.keys()))) - ) - load_components_strict( - self.loader, - names=components_to_reload, - required_names=required_components, - model_id=real_repo_id, - dtype=dtype, - offload_mode=offload_mode, + incremental_group_offload = should_incrementally_group_offload( + use_group_offload=use_group_offload, quant_config=quant_config, - diagnostics=self._loader_diagnostics, - component_load_kwargs={ - "torch_dtype": dtype, - "trust_remote_code": trust_remote_code, - "quantization_config": quant_config, - }, - incremental_group_offload={ - "device": device, - "mode": offload_mode, - "node_id": self.node_id, - "scope": "modular-diffusers", - } if incremental_qwen_group_offload else None, ) + with self.diffusers_loading_progress(): + load_components_strict( + self.loader, + names=components_to_reload, + required_names=required_components, + model_id=real_repo_id, + dtype=dtype, + offload_mode=offload_mode, + quant_config=quant_config, + diagnostics=self._loader_diagnostics, + component_load_kwargs={ + "torch_dtype": dtype, + "trust_remote_code": trust_remote_code, + "quantization_config": quant_config, + }, + incremental_group_offload={ + "device": device, + "mode": offload_mode, + "node_id": self.node_id, + "scope": "modular-diffusers", + } + if incremental_group_offload + else None, + ) self.loader.update_components(**components_to_update) if use_group_offload: @@ -1029,30 +1302,58 @@ def execute( ) if not offload_result.applied: raise RuntimeError("No compatible Modular Diffusers component was available to offload.") - self._loader_diagnostics["offload"].update({ - "mode": offload_result.mode, - "method": offload_result.method, - "components": sorted(set(self._loader_diagnostics["offload"].get("components", []) + offload_result.components)), - "disk_path": offload_result.disk_path, - "detail": offload_result.detail, - }) + self._loader_diagnostics["offload"].update( + { + "mode": offload_result.mode, + "method": offload_result.method, + "components": sorted( + set(self._loader_diagnostics["offload"].get("components", []) + offload_result.components) + ), + "disk_path": offload_result.disk_path, + "detail": offload_result.detail, + } + ) logger.debug(f" ModelsLoader: applied {offload_mode} to {offload_result.components}") except RuntimeError as exc: self.notify(str(exc), variant="error", persist=False, autoHideDuration=MESSAGE_DURATION) raise elif offload_mode == "none": - self.loader.to(device) - self._loader_diagnostics["offload"].update({ - "mode": offload_mode, - "method": "to_device", - "components": [], - }) + resident_modules = place_pipeline_components( + self.loader, + device, + progress_callback=lambda name, index, total: self.progress( + 99, + phase="component_placement", + message=( + f"Placing {name} on {device} ({index}/{total}); " + "this one-time accelerator copy can take several minutes" + ), + current_step=index, + total_steps=total, + ), + ) + self._loader_diagnostics["offload"].update( + { + "mode": offload_mode, + "method": "to_device", + "components": resident_modules, + } + ) elif offload_mode == OFFLOAD_MODE_MODEL_CPU: - self._loader_diagnostics["offload"].update({ - "mode": offload_mode, - "method": "components_manager_auto_cpu_offload", - "components": [], - }) + self._loader_diagnostics["offload"].update( + { + "mode": offload_mode, + "method": "components_manager_auto_cpu_offload", + "components": [], + } + ) + + record_pipeline_component_runtime_policy( + self.loader, + offload_mode=offload_mode, + device=device, + node_id=self.node_id, + ) print(f" ModelsLoader: reloaded components: {components_to_reload}") print(f" ModelsLoader: updated components: {components_to_update.keys()}") @@ -1061,6 +1362,7 @@ def execute( self.loader.unload_lora_weights() if lora_list: update_lora_adapters(self.loader, lora_list) + apply_lora_scheduler_override(self.loader, lora_list) # Construct loaded_components at the end after all modifications try: @@ -1085,17 +1387,23 @@ def execute( persist=False, autoHideDuration=MESSAGE_DURATION, ) - self._loader_diagnostics.setdefault("components_failed", []).append({ - "name": "component_info", - "required": True, - "error": str(e), - }) + self._loader_diagnostics.setdefault("components_failed", []).append( + { + "name": "component_info", + "required": True, + "error": str(e), + } + ) raise RuntimeError(f"ModelsLoader could not retrieve required component info: {e}") from e - # add repo_id to all models info dicts + # Make every connected output self-describing. Runtime cleanup may + # recreate downstream nodes without replaying their dynamic UI signal. for k, v in loaded_components.items(): - if v is not None: + if isinstance(v, dict): v["repo_id"] = real_repo_id + v["model_type"] = model_type + v["revision"] = revision + v["trust_remote_code"] = bool(trust_remote_code) logger.debug(f" ModelsLoader: Final component_manager state: {components}") diff --git a/modules/ModularDiffusers/modular_utils.py b/modules/ModularDiffusers/modular_utils.py index fce83a2..d9078f3 100644 --- a/modules/ModularDiffusers/modular_utils.py +++ b/modules/ModularDiffusers/modular_utils.py @@ -1,14 +1,71 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging +import re import threading +from pathlib import Path from typing import Any, Dict, Optional from diffusers import Flux2KleinModularPipeline +from modiff.model_artifact_catalog import resolve_model_revision from .pipeline_schema import MoDiffParam as PipelineParam from .pipeline_schema import MoDiffPipelineConfig as PipelineConfig logger = logging.getLogger("modiff") +IMMUTABLE_HUB_REVISION = re.compile(r"^[0-9a-fA-F]{40}$") + + +def require_immutable_hub_revision(repo_id, revision, *, required: bool): + """Require a commit hash before loading a trust-sensitive Hub repository.""" + + repository = str(repo_id or "").strip() + normalized_revision = str(revision or "").strip() or None + if not repository or Path(repository).expanduser().exists(): + return normalized_revision + if required and not (normalized_revision and IMMUTABLE_HUB_REVISION.fullmatch(normalized_revision)): + raise ValueError( + "Remote Modular Diffusers repositories require an immutable 40-character Hugging Face commit revision. " + "Review the repository contents, copy its commit hash into Revision, and retry." + ) + return normalized_revision + + +def pin_modular_component_revisions(pipeline, primary_repo, primary_revision): + """Attach resolved Hub revisions to the component specs a modular config creates. + + Upstream ``ModularPipeline.from_pretrained`` uses ``revision`` to fetch the + pipeline configuration, but the current experimental API does not copy it + into the component specs built from that configuration. Without this pass, + ``load_components`` can still follow an auxiliary repository's moving + default branch even though the graph and top-level config were pinned. + Unknown remote auxiliaries fail before any component spec is mutated. + """ + + primary = str(primary_repo or "").strip() + resolved_specs = [] + for name, spec in (getattr(pipeline, "_component_specs", None) or {}).items(): + repository = getattr(spec, "pretrained_model_name_or_path", None) + if not isinstance(repository, str) or not repository.strip(): + continue + if primary and repository.lower() == primary.lower(): + revision = primary_revision + if not revision: + continue + else: + revision = resolve_model_revision(repository, getattr(spec, "revision", None)) + revision = require_immutable_hub_revision(repository, revision, required=True) + if not revision: + continue + resolved_specs.append((str(name), spec, revision)) + + applied = {} + for name, spec, revision in resolved_specs: + spec.revision = revision + applied[name] = revision + return applied + + SDXL_NODE_SPECS = { "controlnet": { "inputs": [ @@ -983,16 +1040,103 @@ class DummyCustomPipeline: """Placeholder class used as registry key for custom pipelines.""" repo_id = None + revision = None + trust_remote_code = False def __new__(cls): from diffusers import ModularPipeline - return ModularPipeline.from_pretrained(cls.repo_id, trust_remote_code=True) + revision = require_immutable_hub_revision( + cls.repo_id, + cls.revision, + required=bool(cls.trust_remote_code), + ) + kwargs = { + "trust_remote_code": bool(cls.trust_remote_code), + "local_files_only": True, + } + if revision: + kwargs["revision"] = revision + return ModularPipeline.from_pretrained(cls.repo_id, **kwargs) -DUMMY_CUSTOM_PIPELINE_CONFIG = PipelineConfig( - node_specs={}, label="Custom", default_repo="", default_dtype="bfloat16" -) +def pipeline_class_from_runtime_inputs(current_pipeline_class, *runtime_values): + """Recover a modular pipeline class from self-describing connected inputs. + + Dynamic node-definition signals are transient and are not replayed when the + runtime recreates node instances between tasks. ModelsLoader therefore adds + ``model_type`` to each component payload, allowing downstream nodes to + restore the same pipeline contract from their graph inputs. + """ + if current_pipeline_class is not None: + return current_pipeline_class + + model_types = set() + custom_repositories = set() + custom_revisions = set() + custom_trust_values = set() + visited = set() + + def collect(value): + if isinstance(value, dict): + value_id = id(value) + if value_id in visited: + return + visited.add(value_id) + model_type = value.get("model_type") + if isinstance(model_type, str) and model_type.strip(): + model_types.add(model_type.strip()) + if model_type.strip() == "DummyCustomPipeline": + repository = value.get("repo_id") + revision = value.get("revision") + if isinstance(repository, str) and repository.strip(): + custom_repositories.add(repository.strip()) + if isinstance(revision, str) and revision.strip(): + custom_revisions.add(revision.strip()) + custom_trust_values.add(bool(value.get("trust_remote_code"))) + for nested_value in value.values(): + collect(nested_value) + elif isinstance(value, (list, tuple)): + for nested_value in value: + collect(nested_value) + + for runtime_value in runtime_values: + collect(runtime_value) + + if not model_types: + return None + if len(model_types) > 1: + raise ValueError( + "Connected modular model inputs use incompatible pipeline classes: " + ", ".join(sorted(model_types)) + ) + + model_type = next(iter(model_types)) + if model_type == "DummyCustomPipeline": + if len(custom_repositories) != 1 or len(custom_revisions) != 1 or len(custom_trust_values) != 1: + raise ValueError( + "Connected custom Modular Diffusers inputs have incomplete or conflicting trust metadata." + ) + repository = next(iter(custom_repositories)) + revision = next(iter(custom_revisions)) + trust_remote_code = next(iter(custom_trust_values)) + require_immutable_hub_revision(repository, revision, required=True) + DummyCustomPipeline.repo_id = repository + DummyCustomPipeline.revision = revision + DummyCustomPipeline.trust_remote_code = trust_remote_code + return DummyCustomPipeline + + import diffusers as diffusers_module + + pipeline_class = getattr(diffusers_module, model_type, None) + if pipeline_class is None: + raise ValueError( + f"Unknown Diffusers modular pipeline class '{model_type}'. " + "Install a Diffusers version that provides this model type." + ) + return pipeline_class + + +DUMMY_CUSTOM_PIPELINE_CONFIG = PipelineConfig(node_specs={}, label="Custom", default_repo="", default_dtype="bfloat16") # Minimal modular registry for MoDiff node configs @@ -1142,21 +1286,6 @@ def get_all_model_types() -> Dict[str, str]: return all_labels -# YiYi notes: not used for now -def get_model_type_signal_data() -> Dict[str, str]: - """Get model type mapping for onSignal value actions. - - Returns a dict mapping model type names to themselves, used in onSignal - to pass model type through from upstream nodes. - """ - registry = _get_registry_instance().get_all() - model_types = {"": ""} - for pipeline_cls, _ in registry.items(): - model_type = pipeline_cls.__name__ - model_types[model_type] = model_type - return model_types - - def get_model_type_metadata(model_type: str) -> Optional[Dict[str, Any]]: """Get metadata for a model type. diff --git a/modules/ModularDiffusers/pipeline_schema.py b/modules/ModularDiffusers/pipeline_schema.py index e872fc0..9db32cd 100644 --- a/modules/ModularDiffusers/pipeline_schema.py +++ b/modules/ModularDiffusers/pipeline_schema.py @@ -1,7 +1,13 @@ -"""MoDiff-owned schema helpers for Modular Diffusers node metadata. +"""MoDiff adapter for Hugging Face Modular Diffusers node metadata. -Adapted from Hugging Face Diffusers modular pipeline utilities under Apache-2.0. -The local copy gives MoDiff a stable, product-owned schema and Hub config format. +Derived from Hugging Face Diffusers' +``src/diffusers/modular_pipelines/mellon_node_utils.py`` at commit +``13a7bee4878d62fccc8d25f97e480e68de96fa03`` (Apache-2.0): +https://github.com/huggingface/diffusers/blob/13a7bee4878d62fccc8d25f97e480e68de96fa03/src/diffusers/modular_pipelines/mellon_node_utils.py + +MoDiff changes the Mellon-facing names, metadata key, configuration filename, +and imports to integrate the helper with MoDiff. The executable Diffusers +dependency is pinned separately in ``pyproject.toml``. """ import copy @@ -599,7 +605,28 @@ def node_spec_to_modiff_dict(node_spec: dict[str, Any], node_type: str) -> dict[ For Modular MoDiff nodes, we need to distinguish: - `inputs`: Pipeline inputs (e.g., seed, prompt, image) - - `mode…266 tokens truncated… - `block_name`: The backend block name + - `model_inputs`: Model components (e.g., unet, vae, scheduler) + - `outputs`: Node outputs (e.g., latents, images) + + The node spec also includes: + - `required_inputs` / `required_model_inputs`: Which params are required (marked with *) + - `block_name`: The modular pipeline block this node corresponds to on backend + + We provide factory methods for common parameters (e.g., `MoDiffParam.seed()`, `MoDiffParam.unet()`) so you don't + have to manually specify all the UI configuration. + + Args: + node_spec: Dict with `inputs`, `model_inputs`, `outputs` (lists of MoDiffParam), + plus `required_inputs`, `required_model_inputs`, `block_name`. + node_type: The node type string (e.g., "denoise", "controlnet") + + Returns: + Dict with: + - `params`: Flat dict of all params in MoDiff UI format + - `input_names`: List of input parameter names + - `model_input_names`: List of model input parameter names + - `output_names`: List of output parameter names + - `block_name`: The backend block name - `node_type`: The node type Example: diff --git a/modules/ModularDiffusers/schedulers.py b/modules/ModularDiffusers/schedulers.py index 8ae099f..b068281 100644 --- a/modules/ModularDiffusers/schedulers.py +++ b/modules/ModularDiffusers/schedulers.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging from diffusers import ComponentSpec diff --git a/modules/ModularDiffusers/utils.py b/modules/ModularDiffusers/utils.py index 1b1f66d..7f3ae19 100644 --- a/modules/ModularDiffusers/utils.py +++ b/modules/ModularDiffusers/utils.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. def combine_multi_inputs(inputs): """ Recursively combines a list of dictionaries into a dictionary of lists. diff --git a/modules/Primitive/main.py b/modules/Primitive/main.py index 4c70f9a..708ccdd 100644 --- a/modules/Primitive/main.py +++ b/modules/Primitive/main.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. from modiff.NodeBase import NodeBase from PIL import Image import json diff --git a/modules/QwenImage/__init__.py b/modules/QwenImage/__init__.py deleted file mode 100644 index 15b6a64..0000000 --- a/modules/QwenImage/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .main import * diff --git a/modules/QwenImage/main.py b/modules/QwenImage/main.py deleted file mode 100644 index ed0f936..0000000 --- a/modules/QwenImage/main.py +++ /dev/null @@ -1,661 +0,0 @@ -import logging -import inspect -from typing import Any - -from PIL import Image, ImageColor, ImageDraw, ImageFilter - -from modiff.NodeBase import NodeBase -from modiff.diffusers_offload import ( - OFFLOAD_MODE_GROUP_CPU, - OFFLOAD_MODE_GROUP_DISK, - OFFLOAD_MODE_MODEL_CPU, - OFFLOAD_MODE_NONE, - OFFLOAD_MODE_SEQUENTIAL_CPU, - apply_pipeline_offload, - normalize_offload_mode, - offload_mode_param, -) -from modiff.diffusers_profiles import QWEN_IMAGE_2512_PREQUANTIZED_REPO, QWEN_IMAGE_2512_REPO -from utils.huggingface import local_files_only -from utils.torch_utils import DEFAULT_DEVICE, DEVICE_LIST, str_to_dtype - -logger = logging.getLogger("modiff") - -QWEN_IMAGE_EDIT_DEFAULT_REPO = "Qwen/Qwen-Image-Edit" -DEVICE_OPTIONS = list(DEVICE_LIST.keys()) -QWEN_DIRECT_OFFLOAD_MODES = [ - OFFLOAD_MODE_NONE, - OFFLOAD_MODE_MODEL_CPU, - OFFLOAD_MODE_SEQUENTIAL_CPU, - OFFLOAD_MODE_GROUP_CPU, - OFFLOAD_MODE_GROUP_DISK, -] -QWEN_QUANTIZABLE_COMPONENTS = ["transformer", "text_encoder"] - - -def repo_value(value: Any) -> str: - if isinstance(value, dict): - return str(value.get("value") or "") - return str(value or "") - - -def none_if_blank(value: Any): - if value is None: - return None - if isinstance(value, str) and value.strip() == "": - return None - return value - - -def ensure_single_prompt(prompt: Any, field_name: str): - if isinstance(prompt, list): - raise ValueError(f"Qwen Image inpaint currently supports one {field_name}; prompt lists are not supported by this pipeline.") - return prompt - - -def ensure_single_image(value: Any, field_name: str) -> Image.Image: - if value in (None, ""): - raise ValueError(f"Qwen Image workflow requires a {field_name}.") - if isinstance(value, list): - images = [item for item in value if item not in (None, "")] - if len(images) != 1: - raise ValueError(f"Qwen Image workflow requires exactly one {field_name}; received {len(images)}.") - value = images[0] - if not isinstance(value, Image.Image): - raise ValueError(f"Qwen Image workflow {field_name} must be a PIL image loaded by the Image Load node.") - return value - - -def normalize_padding_mask_crop(value: Any): - if value in (None, ""): - return None - value = int(value) - return value if value > 0 else None - - -def coerce_pipeline_quantization_config(quant_config: Any): - if not quant_config: - return None - if isinstance(quant_config, str): - if quant_config.strip() == "": - return None - raise ValueError( - "Qwen Image quant_config must be connected to a Quantization Config node output. " - f"Received unresolved string value {quant_config!r}." - ) - - from diffusers.quantizers import PipelineQuantizationConfig - - if isinstance(quant_config, PipelineQuantizationConfig): - return quant_config - if isinstance(quant_config, dict): - return PipelineQuantizationConfig(quant_mapping=quant_config) - raise ValueError("Qwen Image inpaint quant_config must be a Diffusers PipelineQuantizationConfig or component mapping.") - - -def normalize_component_list(value: Any) -> list[str]: - if value in (None, ""): - return [] - if isinstance(value, str): - raw_items = [item.strip() for item in value.split(",")] - elif isinstance(value, list): - raw_items = [str(item).strip() for item in value] - else: - raw_items = [str(value).strip()] - return [item for item in raw_items if item in QWEN_QUANTIZABLE_COMPONENTS] - - -def quantization_info(config: Any): - if config is None: - return None - if hasattr(config, "to_diff_dict"): - return config.to_diff_dict() - if hasattr(config, "to_dict"): - return config.to_dict() - return str(config) - - -def build_qwen_pipeline_quantization_config( - *, - components: list[str], - quantization_mode: str, - compute_dtype: Any, - quant_type: str, - double_quant: bool, -): - if quantization_mode != "bnb_4bit" or not components: - return None - - import torch - from diffusers import BitsAndBytesConfig as DiffusersBitsAndBytesConfig - from diffusers.quantizers import PipelineQuantizationConfig - from transformers import BitsAndBytesConfig as TransformersBitsAndBytesConfig - - compute_dtype = compute_dtype or torch.bfloat16 - quant_mapping = {} - if "transformer" in components: - quant_mapping["transformer"] = DiffusersBitsAndBytesConfig( - load_in_4bit=True, - bnb_4bit_quant_type=quant_type, - bnb_4bit_compute_dtype=compute_dtype, - bnb_4bit_use_double_quant=bool(double_quant), - ) - if "text_encoder" in components: - quant_mapping["text_encoder"] = TransformersBitsAndBytesConfig( - load_in_4bit=True, - bnb_4bit_quant_type=quant_type, - bnb_4bit_compute_dtype=compute_dtype, - bnb_4bit_use_double_quant=bool(double_quant), - ) - return PipelineQuantizationConfig(quant_mapping=quant_mapping) - - -def diffusers_device_map_strategy(device: Any): - device_text = str(device or "").strip().lower() - if device_text.startswith("cuda"): - return "cuda" - if device_text.startswith("cpu"): - return "cpu" - return None - - -def int_in_range(value: Any, fallback: int, minimum: int, maximum: int) -> int: - try: - parsed = int(value) - except (TypeError, ValueError): - parsed = fallback - return max(minimum, min(maximum, parsed)) - - -def float_in_range(value: Any, fallback: float, minimum: float, maximum: float) -> float: - try: - parsed = float(value) - except (TypeError, ValueError): - parsed = fallback - return max(minimum, min(maximum, parsed)) - - -def parse_fill_color(value: Any) -> tuple[int, int, int]: - if not isinstance(value, str) or not value.strip(): - return (0, 0, 0) - try: - return ImageColor.getrgb(value.strip())[:3] - except ValueError: - raise ValueError(f"Invalid Qwen outpaint fill color: {value!r}. Use a CSS color name or hex color.") - - -def supports_pipeline_arg(pipeline: Any, arg_name: str) -> bool: - try: - return arg_name in inspect.signature(pipeline.__call__).parameters - except (TypeError, ValueError): - return False - - -def add_step_progress_callback(node: NodeBase, pipeline: Any, call_kwargs: dict[str, Any], steps: int): - if not supports_pipeline_arg(pipeline, "callback_on_step_end"): - return - - total_steps = max(1, int(steps or 1)) - - def progress_callback(pipe, step_index, timestep, callback_kwargs): - progress = int(((step_index + 1) / total_steps) * 100) - node.progress( - min(100, max(0, progress)), - phase="denoising", - message=f"Denoising {step_index + 1}/{total_steps}", - current_step=step_index + 1, - total_steps=total_steps, - ) - return callback_kwargs - - call_kwargs["callback_on_step_end"] = progress_callback - if supports_pipeline_arg(pipeline, "callback_on_step_end_tensor_inputs"): - call_kwargs["callback_on_step_end_tensor_inputs"] = [] - - -class LoadPipeline(NodeBase): - """Load a direct Diffusers Qwen Image text-to-image pipeline.""" - - label = "Load Qwen Image" - category = "Qwen Image" - resizable = True - params = { - "pipeline": {"label": "Pipeline", "display": "output", "type": "qwen_image_pipeline"}, - "model_id": { - "label": "Model", - "display": "modelselect", - "type": "string", - "value": {"source": "hub", "value": QWEN_IMAGE_2512_REPO}, - "fieldOptions": {"noValidation": True, "sources": ["hub", "local"]}, - }, - "revision": {"label": "Revision", "type": "string", "default": ""}, - "dtype": { - "label": "DType", - "type": "string", - "options": ["float32", "float16", "bfloat16"], - "default": "bfloat16", - }, - "device": { - "label": "Device", - "type": "string", - "options": DEVICE_OPTIONS, - "default": DEFAULT_DEVICE, - }, - "quantization_mode": { - "label": "Quantization", - "type": "string", - "options": ["none", "bnb_4bit"], - "default": "bnb_4bit", - }, - "quantized_components": { - "label": "Quantized Components", - "type": "string", - "display": "select", - "options": QWEN_QUANTIZABLE_COMPONENTS, - "fieldOptions": {"multiple": True}, - "default": QWEN_QUANTIZABLE_COMPONENTS, - }, - "bnb_4bit_quant_type": { - "label": "4-bit Quant Type", - "type": "string", - "options": ["nf4", "fp4"], - "default": "nf4", - }, - "bnb_4bit_compute_dtype": { - "label": "Compute DType", - "type": "string", - "options": ["float32", "float16", "bfloat16"], - "default": "bfloat16", - }, - "bnb_4bit_use_double_quant": {"label": "Double Quant", "type": "bool", "default": True}, - "auto_offload": {"label": "Auto offload", "type": "bool", "default": True}, - "offload_mode": offload_mode_param(modes=QWEN_DIRECT_OFFLOAD_MODES), - "low_cpu_mem_usage": {"label": "Low CPU memory", "type": "bool", "default": True}, - "resolved_artifact": {"label": "Resolved Artifact", "display": "output", "type": "string"}, - } - - def execute(self, **kwargs): - import torch - from diffusers import QwenImagePipeline - - model_id = repo_value(kwargs.get("model_id")) or QWEN_IMAGE_2512_REPO - dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) - revision = none_if_blank(kwargs.get("revision")) - device = kwargs.get("device") or DEFAULT_DEVICE - auto_offload = bool(kwargs.get("auto_offload", True)) - offload_mode = normalize_offload_mode(kwargs.get("offload_mode") or OFFLOAD_MODE_MODEL_CPU, auto_offload=auto_offload) - quantization_mode = str(kwargs.get("quantization_mode") or "none") - quantized_components = normalize_component_list(kwargs.get("quantized_components")) - compute_dtype = str_to_dtype(kwargs.get("bnb_4bit_compute_dtype") or "bfloat16") - quant_type = str(kwargs.get("bnb_4bit_quant_type") or "nf4") - double_quant = bool(kwargs.get("bnb_4bit_use_double_quant", True)) - - if model_id == QWEN_IMAGE_2512_PREQUANTIZED_REPO: - # The fallback artifact is already quantized. Do not quantize it a second time. - quantization_mode = "none" - quantized_components = [] - - quant_config = build_qwen_pipeline_quantization_config( - components=quantized_components, - quantization_mode=quantization_mode, - compute_dtype=compute_dtype, - quant_type=quant_type, - double_quant=double_quant, - ) - quant_mapping = getattr(quant_config, "quant_mapping", None) if quant_config else None - self._loader_diagnostics = { - "node_id": self.node_id, - "loader": "QwenImage.LoadPipeline", - "pipeline_class": "QwenImagePipeline", - "repo_id": model_id, - "dtype": str(dtype), - "graph_offload_mode": kwargs.get("offload_mode"), - "normalized_offload_mode": offload_mode, - "auto_offload": auto_offload, - "quantization_mode": quantization_mode, - "quantized_components": list(quantized_components), - "quantization": { - name: quantization_info(config) - for name, config in (quant_mapping or {}).items() - }, - "resolved_artifact": model_id, - } - - load_kwargs = { - "torch_dtype": dtype, - "revision": revision, - "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), - "local_files_only": local_files_only(model_id), - } - if quant_config: - load_kwargs["quantization_config"] = quant_config - if str(device).startswith("cuda"): - # Diffusers expects a device-map strategy here, not a torch device such as cuda:0. - load_kwargs["device_map"] = diffusers_device_map_strategy(device) - - logger.info("Loading Qwen Image pipeline: %s", model_id) - self.progress(-1, phase="loading", message="Loading Qwen Image pipeline") - pipeline = QwenImagePipeline.from_pretrained(model_id, **load_kwargs) - - self.progress(-1, phase="loading", message=f"Applying {offload_mode} offload") - offload_result = apply_pipeline_offload( - pipeline, - mode=offload_mode, - device=device, - node_id=self.node_id, - scope="qwen-image", - ) - self._loader_diagnostics["offload"] = { - "mode": offload_result.mode, - "method": offload_result.method, - "components": offload_result.components, - "disk_path": offload_result.disk_path, - "detail": offload_result.detail, - } - - self.mm_add(pipeline, priority=2) - return {"pipeline": pipeline, "resolved_artifact": model_id} - - -class Generate(NodeBase): - """Generate an image with a direct Qwen Image Diffusers pipeline.""" - - label = "Qwen Image Generate" - category = "Qwen Image" - resizable = True - params = { - "pipeline": {"label": "Pipeline", "display": "input", "type": "qwen_image_pipeline"}, - "prompt": {"label": "Prompt", "display": "textarea", "type": "text", "default": ""}, - "negative_prompt": {"label": "Negative Prompt", "display": "textarea", "type": "text", "default": ""}, - "width": {"label": "Width", "type": "int", "default": 1024, "min": 16, "max": 2048, "step": 16}, - "height": {"label": "Height", "type": "int", "default": 1024, "min": 16, "max": 2048, "step": 16}, - "seed": {"label": "Seed", "type": "int", "display": "random", "default": 0, "min": 0, "max": 4294967295}, - "num_inference_steps": {"label": "Steps", "display": "slider", "type": "int", "default": 50, "min": 1, "max": 100}, - "true_cfg_scale": {"label": "Guidance", "display": "slider", "type": "float", "default": 4.0, "min": 0, "max": 20, "step": 0.1}, - "max_sequence_length": {"label": "Max Sequence Length", "type": "int", "default": 512, "min": 1, "max": 2048}, - "output_type": {"label": "Output type", "type": "string", "options": ["pil", "np", "pt"], "default": "pil"}, - "images": {"label": "Images", "display": "output", "type": "image"}, - "width_out": {"label": "Width", "display": "output", "type": "int"}, - "height_out": {"label": "Height", "display": "output", "type": "int"}, - } - - def execute(self, **kwargs): - import torch - - pipeline = kwargs.get("pipeline") - if pipeline is None: - raise ValueError("Qwen Image pipeline is required.") - - prompt = ensure_single_prompt(none_if_blank(kwargs.get("prompt")), "prompt") - negative_prompt = ensure_single_prompt(none_if_blank(kwargs.get("negative_prompt")), "negative prompt") - width = int(kwargs.get("width", 1024)) - height = int(kwargs.get("height", 1024)) - - device = getattr(pipeline, "_execution_device", None) or getattr(pipeline, "device", None) or "cpu" - try: - generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed", 0))) - except Exception: - generator = torch.Generator(device="cpu").manual_seed(int(kwargs.get("seed", 0))) - - steps = int(kwargs.get("num_inference_steps", 50)) - call_kwargs = { - "prompt": prompt, - "negative_prompt": negative_prompt if negative_prompt is not None else " ", - "true_cfg_scale": float(kwargs.get("true_cfg_scale", 4.0)), - "height": height, - "width": width, - "num_inference_steps": steps, - "max_sequence_length": int(kwargs.get("max_sequence_length", 512)), - "output_type": kwargs.get("output_type", "pil"), - "generator": generator, - "return_dict": True, - } - add_step_progress_callback(self, pipeline, call_kwargs, steps) - - result = pipeline(**call_kwargs) - images = getattr(result, "images", result) - return { - "images": images, - "width_out": width, - "height_out": height, - } - - -def placement_offset(available: int, start_margin: int, end_margin: int) -> int: - if available <= 0: - return 0 - requested = start_margin + end_margin - if requested <= 0: - return available // 2 - if requested > available: - return int(round((start_margin / requested) * available)) - return min(start_margin, available) - - -def outpaint_source_size(source_size: tuple[int, int], target_size: tuple[int, int], margins: tuple[int, int, int, int]) -> tuple[int, int]: - source_width, source_height = source_size - target_width, target_height = target_size - left, right, top, bottom = margins - available_width = max(1, target_width - left - right) - available_height = max(1, target_height - top - bottom) - scale = min( - target_width / source_width, - target_height / source_height, - available_width / source_width, - available_height / source_height, - 1.0, - ) - return ( - max(1, int(round(source_width * scale))), - max(1, int(round(source_height * scale))), - ) - - -class OutpaintCanvas(NodeBase): - """Prepare an expanded canvas and boundary mask for Qwen inpaint outpainting.""" - - label = "Qwen Outpaint Canvas" - category = "Qwen Image" - resizable = True - params = { - "image": {"label": "Source image", "display": "input", "type": "image"}, - "width": {"label": "Canvas width", "type": "int", "default": 1344, "min": 64, "max": 2048, "step": 16}, - "height": {"label": "Canvas height", "type": "int", "default": 768, "min": 64, "max": 2048, "step": 16}, - "left": {"label": "Left margin", "type": "int", "default": 256, "min": 0, "max": 2048, "step": 16}, - "right": {"label": "Right margin", "type": "int", "default": 256, "min": 0, "max": 2048, "step": 16}, - "top": {"label": "Top margin", "type": "int", "default": 0, "min": 0, "max": 2048, "step": 16}, - "bottom": {"label": "Bottom margin", "type": "int", "default": 0, "min": 0, "max": 2048, "step": 16}, - "overlap": {"label": "Seam overlap", "type": "int", "default": 24, "min": 0, "max": 256, "step": 4}, - "feather": {"label": "Mask feather", "type": "float", "default": 8.0, "min": 0, "max": 128, "step": 1}, - "fill_color": {"label": "Fill color", "type": "string", "default": "#000000"}, - "canvas": {"label": "Canvas", "display": "output", "type": "image"}, - "mask_image": {"label": "Mask image", "display": "output", "type": "image"}, - "width_out": {"label": "Width", "display": "output", "type": "int"}, - "height_out": {"label": "Height", "display": "output", "type": "int"}, - } - - def execute(self, **kwargs): - source = ensure_single_image(kwargs.get("image"), "source image") - target_width = int_in_range(kwargs.get("width"), 1344, 64, 2048) - target_height = int_in_range(kwargs.get("height"), 768, 64, 2048) - left = int_in_range(kwargs.get("left"), 256, 0, target_width) - right = int_in_range(kwargs.get("right"), 256, 0, target_width) - top = int_in_range(kwargs.get("top"), 0, 0, target_height) - bottom = int_in_range(kwargs.get("bottom"), 0, 0, target_height) - overlap = int_in_range(kwargs.get("overlap"), 24, 0, 256) - feather = float_in_range(kwargs.get("feather"), 8.0, 0.0, 128.0) - fill_color = parse_fill_color(kwargs.get("fill_color", "#000000")) - - source_rgba = source.convert("RGBA") - pasted_width, pasted_height = outpaint_source_size( - source_rgba.size, - (target_width, target_height), - (left, right, top, bottom), - ) - if (pasted_width, pasted_height) != source_rgba.size: - source_rgba = source_rgba.resize((pasted_width, pasted_height), Image.Resampling.LANCZOS) - - offset_x = placement_offset(target_width - pasted_width, left, right) - offset_y = placement_offset(target_height - pasted_height, top, bottom) - canvas = Image.new("RGB", (target_width, target_height), fill_color) - canvas.paste(source_rgba, (offset_x, offset_y), source_rgba) - - mask = Image.new("L", (target_width, target_height), 255) - keep_left = min(target_width, max(0, offset_x + overlap)) - keep_top = min(target_height, max(0, offset_y + overlap)) - keep_right = min(target_width, max(0, offset_x + pasted_width - overlap)) - keep_bottom = min(target_height, max(0, offset_y + pasted_height - overlap)) - if keep_right > keep_left and keep_bottom > keep_top: - ImageDraw.Draw(mask).rectangle((keep_left, keep_top, keep_right, keep_bottom), fill=0) - if feather > 0: - mask = mask.filter(ImageFilter.GaussianBlur(radius=feather)) - - return { - "canvas": canvas, - "mask_image": mask, - "width_out": target_width, - "height_out": target_height, - } - - -class LoadInpaintPipeline(NodeBase): - """Load a direct Diffusers Qwen Image Edit inpaint pipeline.""" - - label = "Load Qwen Inpaint" - category = "Qwen Image" - resizable = True - params = { - "pipeline": {"label": "Pipeline", "display": "output", "type": "qwen_image_inpaint_pipeline"}, - "model_id": { - "label": "Model", - "display": "modelselect", - "type": "string", - "value": {"source": "hub", "value": QWEN_IMAGE_EDIT_DEFAULT_REPO}, - "fieldOptions": {"noValidation": True, "sources": ["hub", "local"]}, - }, - "revision": {"label": "Revision", "type": "string", "default": ""}, - "dtype": { - "label": "DType", - "type": "string", - "options": ["float32", "float16", "bfloat16"], - "default": "bfloat16", - }, - "device": { - "label": "Device", - "type": "string", - "options": DEVICE_OPTIONS, - "default": DEFAULT_DEVICE, - }, - "quant_config": {"label": "Quantization Config", "display": "input", "type": "quant_config"}, - "auto_offload": {"label": "Auto offload", "type": "bool", "default": True}, - "offload_mode": offload_mode_param(), - "low_cpu_mem_usage": {"label": "Low CPU memory", "type": "bool", "default": True}, - } - - def execute(self, **kwargs): - import torch - from diffusers import QwenImageEditInpaintPipeline - - model_id = repo_value(kwargs.get("model_id")) or QWEN_IMAGE_EDIT_DEFAULT_REPO - dtype = str_to_dtype(kwargs.get("dtype", "bfloat16")) - revision = none_if_blank(kwargs.get("revision")) - device = kwargs.get("device") or DEFAULT_DEVICE - auto_offload = bool(kwargs.get("auto_offload", True)) - offload_mode = normalize_offload_mode(kwargs.get("offload_mode") or OFFLOAD_MODE_MODEL_CPU, auto_offload=auto_offload) - - load_kwargs = { - "torch_dtype": dtype, - "revision": revision, - "low_cpu_mem_usage": bool(kwargs.get("low_cpu_mem_usage", True)), - "local_files_only": local_files_only(model_id), - } - quant_config = kwargs.get("quant_config") - if quant_config: - load_kwargs["quantization_config"] = coerce_pipeline_quantization_config(quant_config) - - logger.info("Loading Qwen Image Edit inpaint pipeline: %s", model_id) - self.progress(-1, phase="loading", message="Loading Qwen inpaint pipeline") - pipeline = QwenImageEditInpaintPipeline.from_pretrained(model_id, **load_kwargs) - - self.progress(-1, phase="loading", message=f"Applying {offload_mode} offload") - apply_pipeline_offload( - pipeline, - mode=offload_mode, - device=device, - node_id=self.node_id, - scope="qwen-image", - ) - - self.mm_add(pipeline, priority=2) - return {"pipeline": pipeline} - - -class Inpaint(NodeBase): - """Run direct Qwen Image Edit inpaint with a source image and mask image.""" - - label = "Qwen Inpaint" - category = "Qwen Image" - resizable = True - params = { - "pipeline": {"label": "Pipeline", "display": "input", "type": "qwen_image_inpaint_pipeline"}, - "image": {"label": "Source image", "display": "input", "type": "image"}, - "mask_image": {"label": "Mask image", "display": "input", "type": "image"}, - "prompt": {"label": "Prompt", "display": "textarea", "type": "text", "default": ""}, - "negative_prompt": {"label": "Negative Prompt", "display": "textarea", "type": "text", "default": ""}, - "width": {"label": "Width", "type": "int", "default": 1024, "min": 16, "max": 2048, "step": 16}, - "height": {"label": "Height", "type": "int", "default": 1024, "min": 16, "max": 2048, "step": 16}, - "seed": {"label": "Seed", "type": "int", "display": "random", "default": 0, "min": 0, "max": 4294967295}, - "num_inference_steps": {"label": "Steps", "display": "slider", "type": "int", "default": 40, "min": 1, "max": 100}, - "true_cfg_scale": {"label": "Guidance", "display": "slider", "type": "float", "default": 4.0, "min": 0, "max": 20, "step": 0.1}, - "strength": {"label": "Strength", "display": "slider", "type": "float", "default": 0.6, "min": 0, "max": 1, "step": 0.05}, - "padding_mask_crop": {"label": "Padding Mask Crop", "type": "int", "default": 0, "min": 0, "max": 512, "step": 8}, - "max_sequence_length": {"label": "Max Sequence Length", "type": "int", "default": 512, "min": 1, "max": 2048}, - "output_type": {"label": "Output type", "type": "string", "options": ["pil", "np", "pt"], "default": "pil"}, - "images": {"label": "Images", "display": "output", "type": "image"}, - "width_out": {"label": "Width", "display": "output", "type": "int"}, - "height_out": {"label": "Height", "display": "output", "type": "int"}, - } - - def execute(self, **kwargs): - import torch - - pipeline = kwargs.get("pipeline") - if pipeline is None: - raise ValueError("Qwen Image inpaint pipeline is required.") - - image = ensure_single_image(kwargs.get("image"), "source image").convert("RGB") - mask_image = ensure_single_image(kwargs.get("mask_image"), "mask image").convert("L") - prompt = ensure_single_prompt(none_if_blank(kwargs.get("prompt")), "prompt") - negative_prompt = ensure_single_prompt(none_if_blank(kwargs.get("negative_prompt")), "negative prompt") - width = int(kwargs.get("width", 1024)) - height = int(kwargs.get("height", 1024)) - - device = getattr(pipeline, "_execution_device", None) or "cpu" - generator = torch.Generator(device=device).manual_seed(int(kwargs.get("seed", 0))) - - steps = int(kwargs.get("num_inference_steps", 40)) - call_kwargs = { - "image": image, - "mask_image": mask_image, - "prompt": prompt, - "negative_prompt": negative_prompt, - "true_cfg_scale": float(kwargs.get("true_cfg_scale", 4.0)), - "height": height, - "width": width, - "padding_mask_crop": normalize_padding_mask_crop(kwargs.get("padding_mask_crop")), - "strength": float(kwargs.get("strength", 0.6)), - "num_inference_steps": steps, - "max_sequence_length": int(kwargs.get("max_sequence_length", 512)), - "output_type": kwargs.get("output_type", "pil"), - "generator": generator, - "return_dict": True, - } - add_step_progress_callback(self, pipeline, call_kwargs, steps) - - result = pipeline(**call_kwargs) - images = getattr(result, "images", result) - return { - "images": images, - "width_out": width, - "height_out": height, - } diff --git a/modules/Segmentation/__init__.py b/modules/Segmentation/__init__.py deleted file mode 100644 index 77ac207..0000000 --- a/modules/Segmentation/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Segmentation nodes.""" - -from .main import * # noqa: F401,F403 diff --git a/modules/Segmentation/main.py b/modules/Segmentation/main.py deleted file mode 100644 index a79fe5a..0000000 --- a/modules/Segmentation/main.py +++ /dev/null @@ -1,157 +0,0 @@ -from utils.torch_utils import DEVICE_LIST, DEFAULT_DEVICE -from modiff.NodeBase import NodeBase - -class InSPyReNetRemover(NodeBase): - label = "InSPyReNet Transparent Background" - category = "segmentation" - params = { - "image": { - "label": "Image", - "display": "input", - "type": "image", - }, - "checkpoint": { - "label": "Checkpoint", - "type": "string", - "options": ["base", "base-nightly", "fast"], - "default": "base" - }, - "hard_edges": { - "label": "Hard Edges", - "type": "boolean", - "default": False, - "onChange": { - True: ["he_threshold"], - False: [] - } - }, - "he_threshold": { - "label": "Hard Edges Threshold", - "display": "slider", - "type": "float", - "default": 0.5, - "step": 0.01, - "min": 0.0, - "max": 1 - }, - "prediction": { - "label": "Prediction", - "display": "output", - "type": "image", - }, - "mask": { - "label": "Mask", - "display": "output", - "type": "image", - }, - } - - def execute(self, **kwargs): - from transparent_background import Remover - - image = kwargs.get("image") - hard_edges = kwargs.get("hard_edges", False) - he_threshold = kwargs.get("he_threshold", 0.5) - threshold = None - if hard_edges: - threshold = he_threshold - - image = [image] if not isinstance(image, list) else image - - masks = [] - predictions = [] - for img in image: - remover = Remover() - - if img.mode != 'RGB': - img = img.convert("RGB") - mask = remover.process(img, type='map', threshold=threshold) - masks.append(mask) - - pred = img.convert("RGBA") - pred.putalpha(mask.convert("L")) - predictions.append(pred) - - return { "mask": masks, "prediction": predictions } - -class RemBg(NodeBase): - """ - Remove background from an image using Rembg - """ - - label = "Remove Background (Rembg)" - category = "segmentation" - params = { - "image": { - "label": "Image", - "display": "input", - "type": "image", - }, - "prediction": { - "label": "Prediction", - "display": "output", - "type": "image", - }, - "mask": { - "label": "Mask", - "display": "output", - "type": "image", - }, - } - - def execute(self, **kwargs): - from rembg import remove - - image = kwargs.get("image") - image = [image] if not isinstance(image, list) else image - - predictions = [] - masks = [] - for img in image: - if img.mode != 'RGB': - img = img.convert("RGB") - prediction = remove(img) - if prediction.mode != 'RGBA': - prediction = prediction.convert("RGBA") - alpha = prediction.split()[-1] - predictions.append(prediction) - masks.append(alpha) - - return {"prediction": predictions, "mask": masks} - -# class Sam2Auto(NodeBase): -# label = "Segment Anything v2" -# category = "segmentation" -# params = { -# "image": { -# "label": "Image", -# "display": "input", -# "type": "image", -# }, -# "masks": { -# "label": "Masks", -# "display": "output", -# "type": "image", -# }, -# "device": { "label": "Device", "type": "string", "default": DEFAULT_DEVICE, "options": DEVICE_LIST }, -# } - -# def execute(self, **kwargs): -# import torch -# from transformers import pipeline -# image = kwargs.get("image") -# image = image[0] if isinstance(image, list) else image -# device = kwargs.get("device", DEFAULT_DEVICE) -# generator = pipeline("mask-generation", model="facebook/sam2.1-hiera-large", device=device) -# output = generator(image, points_per_batch=64) -# masks = output['masks'] -# # convert the binary tensors to PIL images -# from PIL import Image - -# pil_masks = [] -# for mask_tensor in masks: -# mask_numpy = (mask_tensor.squeeze().numpy() * 255).astype('uint8') -# pil_mask = Image.fromarray(mask_numpy, 'L') -# pil_masks.append(pil_mask) - -# return {"masks": pil_masks} \ No newline at end of file diff --git a/modules/Spandrel/__init__.py b/modules/Spandrel/__init__.py index 00b60e1..3fb1bf9 100644 --- a/modules/Spandrel/__init__.py +++ b/modules/Spandrel/__init__.py @@ -1,6 +1,5 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. from utils.torch_utils import DEVICE_LIST, DEFAULT_DEVICE -from utils.paths import list_files -from modiff.config import CONFIG from modiff.modelstore import modelstore MODULE_MAP = { @@ -8,7 +7,12 @@ "label": "Upscale with model", "category": "upscaler", "params": { - "image": { "label": "Image", "type": "image", "display": "input" }, + "image": { + "label": "Image or video frames", + "type": ["image", "video"], + "display": "input", + "required": True, + }, "model_id": { "label": "Model", "display": "modelselect", @@ -32,8 +36,10 @@ # "fieldOptions": { "optionLabel": "label", "noValidation": True } # }, "downscale": { "label": "Downscale", "type": "float", "default": 1.0, "min": 0.1, "max": 1.0, "step": 0.01, "display": "slider", "description": "Post downscaling factor. After the image is upscaled, it is downscaled by this factor." }, + "tile_size": { "label": "Tile size", "type": "int", "default": 256, "min": 0, "max": 2048, "step": 32, "description": "Input tile size. Use 0 only when full-frame inference is known to fit." }, + "tile_overlap": { "label": "Tile overlap", "type": "int", "default": 32, "min": 0, "max": 256, "step": 8, "description": "Context overlap cropped from each tile boundary before CPU-side stitching." }, "device": { "label": "Device", "type": "string", "default": DEFAULT_DEVICE, "options": DEVICE_LIST }, - "output": { "label": "Image", "type": "image", "display": "output" }, + "output": { "label": "Upscaled frames", "type": ["image", "video"], "display": "output" }, } }, } diff --git a/modules/Spandrel/main.py b/modules/Spandrel/main.py index aa507d1..7df57ea 100644 --- a/modules/Spandrel/main.py +++ b/modules/Spandrel/main.py @@ -1,4 +1,5 @@ -from os import path, makedirs +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. +from os import path import logging logger = logging.getLogger('modiff') @@ -21,6 +22,8 @@ def execute(self, **kwargs): model_source = model_id.get('source', 'local') if isinstance(model_id, dict) else 'local' model_path = model_id.get('value', None) if isinstance(model_id, dict) else model_id downscale = kwargs.get("downscale", 1.0) + tile_size = int(kwargs.get("tile_size", 256) or 0) + tile_overlap = int(kwargs.get("tile_overlap", 32) or 0) device = kwargs.get("device") online_status = CONFIG.hf['online_status'] @@ -28,7 +31,7 @@ def execute(self, **kwargs): raise ValueError("Model ID is required") if model_source == 'hub': - from utils.huggingface import cached_file_path, list_repo_models + from utils.huggingface import cached_file_path if model_path.endswith((".safetensors", ".pt", ".pth", ".ckpt", ".pkl", ".bin")): path_segments = model_path.split('/') @@ -39,21 +42,19 @@ def execute(self, **kwargs): repo_id = '/'.join(path_segments[:2]) model_path = cached_file_path(repo_id, file) else: - file = next(iter(list_repo_models(repo_id=model_path, token=CONFIG.hf['token'])), None) - repo_id = model_path - model_path = cached_file_path(repo_id, file) + raise ValueError( + "A Hub upscaler must include a pinned filename, for example " + "amd/realesrgan-x4plus/RealESRGAN_x4plus.pth. Install that file through Model Manager first." + ) if not model_path: if online_status == "Offline": #logger.error(f"Model {model_path} not found in cache and online status is `offline`. Consider changing the online status or adding the model to the cache manually.") raise FileNotFoundError(f"Model {model_path} not found in cache and online status is `offline`. Consider changing the online status or adding the model to the cache manually.") - from huggingface_hub import hf_hub_download - try: - model_path = hf_hub_download(repo_id, file, token=CONFIG.hf['token']) - except Exception as e: - logger.error(f"Error downloading model {model_path}: {e}") - raise + raise FileNotFoundError( + f"Upscaler {repo_id}/{file} is not installed. Install the pinned file through Model Manager first." + ) else: # check if path is absolute @@ -79,7 +80,11 @@ def execute(self, **kwargs): raise e self.mm_add(model, priority=0) - output = self.mm_exec(lambda: Upscaler.upscale(image, model), device, models=[model]) + output = self.mm_exec( + lambda: Upscaler.upscale(image, model, tile_size=tile_size, tile_overlap=tile_overlap), + device, + models=[model], + ) if downscale != 1.0: output = [resize(o, int(o.width * downscale), int(o.height * downscale), resample='LANCZOS') for o in output] @@ -87,7 +92,68 @@ def execute(self, **kwargs): return { "output": output } @staticmethod - def upscale(image, model): + def _upscale_tensor_tiled(image, model, tile_size=256, tile_overlap=32): + """Upscale one BCHW tensor without retaining full-resolution GPU features. + + ESRGAN-style networks can require tens of GiB of temporary activation + memory for a 1024px input even though the model itself is small. Process + overlapping input tiles and copy only each tile's non-overlap core into + a CPU output tensor. This keeps the node model-agnostic while preserving + seamless context around every tile boundary. + """ + import torch + + if image.ndim == 3: + image = image.unsqueeze(0) + height, width = image.shape[-2:] + tile_size = max(0, int(tile_size or 0)) + if tile_size == 0 or (height <= tile_size and width <= tile_size): + return model(image.to(model.device)).to("cpu") + + overlap = max(0, min(int(tile_overlap or 0), tile_size // 2)) + output = None + scale_y = scale_x = None + for top in range(0, height, tile_size): + bottom = min(top + tile_size, height) + input_top = max(0, top - overlap) + input_bottom = min(height, bottom + overlap) + for left in range(0, width, tile_size): + right = min(left + tile_size, width) + input_left = max(0, left - overlap) + input_right = min(width, right + overlap) + tile = image[..., input_top:input_bottom, input_left:input_right].to(model.device) + tile_output = model(tile).to("cpu") + del tile + + current_scale_y = tile_output.shape[-2] // (input_bottom - input_top) + current_scale_x = tile_output.shape[-1] // (input_right - input_left) + if current_scale_y <= 0 or current_scale_x <= 0: + raise ValueError("Upscaler returned an invalid spatial scale.") + if output is None: + scale_y, scale_x = current_scale_y, current_scale_x + output = torch.empty( + (tile_output.shape[0], tile_output.shape[1], height * scale_y, width * scale_x), + dtype=tile_output.dtype, + device="cpu", + ) + elif (current_scale_y, current_scale_x) != (scale_y, scale_x): + raise ValueError("Upscaler returned an inconsistent spatial scale between tiles.") + + crop_top = (top - input_top) * scale_y + crop_left = (left - input_left) * scale_x + crop_bottom = crop_top + (bottom - top) * scale_y + crop_right = crop_left + (right - left) * scale_x + output[..., top * scale_y:bottom * scale_y, left * scale_x:right * scale_x] = tile_output[ + ..., crop_top:crop_bottom, crop_left:crop_right + ] + del tile_output + + if output is None: + raise ValueError("Upscaler received an empty image tensor.") + return output + + @staticmethod + def upscale(image, model, tile_size=256, tile_overlap=32): image = ImageToTensor(image) image = image if isinstance(image, list) else [image] output = [] @@ -99,10 +165,17 @@ def upscale(image, model): if img.shape[1] == 4: img = img[:, :3] - output.append(model(img.to(model.device)).to('cpu')) + output.append( + Upscaler._upscale_tensor_tiled( + img, + model, + tile_size=tile_size, + tile_overlap=tile_overlap, + ) + ) del image if output: output = TensorToImage(output) - return output \ No newline at end of file + return output diff --git a/modules/Tensor/main.py b/modules/Tensor/main.py index 39c2505..90f8f0a 100644 --- a/modules/Tensor/main.py +++ b/modules/Tensor/main.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import torch from modiff.NodeBase import NodeBase @@ -176,4 +177,4 @@ def process_input(inp): return inp multiplied_tensor = process_input(tensor) - return { "output": multiplied_tensor } \ No newline at end of file + return { "output": multiplied_tensor } diff --git a/modules/Text/main.py b/modules/Text/main.py index 71b980c..4235969 100644 --- a/modules/Text/main.py +++ b/modules/Text/main.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. from modiff.NodeBase import NodeBase class TextToList(NodeBase): @@ -28,4 +29,4 @@ class TextToList(NodeBase): def execute(self, **kwargs): text = kwargs["text"] separator = kwargs["separator"] - return [item.strip() for item in text.split(separator)] \ No newline at end of file + return [item.strip() for item in text.split(separator)] diff --git a/modules/Video/main.py b/modules/Video/main.py index 1f85bf4..11120f9 100644 --- a/modules/Video/main.py +++ b/modules/Video/main.py @@ -1,8 +1,8 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. from modiff.NodeBase import NodeBase -from modiff.config import CONFIG +from modiff.path_identifiers import resolve_runtime_input_path from pathlib import Path import logging -from utils.torch_utils import DEVICE_LIST, DEFAULT_DEVICE from utils.paths import parse_filename logger = logging.getLogger('modiff') @@ -44,16 +44,15 @@ class Load(NodeBase): def execute(self, **kwargs): import imageio from PIL import Image - file = kwargs["file"] - file = Path(file[0] if isinstance(file, list) else file) + file_value = kwargs["file"] + file_value = file_value[0] if isinstance(file_value, list) and file_value else file_value + if not file_value: + raise ValueError("Load Video needs an existing video file.") + file = resolve_runtime_input_path(file_value) logger.debug(f"Loading video from file: {file}") - if file is None or file == "": - file = "" - if not Path(file).is_absolute(): - file = Path(CONFIG.paths['work_dir']) / file - if not Path(file).exists(): - file = "" + if not file.is_file(): + raise ValueError("Load Video needs an existing video file.") images = [] reader = imageio.get_reader(str(file), 'ffmpeg') @@ -164,7 +163,7 @@ def save_video(video_data): width, height, frame_count = 0, 0, 0 if isinstance(video_data, str): - reader = imageio.get_reader(video_data) + reader = imageio.get_reader(str(resolve_runtime_input_path(video_data))) meta = reader.get_meta_data() width, height = meta.get('size', (0, 0)) try: @@ -219,3 +218,1146 @@ def save_video(video_data): "height": height, "frames": frames, } + + +def _pil_frames(value): + """Normalize any supported in-memory video value to RGB PIL frames.""" + import numpy as np + from PIL import Image + try: + import torch + except ImportError: # pragma: no cover - torch is part of the runtime + torch = None + + if value is None or (isinstance(value, str) and not value): + return [] + if isinstance(value, dict) and (value.get("path") or value.get("file")): + value = value.get("path") or value.get("file") + if isinstance(value, str): + import imageio + reader = imageio.get_reader(str(resolve_runtime_input_path(value)), "ffmpeg") + try: + return [Image.fromarray(frame).convert("RGB") for frame in reader] + finally: + reader.close() + if torch is not None and isinstance(value, torch.Tensor): + tensor = value.detach().float().cpu() + if tensor.ndim == 5: + tensor = tensor.squeeze(0) + value = [tensor[index] for index in range(tensor.shape[0])] if tensor.ndim == 4 else [tensor] + if isinstance(value, np.ndarray) and value.ndim == 4: + value = list(value) + if not isinstance(value, list): + value = [value] + + frames = [] + for frame in value: + if isinstance(frame, Image.Image): + frames.append(frame.convert("RGB")) + continue + if torch is not None and isinstance(frame, torch.Tensor): + frame = frame.detach().float().cpu() + if frame.ndim == 4: + frame = frame.squeeze(0) + if frame.ndim == 3 and frame.shape[0] in (1, 3, 4): + frame = frame.permute(1, 2, 0) + frame = frame.numpy() + array = np.asarray(frame) + if array.dtype.kind == "f": + array = np.clip(array, 0, 1) * 255 + if array.ndim == 3 and array.shape[0] in (1, 3, 4): + array = np.moveaxis(array, 0, -1) + frames.append(Image.fromarray(array.astype(np.uint8)).convert("RGB")) + return frames + + +class MaskedComposite(NodeBase): + """Composite generated video only inside a white mask sequence.""" + + label = "Masked Video Composite" + category = "Video" + resizable = True + params = { + "source": {"label": "Source Video", "display": "input", "type": ["video", "str"]}, + "generated": {"label": "Generated Video", "display": "input", "type": ["video", "str"]}, + "mask": {"label": "White Generate Mask", "display": "input", "type": ["video", "image"]}, + "feather": {"label": "Edge Feather", "type": "float", "default": 0.0, "min": 0, "max": 128, "step": 1}, + "output": {"label": "Video", "display": "output", "type": "video"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + from PIL import Image, ImageFilter + + source = _pil_frames(kwargs.get("source")) + generated = _pil_frames(kwargs.get("generated")) + masks = _pil_frames(kwargs.get("mask")) + if not source or not generated or not masks: + raise ValueError("Masked Video Composite needs source, generated, and mask frames.") + if len(source) != len(generated): + raise ValueError( + f"Masked Video Composite frame mismatch: source has {len(source)} frames and generated has " + f"{len(generated)}." + ) + if len(masks) == 1: + masks = masks * len(source) + elif len(masks) != len(source): + raise ValueError( + f"Masked Video Composite mask mismatch: expected 1 or {len(source)} masks; received {len(masks)}." + ) + + feather = max(0.0, float(kwargs.get("feather") or 0.0)) + output = [] + for source_frame, generated_frame, mask_frame in zip(source, generated, masks): + size = source_frame.size + if generated_frame.size != size: + generated_frame = generated_frame.resize(size, Image.Resampling.LANCZOS) + mask_image = mask_frame.convert("L") + if mask_image.size != size: + mask_image = mask_image.resize(size, Image.Resampling.LANCZOS) + if feather > 0: + mask_image = mask_image.filter(ImageFilter.GaussianBlur(radius=feather)) + output.append(Image.composite(generated_frame, source_frame, mask_image)) + return {"output": output, "frames": len(output)} + + +class TemporalCleanPlate(NodeBase): + """Build a person-free plate sequence by interpolating two clean frames.""" + + label = "Temporal Clean Plate" + category = "Video" + resizable = True + params = { + "video": {"label": "Video", "display": "input", "type": ["video", "str"]}, + "start_index": {"label": "Clean Start Frame", "type": "int", "default": 0, "min": -100000}, + "end_index": {"label": "Clean End Frame", "type": "int", "default": -1, "min": -100000}, + "easing": {"label": "Interpolation", "type": "string", "options": ["smoothstep", "linear"], "default": "smoothstep"}, + "output": {"label": "Plate Video", "display": "output", "type": "video"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + from PIL import Image + + frames = _pil_frames(kwargs.get("video")) + if not frames: + raise ValueError("Temporal Clean Plate needs a non-empty video.") + + def normalize_index(value, fallback): + index = int(value if value is not None else fallback) + if index < 0: + index += len(frames) + return max(0, min(len(frames) - 1, index)) + + start_index = normalize_index(kwargs.get("start_index"), 0) + end_index = normalize_index(kwargs.get("end_index"), len(frames) - 1) + start = frames[start_index] + end = frames[end_index] + if end.size != start.size: + end = end.resize(start.size, Image.Resampling.LANCZOS) + easing = str(kwargs.get("easing") or "smoothstep") + output = [] + denominator = max(1, len(frames) - 1) + for index in range(len(frames)): + alpha = index / denominator + if easing == "smoothstep": + alpha = alpha * alpha * (3.0 - 2.0 * alpha) + elif easing != "linear": + raise ValueError(f"Unsupported clean-plate interpolation {easing!r}.") + output.append(Image.blend(start, end, alpha)) + return {"output": output, "frames": len(output)} + + +class ExtendCleanPlate(NodeBase): + """Copy a clean background strip into a neighboring occluded region.""" + + label = "Extend Video Clean Plate" + category = "Video" + resizable = True + params = { + "video": {"label": "Plate Video", "display": "input", "type": ["video", "str"]}, + "boundary_x": {"label": "Clean Boundary X", "type": "int", "default": 535, "min": 1, "max": 16384}, + "extend_left": {"label": "Extend Left", "type": "int", "default": 20, "min": 1, "max": 2048}, + "mode": {"label": "Extension", "type": "string", "options": ["copy", "mirror"], "default": "copy"}, + "top": {"label": "Top", "type": "int", "default": 0, "min": 0, "max": 16384}, + "bottom": {"label": "Bottom", "type": "int", "default": 230, "min": 1, "max": 16384}, + "output": {"label": "Extended Plate", "display": "output", "type": "video"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + from PIL import Image + + frames = _pil_frames(kwargs.get("video")) + if not frames: + raise ValueError("Extend Video Clean Plate needs a non-empty video.") + boundary = int(kwargs.get("boundary_x", 535)) + extend = int(kwargs.get("extend_left", 20)) + top = int(kwargs.get("top", 0)) + bottom = int(kwargs.get("bottom", 230)) + mode = str(kwargs.get("mode") or "copy") + if mode not in {"copy", "mirror"}: + raise ValueError(f"Unsupported clean-plate extension {mode!r}.") + output = [] + for frame in frames: + width, height = frame.size + if not 0 < boundary < width: + raise ValueError(f"Clean boundary X must be inside the frame; received {boundary} for width {width}.") + actual_extend = min(extend, boundary, width - boundary) + actual_top = max(0, min(height - 1, top)) + actual_bottom = max(actual_top + 1, min(height, bottom)) + result = frame.copy() + clean_strip = frame.crop((boundary, actual_top, boundary + actual_extend, actual_bottom)) + if mode == "mirror": + clean_strip = clean_strip.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + result.paste(clean_strip, (boundary - actual_extend, actual_top)) + output.append(result) + return {"output": output, "frames": len(output)} + + +class Compose(NodeBase): + """Compose two to six model-agnostic clips into one continuous timeline.""" + + label = "Compose Video" + category = "Video" + resizable = True + params = { + # Keep these explicit: MoDiff's static AST registry intentionally does + # not execute dict comprehensions while discovering node contracts. + "clip_1": {"label": "Clip 1", "display": "input", "type": ["video_collection", "video", "str"], "required": True}, + "clip_2": {"label": "Clip 2", "display": "input", "type": ["video_collection", "video", "str"], "required": False}, + "clip_3": {"label": "Clip 3", "display": "input", "type": ["video_collection", "video", "str"], "required": False}, + "clip_4": {"label": "Clip 4", "display": "input", "type": ["video_collection", "video", "str"], "required": False}, + "clip_5": {"label": "Clip 5", "display": "input", "type": ["video_collection", "video", "str"], "required": False}, + "clip_6": {"label": "Clip 6", "display": "input", "type": ["video_collection", "video", "str"], "required": False}, + "transition_seconds": {"label": "Crossfade", "type": "float", "default": 0.35, "min": 0, "max": 2, "step": 0.05}, + "fps": {"label": "FPS", "type": "float", "default": 16, "min": 1, "max": 120, "step": 0.01}, + "video": {"display": "output", "type": "video"}, + "frames": {"display": "output", "type": "int"}, + "duration_seconds": {"display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + from PIL import Image + clips = [] + for index in range(1, 7): + value = kwargs.get(f"clip_{index}") + if isinstance(value, list) and value and isinstance(value[0], list): + clips.extend(_pil_frames(item) for item in value) + else: + clips.append(_pil_frames(value)) + clips = [clip for clip in clips if clip] + if not clips: + raise ValueError("Compose Video needs at least one non-empty clip.") + fps = float(kwargs.get("fps") or 16) + fade_frames = max(0, int(round(float(kwargs.get("transition_seconds") or 0) * fps))) + target_size = clips[0][0].size + clips = [[frame.resize(target_size, Image.Resampling.LANCZOS) if frame.size != target_size else frame for frame in clip] for clip in clips] + output = list(clips[0]) + for clip in clips[1:]: + overlap = min(fade_frames, len(output), len(clip)) + if overlap: + start = len(output) - overlap + for index in range(overlap): + alpha = (index + 1) / (overlap + 1) + output[start + index] = Image.blend(output[start + index], clip[index], alpha) + output.extend(clip[overlap:]) + return {"video": output, "frames": len(output), "duration_seconds": len(output) / fps} + + +class LyricOverlay(NodeBase): + """Render an authored LRC timeline over any video model's frames.""" + + label = "Timed Lyric Overlay" + category = "Video" + resizable = True + params = { + "video": {"display": "input", "type": ["video", "str"]}, + "lrc": {"label": "Timed Lyrics (LRC)", "display": "textarea", "type": "text", "default": ""}, + "fps": {"label": "FPS", "type": "float", "default": 16, "min": 1, "max": 120, "step": 0.01}, + "font_size": {"label": "Font Size", "type": "int", "default": 42, "min": 12, "max": 160}, + "bottom_margin": {"label": "Bottom Margin", "type": "int", "default": 54, "min": 0, "max": 400}, + "output": {"display": "output", "type": "video"}, + } + + @staticmethod + def _timeline(text): + import re + entries = [] + for line in str(text or "").splitlines(): + match = re.match(r"\s*\[(\d+):(\d+(?:\.\d+)?)\]\s*(.+?)\s*$", line) + if match: + entries.append((int(match.group(1)) * 60 + float(match.group(2)), match.group(3))) + return sorted(entries) + + def execute(self, **kwargs): + from PIL import ImageDraw, ImageFont + frames = _pil_frames(kwargs.get("video")) + timeline = self._timeline(kwargs.get("lrc")) + if not frames or not timeline: + raise ValueError("Timed Lyric Overlay needs video frames and at least one [mm:ss] lyric line.") + fps = float(kwargs.get("fps") or 16) + font_size = int(kwargs.get("font_size") or 42) + margin = int(kwargs.get("bottom_margin") or 54) + try: + font = ImageFont.truetype("DejaVuSans-Bold.ttf", font_size) + except OSError: + font = ImageFont.load_default() + output = [] + for frame_index, source in enumerate(frames): + timestamp = frame_index / fps + active = next((text for start, text in reversed(timeline) if start <= timestamp), "") + frame = source.copy() + if active: + draw = ImageDraw.Draw(frame) + box = draw.textbbox((0, 0), active, font=font, stroke_width=2) + x = max(16, (frame.width - (box[2] - box[0])) // 2) + y = max(16, frame.height - margin - (box[3] - box[1])) + draw.text((x, y), active, font=font, fill="white", stroke_width=3, stroke_fill="black") + output.append(frame) + return {"output": output} + + +class ExportWithAudio(NodeBase): + """Export composed frames with generated or loaded audio in one MP4.""" + + label = "Export Video with Audio" + category = "Video" + resizable = True + params = { + "video": {"display": "input", "type": ["video", "str"]}, + "audio": {"display": "input", "type": ["audio", "str"]}, + "filename": {"label": "File", "type": "str", "default": "{PATH:videos}/MoDiff_{HASH:6}.mp4"}, + "fps": {"label": "FPS", "type": "float", "default": 16, "min": 1, "max": 120, "step": 0.01}, + "quality": {"display": "slider", "type": "int", "min": 1, "max": 10, "default": 8}, + "preview": {"display": "ui_video", "type": "url", "dataSource": "file"}, + "file": {"type": "video", "display": "output"}, + "frames": {"display": "output", "type": "int"}, + "duration_seconds": {"display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + import imageio + import numpy as np + import subprocess + from scipy.io import wavfile + from imageio_ffmpeg import get_ffmpeg_exe + + frames = _pil_frames(kwargs.get("video")) + if not frames: + raise ValueError("Export Video with Audio needs non-empty video frames.") + fps = float(kwargs.get("fps") or 16) + destination = Path(parse_filename(kwargs.get("filename") or "{PATH:videos}/MoDiff_{HASH:6}.mp4")) + destination.parent.mkdir(parents=True, exist_ok=True) + silent_path = destination.with_suffix(".silent.mp4") + audio_path = destination.with_suffix(".audio.wav") + + audio = kwargs.get("audio") + if isinstance(audio, str): + audio_path = resolve_runtime_input_path(audio) + else: + data = audio.get("samples", audio.get("audio")) if isinstance(audio, dict) else audio + sample_rate = int(audio.get("sample_rate", 48000)) if isinstance(audio, dict) else 48000 + samples = np.asarray(data, dtype=np.float32) + while samples.ndim > 2: + samples = samples[0] + if samples.ndim == 2 and samples.shape[0] <= 8 and samples.shape[1] > samples.shape[0]: + samples = samples.T + wavfile.write(audio_path, sample_rate, (np.clip(samples, -1, 1) * 32767).astype(np.int16)) + + writer = imageio.get_writer(silent_path, fps=fps, quality=int(kwargs.get("quality") or 8), codec="libx264") + try: + for frame in frames: + writer.append_data(np.asarray(frame)) + finally: + writer.close() + subprocess.run( + [get_ffmpeg_exe(), "-y", "-v", "error", "-i", str(silent_path), "-i", str(audio_path), + "-c:v", "copy", "-c:a", "aac", "-b:a", "256k", "-shortest", str(destination)], + check=True, + ) + silent_path.unlink(missing_ok=True) + if audio_path.parent == destination.parent and audio_path.name.endswith(".audio.wav"): + audio_path.unlink(missing_ok=True) + return {"file": str(destination), "frames": len(frames), "duration_seconds": len(frames) / fps} + + +def _video_collection(value): + """Normalize a collection of clips without confusing one clip with many.""" + if value in (None, ""): + return [] + if isinstance(value, tuple): + value = list(value) + if not isinstance(value, list): + return [_pil_frames(value)] + if not value: + return [] + if isinstance(value[0], list): + return [_pil_frames(item) for item in value if item] + if isinstance(value[0], str) and len(value) > 1: + return [_pil_frames(item) for item in value if item] + return [_pil_frames(value)] + + +def _parse_numbers(value, *, cast=float): + if value in (None, ""): + return [] + if isinstance(value, str): + values = value.replace("\n", ",").split(",") + elif isinstance(value, (list, tuple, set)): + values = value + else: + values = [value] + return [cast(item) for item in values if str(item).strip()] + + +def _concatenate_clips(clips, *, transition_frames=0): + from PIL import Image + + clips = [list(clip) for clip in clips if clip] + if not clips: + return [] + target_size = clips[0][0].size + normalized = [ + [frame.resize(target_size, Image.Resampling.LANCZOS) if frame.size != target_size else frame for frame in clip] + for clip in clips + ] + output = list(normalized[0]) + for clip in normalized[1:]: + overlap = min(max(0, int(transition_frames)), len(output), len(clip)) + if overlap: + start = len(output) - overlap + for index in range(overlap): + alpha = (index + 1) / (overlap + 1) + output[start + index] = Image.blend(output[start + index], clip[index], alpha) + output.extend(clip[overlap:]) + return output + + +class FrameExtract(NodeBase): + """Extract ordered frames by boundary, index, timecode, or interval.""" + + label = "Extract Video Frames" + category = "Video" + resizable = True + params = { + "video": {"label": "Video", "display": "input", "type": ["video_asset", "video", "str"]}, + "mode": { + "label": "Selection", + "type": "string", + "options": ["first", "last", "first_last", "indices", "timecodes", "every_n"], + "default": "first_last", + }, + "indices": {"label": "Frame Indices", "type": "string", "default": "0,-1"}, + "timecodes": {"label": "Times (seconds)", "type": "string", "default": "0"}, + "every_n": {"label": "Every N Frames", "type": "int", "default": 16, "min": 1}, + "fps": {"label": "FPS", "type": "float", "default": 16, "min": 0.01}, + "frames": {"label": "Frames", "display": "output", "type": "image"}, + "selected_indices": {"label": "Indices", "display": "output", "type": "collection"}, + "timestamps": {"label": "Timestamps", "display": "output", "type": "collection"}, + } + + def execute(self, **kwargs): + value = kwargs.get("video") + file_asset = None + if (isinstance(value, dict) and (value.get("path") or value.get("file"))) or isinstance(value, str): + from modiff.media_assets import coerce_video_asset + + file_asset = coerce_video_asset(value) + frame_count = int(file_asset["frame_count"]) + else: + frames = _pil_frames(value) + frame_count = len(frames) + if frame_count < 1: + raise ValueError("Extract Video Frames needs a non-empty video.") + mode = str(kwargs.get("mode") or "first_last") + if mode == "first": + indices = [0] + elif mode == "last": + indices = [frame_count - 1] + elif mode == "first_last": + indices = [0, frame_count - 1] + elif mode == "indices": + indices = _parse_numbers(kwargs.get("indices"), cast=int) + elif mode == "timecodes": + fps = float(kwargs.get("fps") or 16) + indices = [round(value * fps) for value in _parse_numbers(kwargs.get("timecodes"), cast=float)] + elif mode == "every_n": + step = max(1, int(kwargs.get("every_n") or 1)) + indices = list(range(0, frame_count, step)) + else: + raise ValueError(f"Unsupported frame selection mode {mode!r}.") + normalized = [] + for index in indices: + index = int(index) + if index < 0: + index += frame_count + if 0 <= index < frame_count and index not in normalized: + normalized.append(index) + if not normalized: + raise ValueError("The requested frame selection is outside this video.") + fps = float(file_asset["fps"] if file_asset and file_asset.get("fps") else kwargs.get("fps") or 16) + if file_asset: + import imageio.v2 as imageio + from PIL import Image + + reader = imageio.get_reader(file_asset["path"], "ffmpeg") + try: + selected_frames = [Image.fromarray(reader.get_data(index)).convert("RGB") for index in normalized] + finally: + reader.close() + else: + selected_frames = [frames[index] for index in normalized] + return { + "frames": selected_frames, + "selected_indices": normalized, + "timestamps": [index / fps for index in normalized], + } + + +class Trim(NodeBase): + """Trim a video by frame range or seconds.""" + + label = "Trim Video" + category = "Video" + params = { + "video": {"label": "Video", "display": "input", "type": ["video", "str"]}, + "range_mode": {"label": "Range", "type": "string", "options": ["frames", "seconds"], "default": "seconds"}, + "start": {"label": "Start", "type": "float", "default": 0, "min": 0}, + "end": {"label": "End (0 = end)", "type": "float", "default": 0, "min": 0}, + "fps": {"label": "FPS", "type": "float", "default": 16, "min": 0.01}, + "output": {"label": "Video", "display": "output", "type": "video"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + "duration_seconds": {"label": "Duration", "display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + frames = _pil_frames(kwargs.get("video")) + fps = float(kwargs.get("fps") or 16) + scale = fps if kwargs.get("range_mode") == "seconds" else 1 + start = max(0, int(round(float(kwargs.get("start") or 0) * scale))) + end_value = float(kwargs.get("end") or 0) + end = int(round(end_value * scale)) if end_value > 0 else len(frames) + if end < start: + raise ValueError("Trim Video end must not be before start.") + output = frames[start:min(end, len(frames))] + return {"output": output, "frames": len(output), "duration_seconds": len(output) / fps} + + +class Concatenate(NodeBase): + """Concatenate an arbitrary ordered clip collection with optional crossfades.""" + + label = "Concatenate Videos" + category = "Video" + params = { + "clips": {"label": "Clips", "display": "input", "type": ["video_collection", "collection", "video"]}, + "transition_seconds": {"label": "Crossfade", "type": "float", "default": 0, "min": 0}, + "fps": {"label": "FPS", "type": "float", "default": 16, "min": 0.01}, + "output": {"label": "Video", "display": "output", "type": "video"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + "duration_seconds": {"label": "Duration", "display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + clips = _video_collection(kwargs.get("clips")) + if not clips: + raise ValueError("Concatenate Videos needs at least one clip.") + fps = float(kwargs.get("fps") or 16) + transition = round(max(0.0, float(kwargs.get("transition_seconds") or 0)) * fps) + output = _concatenate_clips(clips, transition_frames=transition) + return {"output": output, "frames": len(output), "duration_seconds": len(output) / fps} + + +class StackTile(NodeBase): + """Arrange a collection of videos into a synchronized video wall.""" + + label = "Stack / Tile Videos" + category = "Video" + params = { + "videos": {"label": "Videos", "display": "input", "type": ["video_collection", "collection"]}, + "columns": {"label": "Columns", "type": "int", "default": 2, "min": 1}, + "sync": {"label": "Length", "type": "string", "options": ["shortest", "longest_hold"], "default": "longest_hold"}, + "gap": {"label": "Gap", "type": "int", "default": 0, "min": 0}, + "background": {"label": "Background", "type": "string", "default": "black"}, + "output": {"label": "Video", "display": "output", "type": "video"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + from math import ceil + from PIL import Image, ImageColor + + clips = _video_collection(kwargs.get("videos")) + if not clips: + raise ValueError("Stack / Tile Videos needs at least one clip.") + width, height = clips[0][0].size + columns = max(1, int(kwargs.get("columns") or 1)) + rows = ceil(len(clips) / columns) + gap = max(0, int(kwargs.get("gap") or 0)) + count = min(map(len, clips)) if kwargs.get("sync") == "shortest" else max(map(len, clips)) + output = [] + for frame_index in range(count): + canvas = Image.new( + "RGB", + (columns * width + max(0, columns - 1) * gap, rows * height + max(0, rows - 1) * gap), + ImageColor.getrgb(str(kwargs.get("background") or "black")), + ) + for clip_index, clip in enumerate(clips): + source = clip[min(frame_index, len(clip) - 1)] + if source.size != (width, height): + source = source.resize((width, height), Image.Resampling.LANCZOS) + left = (clip_index % columns) * (width + gap) + top = (clip_index // columns) * (height + gap) + canvas.paste(source, (left, top)) + output.append(canvas) + return {"output": output, "frames": len(output)} + + +class Reverse(NodeBase): + """Reverse frame order, optionally excluding duplicate endpoints for looping.""" + + label = "Reverse Video" + category = "Video" + params = { + "video": {"label": "Video", "display": "input", "type": ["video", "str"]}, + "exclude_endpoints": {"label": "Exclude Endpoints", "type": "bool", "default": False}, + "output": {"label": "Video", "display": "output", "type": "video"}, + } + + def execute(self, **kwargs): + frames = _pil_frames(kwargs.get("video")) + output = list(reversed(frames[1:-1] if kwargs.get("exclude_endpoints") and len(frames) > 2 else frames)) + return {"output": output} + + +class Crossfade(NodeBase): + """Join two clips with a frame-accurate crossfade.""" + + label = "Crossfade Videos" + category = "Video" + params = { + "first": {"label": "First", "display": "input", "type": ["video", "str"]}, + "second": {"label": "Second", "display": "input", "type": ["video", "str"]}, + "duration_seconds": {"label": "Duration", "type": "float", "default": 0.35, "min": 0}, + "fps": {"label": "FPS", "type": "float", "default": 16, "min": 0.01}, + "output": {"label": "Video", "display": "output", "type": "video"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + fps = float(kwargs.get("fps") or 16) + output = _concatenate_clips( + [_pil_frames(kwargs.get("first")), _pil_frames(kwargs.get("second"))], + transition_frames=round(max(0.0, float(kwargs.get("duration_seconds") or 0)) * fps), + ) + if not output: + raise ValueError("Crossfade Videos needs two non-empty clips.") + return {"output": output, "frames": len(output)} + + +class FirstLastSegmentBuilder(NodeBase): + """Turn ordered keyframes into deterministic first/last-frame generation jobs.""" + + label = "Build First / Last Segments" + category = "Video" + params = { + "keyframes": {"label": "Keyframes", "display": "input", "type": "image"}, + "prompts": {"label": "Segment Prompts", "display": "textarea", "type": "text", "default": "[]"}, + "settings": {"label": "Shared Settings (JSON)", "display": "textarea", "type": "text", "default": "{}"}, + "jobs": {"label": "Segment Jobs", "display": "output", "type": "collection"}, + "count": {"label": "Segments", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + import json + + keyframes = kwargs.get("keyframes") + keyframes = keyframes if isinstance(keyframes, list) else [keyframes] if keyframes is not None else [] + if len(keyframes) < 2: + raise ValueError("Build First / Last Segments needs at least two keyframes.") + raw_prompts = kwargs.get("prompts") + if isinstance(raw_prompts, str): + text = raw_prompts.strip() + if text.startswith("["): + prompts = json.loads(text) + else: + prompts = text.splitlines() + else: + prompts = list(raw_prompts or []) + settings = kwargs.get("settings") + settings = json.loads(settings or "{}") if isinstance(settings, str) else dict(settings or {}) + if not isinstance(settings, dict): + raise ValueError("Shared Settings must be a JSON object.") + jobs = [] + for index in range(len(keyframes) - 1): + jobs.append( + { + "index": index, + "first_frame": keyframes[index], + "last_frame": keyframes[index + 1], + "prompt": str(prompts[index]) if index < len(prompts) else "", + "settings": dict(settings), + } + ) + return {"jobs": jobs, "count": len(jobs)} + + +class KeyframeChain(NodeBase): + """Assemble clips collected from a loop over first/last-frame segment jobs.""" + + label = "Assemble Keyframe Chain" + category = "Video" + params = { + "clips": {"label": "Generated Clips", "display": "input", "type": ["video_collection", "collection"]}, + "boundary": {"label": "Boundary", "type": "string", "options": ["keep", "drop_duplicate", "crossfade"], "default": "drop_duplicate"}, + "crossfade_seconds": {"label": "Crossfade", "type": "float", "default": 0.2, "min": 0}, + "fps": {"label": "FPS", "type": "float", "default": 16, "min": 0.01}, + "output": {"label": "Video", "display": "output", "type": "video"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + clips = _video_collection(kwargs.get("clips")) + if not clips: + raise ValueError("Assemble Keyframe Chain needs generated clips.") + boundary = str(kwargs.get("boundary") or "drop_duplicate") + if boundary == "drop_duplicate": + clips = [clips[0], *[clip[1:] if len(clip) > 1 else [] for clip in clips[1:]]] + transition = 0 + elif boundary == "crossfade": + transition = round(float(kwargs.get("crossfade_seconds") or 0) * float(kwargs.get("fps") or 16)) + elif boundary == "keep": + transition = 0 + else: + raise ValueError(f"Unsupported keyframe boundary policy {boundary!r}.") + output = _concatenate_clips(clips, transition_frames=transition) + return {"output": output, "frames": len(output)} + + +class ExportAsset(NodeBase): + """Stream video frames to a retained file-backed asset.""" + + label = "Export Retained Video Asset" + category = "Video" + resizable = True + params = { + "video": {"label": "Video", "display": "input", "type": ["video_asset", "video", "str"]}, + "fps": {"label": "FPS", "type": "float", "default": 16, "min": 1, "max": 120}, + "quality": {"label": "Quality", "type": "int", "default": 8, "min": 1, "max": 10}, + "pin": {"label": "Protect From Cleanup", "type": "bool", "default": False}, + "preview": {"display": "ui_video", "type": "url", "dataSource": "file"}, + "asset": {"label": "Video Asset", "display": "output", "type": "video_asset"}, + "file": {"label": "File", "display": "output", "type": "video"}, + "duration_seconds": {"label": "Duration", "display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + import imageio + import numpy as np + from modiff.media_assets import ( + allocate_video_path, + coerce_video_asset, + current_task_id, + register_derived_video_asset, + register_video_asset, + run_ffmpeg, + ) + + video = kwargs.get("video") + task_id = current_task_id() + asset_id, destination = allocate_video_path(task_id=task_id) + fps = float(kwargs.get("fps") or 16) + if isinstance(video, (str, dict)): + source = coerce_video_asset(video) + quality = int(kwargs.get("quality") or 8) + run_ffmpeg( + [ + "-i", source["path"], + "-map", "0:v:0", "-map", "0:a?", + "-vf", f"fps={fps}", + "-c:v", "libx264", "-crf", str(max(12, 32 - quality * 2)), + "-c:a", "aac", "-movflags", "+faststart", + ], + destination, + ) + asset = register_derived_video_asset( + destination, + asset_id=asset_id, + task_id=task_id, + source_assets=[source], + operation="retain", + pinned=bool(kwargs.get("pin", False)), + ) + return {"asset": asset, "file": str(destination), "duration_seconds": asset["duration_seconds"]} + + frames = _pil_frames(video) + if not frames: + raise ValueError("Export Retained Video Asset needs non-empty video frames.") + writer = imageio.get_writer( + destination, + fps=fps, + quality=int(kwargs.get("quality") or 8), + codec="libx264", + ) + try: + for index, frame in enumerate(frames): + writer.append_data(np.asarray(frame.convert("RGB"))) + if index % max(1, len(frames) // 100) == 0: + self.progress(index / len(frames), phase="encoding", message="Writing retained video") + finally: + writer.close() + width, height = frames[0].size + asset = register_video_asset( + destination, + asset_id=asset_id, + task_id=task_id, + width=width, + height=height, + fps=fps, + frame_count=len(frames), + temporary=True, + pinned=bool(kwargs.get("pin", False)), + ) + return {"asset": asset, "file": str(destination), "duration_seconds": asset["duration_seconds"]} + + +def _file_asset_collection(value): + from modiff.media_assets import coerce_video_asset + + values = list(value) if isinstance(value, (list, tuple)) else [value] + assets = [coerce_video_asset(item) for item in values if item not in (None, "")] + if not assets: + raise ValueError("At least one retained video asset or file path is required.") + return assets + + +def _video_filter(asset, label, *, width, height, fps, duration=None): + # `xfade` rejects inputs whose filter-link frame rate is unspecified. The + # source MP4 can be perfectly CFR while a preceding `xfade` link still + # reports 1/0, so normalize both the rate and time base explicitly. + fps_text = f"{float(fps):.12g}" + expression = ( + f"[{label}:v]scale={width}:{height}:force_original_aspect_ratio=decrease," + f"pad={width}:{height}:(ow-iw)/2:(oh-ih)/2,setsar=1," + f"settb=expr=1/{fps_text},setpts=PTS-STARTPTS,fps={fps_text}" + ) + if duration is not None: + expression += f",tpad=stop_mode=clone:stop_duration={max(0.0, duration)}" + return expression + + +def _derived_asset_result(destination, asset_id, sources, operation, *, pin=False): + from modiff.media_assets import current_task_id, register_derived_video_asset + + asset = register_derived_video_asset( + destination, + asset_id=asset_id, + task_id=current_task_id(), + source_assets=sources, + operation=operation, + pinned=pin, + ) + return { + "asset": asset, + "file": str(destination), + "duration_seconds": asset["duration_seconds"], + "frames": asset["frame_count"], + } + + +class TrimAsset(NodeBase): + """Trim a retained video without loading its full frame sequence into memory.""" + + label = "Trim Retained Video" + category = "Video" + params = { + "video": {"label": "Video", "display": "input", "type": ["video_asset", "str"]}, + "start_seconds": {"label": "Start", "type": "float", "default": 0, "min": 0}, + "end_seconds": {"label": "End (0 = end)", "type": "float", "default": 0, "min": 0}, + "pin": {"label": "Protect From Cleanup", "type": "bool", "default": False}, + "preview": {"display": "ui_video", "type": "url", "dataSource": "file"}, + "asset": {"label": "Video Asset", "display": "output", "type": "video_asset"}, + "file": {"label": "File", "display": "output", "type": "video"}, + "duration_seconds": {"label": "Duration", "display": "output", "type": "float"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + from modiff.media_assets import allocate_video_path, current_task_id, run_ffmpeg + + source = _file_asset_collection(kwargs.get("video"))[0] + start = max(0.0, float(kwargs.get("start_seconds") or 0)) + end = float(kwargs.get("end_seconds") or 0) + if end and end <= start: + raise ValueError("Trim Retained Video end must be after start.") + asset_id, destination = allocate_video_path(task_id=current_task_id()) + args = ["-ss", str(start)] + if end: + args += ["-to", str(end)] + args += [ + "-i", source["path"], "-map", "0:v:0", "-map", "0:a?", + "-c:v", "libx264", "-crf", "18", "-c:a", "aac", "-movflags", "+faststart", + ] + run_ffmpeg(args, destination) + return _derived_asset_result(destination, asset_id, [source], "trim", pin=bool(kwargs.get("pin"))) + + +class ConcatenateAssets(NodeBase): + """Join retained videos through FFmpeg with bounded process memory.""" + + label = "Join Retained Videos" + category = "Video" + params = { + "clips": {"label": "Clips", "display": "input", "type": ["video_asset_collection", "collection"]}, + "transition_seconds": {"label": "Crossfade", "type": "float", "default": 0, "min": 0, "max": 5}, + "pin": {"label": "Protect From Cleanup", "type": "bool", "default": False}, + "preview": {"display": "ui_video", "type": "url", "dataSource": "file"}, + "asset": {"label": "Video Asset", "display": "output", "type": "video_asset"}, + "file": {"label": "File", "display": "output", "type": "video"}, + "duration_seconds": {"label": "Duration", "display": "output", "type": "float"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + from modiff.media_assets import allocate_video_path, current_task_id, run_ffmpeg + + sources = _file_asset_collection(kwargs.get("clips")) + first = sources[0] + width, height = int(first["width"]), int(first["height"]) + fps = float(first["fps"] or 16) + transition = max(0.0, float(kwargs.get("transition_seconds") or 0)) + filters = [_video_filter(source, index, width=width, height=height, fps=fps) + f"[v{index}]" for index, source in enumerate(sources)] + if len(sources) == 1: + filters.append("[v0]null[outv]") + elif transition <= 0: + filters.append("".join(f"[v{index}]" for index in range(len(sources))) + f"concat=n={len(sources)}:v=1:a=0[outv]") + else: + previous = "v0" + elapsed = float(first["duration_seconds"]) + fps_text = f"{fps:.12g}" + for index, source in enumerate(sources[1:], 1): + usable = min(transition, max(0.001, elapsed - 1 / fps), max(0.001, float(source["duration_seconds"]) - 1 / fps)) + output = "outv" if index == len(sources) - 1 else f"x{index}" + raw_output = f"raw_{output}" + offset = max(0.0, elapsed - usable) + filters.append( + f"[{previous}][v{index}]xfade=transition=fade:duration={usable}:offset={offset}[{raw_output}]" + ) + # FFmpeg 7 can drop the negotiated frame-rate metadata from an + # xfade output. Reassert it before feeding that link into the + # next xfade; otherwise a chain of three or more clips fails + # with `current rate of 1/0 is invalid`. + filters.append( + f"[{raw_output}]settb=expr=1/{fps_text},setpts=PTS-STARTPTS,fps={fps_text}[{output}]" + ) + previous = output + elapsed += float(source["duration_seconds"]) - usable + asset_id, destination = allocate_video_path(task_id=current_task_id()) + inputs = [part for source in sources for part in ("-i", source["path"])] + run_ffmpeg( + inputs + + [ + "-filter_complex", ";".join(filters), "-map", "[outv]", + "-r", str(fps), "-an", "-c:v", "libx264", "-crf", "18", "-movflags", "+faststart", + ], + destination, + ) + return _derived_asset_result(destination, asset_id, sources, "concatenate", pin=bool(kwargs.get("pin"))) + + +class StackTileAssets(NodeBase): + """Build a synchronized retained-video wall without Python frame materialization.""" + + label = "Tile Retained Videos" + category = "Video" + params = { + "videos": {"label": "Videos", "display": "input", "type": ["video_asset_collection", "collection"]}, + "columns": {"label": "Columns", "type": "int", "default": 2, "min": 1}, + "sync": {"label": "Length", "type": "string", "options": ["shortest", "longest_hold"], "default": "longest_hold"}, + "gap": {"label": "Gap", "type": "int", "default": 0, "min": 0}, + "background": {"label": "Background", "type": "string", "default": "black"}, + "pin": {"label": "Protect From Cleanup", "type": "bool", "default": False}, + "preview": {"display": "ui_video", "type": "url", "dataSource": "file"}, + "asset": {"label": "Video Asset", "display": "output", "type": "video_asset"}, + "file": {"label": "File", "display": "output", "type": "video"}, + "duration_seconds": {"label": "Duration", "display": "output", "type": "float"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + from modiff.media_assets import allocate_video_path, current_task_id, run_ffmpeg + + sources = _file_asset_collection(kwargs.get("videos")) + first = sources[0] + width, height, fps = int(first["width"]), int(first["height"]), float(first["fps"] or 16) + columns = max(1, int(kwargs.get("columns") or 1)) + gap = max(0, int(kwargs.get("gap") or 0)) + durations = [float(source["duration_seconds"]) for source in sources] + output_duration = min(durations) if kwargs.get("sync") == "shortest" else max(durations) + filters = [] + for index, source in enumerate(sources): + hold = output_duration - float(source["duration_seconds"]) if kwargs.get("sync") != "shortest" else None + filters.append(_video_filter(source, index, width=width, height=height, fps=fps, duration=hold) + f"[v{index}]") + layout = "|".join(f"{index % columns * (width + gap)}_{index // columns * (height + gap)}" for index in range(len(sources))) + filters.append("".join(f"[v{index}]" for index in range(len(sources))) + f"xstack=inputs={len(sources)}:layout={layout}:fill={kwargs.get('background') or 'black'}[outv]") + asset_id, destination = allocate_video_path(task_id=current_task_id()) + inputs = [part for source in sources for part in ("-i", source["path"])] + run_ffmpeg( + inputs + + [ + "-filter_complex", ";".join(filters), "-map", "[outv]", + "-r", str(fps), "-t", str(output_duration), + "-an", "-c:v", "libx264", "-crf", "18", "-movflags", "+faststart", + ], + destination, + ) + return _derived_asset_result(destination, asset_id, sources, "tile", pin=bool(kwargs.get("pin"))) + + +class ReverseAsset(NodeBase): + """Reverse a retained clip on disk; audio can be remuxed after visual editing.""" + + label = "Reverse Retained Video" + category = "Video" + params = { + "video": {"label": "Video", "display": "input", "type": ["video_asset", "str"]}, + "pin": {"label": "Protect From Cleanup", "type": "bool", "default": False}, + "preview": {"display": "ui_video", "type": "url", "dataSource": "file"}, + "asset": {"label": "Video Asset", "display": "output", "type": "video_asset"}, + "file": {"label": "File", "display": "output", "type": "video"}, + "duration_seconds": {"label": "Duration", "display": "output", "type": "float"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + from modiff.media_assets import allocate_video_path, current_task_id, run_ffmpeg + + source = _file_asset_collection(kwargs.get("video"))[0] + asset_id, destination = allocate_video_path(task_id=current_task_id()) + run_ffmpeg(["-i", source["path"], "-vf", "reverse", "-an", "-c:v", "libx264", "-crf", "18", "-movflags", "+faststart"], destination) + return _derived_asset_result(destination, asset_id, [source], "reverse", pin=bool(kwargs.get("pin"))) + + +class CrossfadeAssets(NodeBase): + """Crossfade two retained clips through the same scalable join implementation.""" + + label = "Crossfade Retained Videos" + category = "Video" + params = { + "first": {"label": "First", "display": "input", "type": ["video_asset", "str"]}, + "second": {"label": "Second", "display": "input", "type": ["video_asset", "str"]}, + "duration_seconds": {"label": "Duration", "type": "float", "default": 0.35, "min": 0, "max": 5}, + "pin": {"label": "Protect From Cleanup", "type": "bool", "default": False}, + "preview": {"display": "ui_video", "type": "url", "dataSource": "file"}, + "asset": {"label": "Video Asset", "display": "output", "type": "video_asset"}, + "file": {"label": "File", "display": "output", "type": "video"}, + "duration_seconds_output": {"label": "Output Duration", "display": "output", "type": "float"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + result = ConcatenateAssets().execute( + clips=[kwargs.get("first"), kwargs.get("second")], + transition_seconds=kwargs.get("duration_seconds"), + pin=kwargs.get("pin"), + ) + result["duration_seconds_output"] = result.pop("duration_seconds") + return result + + +class MuxAudioAsset(NodeBase): + """Attach loaded or generated audio to a retained video without decoding its frames.""" + + label = "Add Audio to Retained Video" + category = "Video" + params = { + "video": {"label": "Video", "display": "input", "type": ["video_asset", "str"]}, + "audio": {"label": "Audio", "display": "input", "type": ["audio", "str"]}, + "fit": {"label": "Duration", "type": "string", "options": ["match_video", "shortest"], "default": "match_video"}, + "pin": {"label": "Protect From Cleanup", "type": "bool", "default": False}, + "preview": {"display": "ui_video", "type": "url", "dataSource": "file"}, + "asset": {"label": "Video Asset", "display": "output", "type": "video_asset"}, + "file": {"label": "File", "display": "output", "type": "video"}, + "duration_seconds": {"label": "Duration", "display": "output", "type": "float"}, + "frames": {"label": "Frames", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + import numpy as np + from scipy.io import wavfile + from modiff.media_assets import allocate_video_path, current_task_id, run_ffmpeg + + source = _file_asset_collection(kwargs.get("video"))[0] + audio = kwargs.get("audio") + temporary_audio = None + if isinstance(audio, str): + audio_path = resolve_runtime_input_path(audio).resolve() + elif isinstance(audio, dict) and (audio.get("path") or audio.get("file")): + audio_path = resolve_runtime_input_path(str(audio.get("path") or audio.get("file"))).resolve() + else: + samples = audio.get("samples", audio.get("audio")) if isinstance(audio, dict) else audio + if samples is None: + raise ValueError("Add Audio to Retained Video needs loaded or generated audio.") + sample_rate = int(audio.get("sample_rate") or 48000) if isinstance(audio, dict) else 48000 + array = np.asarray(samples, dtype=np.float32) + while array.ndim > 2: + array = array[0] + if array.ndim == 2 and array.shape[0] <= 8 and array.shape[1] > array.shape[0]: + array = array.T + temporary_audio = Path(source["path"]).with_name(f".{Path(source['path']).stem}-audio.wav") + wavfile.write(temporary_audio, sample_rate, (np.clip(array, -1, 1) * 32767).astype(np.int16)) + audio_path = temporary_audio + if not audio_path.is_file(): + raise FileNotFoundError(f"Audio file does not exist: {audio_path}") + + asset_id, destination = allocate_video_path(task_id=current_task_id()) + args = [ + "-i", source["path"], "-i", str(audio_path), + "-map", "0:v:0", "-map", "1:a:0", "-c:v", "copy", "-c:a", "aac", "-b:a", "256k", + ] + if kwargs.get("fit") == "shortest": + args.append("-shortest") + else: + args += ["-af", "apad", "-t", str(source["duration_seconds"])] + args += ["-movflags", "+faststart"] + try: + run_ffmpeg(args, destination) + finally: + if temporary_audio is not None: + temporary_audio.unlink(missing_ok=True) + return _derived_asset_result(destination, asset_id, [source], "mux_audio", pin=bool(kwargs.get("pin"))) + + +class CleanupAssets(NodeBase): + """Remove retained temporary media with pinned-asset protection.""" + + label = "Clean Temporary Media" + category = "Video" + params = { + "scope": {"label": "Scope", "type": "string", "options": ["current_run", "older_than", "all_unpinned"], "default": "current_run"}, + "older_than_hours": {"label": "Older Than Hours", "type": "float", "default": 24, "min": 0}, + "report": {"label": "Cleanup Report", "display": "output", "type": "string"}, + "removed_count": {"label": "Removed", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + import json + from modiff.media_assets import cleanup_media_assets + + scope = str(kwargs.get("scope") or "current_run") + task_id = None + older = None + if scope == "current_run": + try: + from modiff.server import server + task_id = (server.current_task or {}).get("task_id") + except Exception: + task_id = None + if not task_id: + raise ValueError("Current-run cleanup is only available while a task identity is active.") + elif scope == "older_than": + older = float(kwargs.get("older_than_hours") or 0) * 3600 + elif scope != "all_unpinned": + raise ValueError(f"Unsupported temporary media cleanup scope {scope!r}.") + report = cleanup_media_assets(task_id=task_id, older_than_seconds=older) + return {"report": json.dumps(report, sort_keys=True), "removed_count": len(report["removed"])} diff --git a/modules/VideoColor/__init__.py b/modules/VideoColor/__init__.py index 15b6a64..216bacc 100644 --- a/modules/VideoColor/__init__.py +++ b/modules/VideoColor/__init__.py @@ -1 +1 @@ -from .main import * +from .main import * # noqa: F403 diff --git a/modules/VideoConditioning/__init__.py b/modules/VideoConditioning/__init__.py index 15b6a64..216bacc 100644 --- a/modules/VideoConditioning/__init__.py +++ b/modules/VideoConditioning/__init__.py @@ -1 +1 @@ -from .main import * +from .main import * # noqa: F403 diff --git a/modules/VideoConditioning/main.py b/modules/VideoConditioning/main.py index 55669d6..8213201 100644 --- a/modules/VideoConditioning/main.py +++ b/modules/VideoConditioning/main.py @@ -96,6 +96,13 @@ class AlignMask(NodeBase): "mask": {"label": "Mask", "display": "input", "type": ["video", "image"]}, "invert": {"label": "Invert mask", "type": "bool", "default": False}, "threshold": {"label": "Threshold", "display": "slider", "type": "int", "min": 0, "max": 255, "default": 127}, + "grow_pixels": { + "label": "Grow generated region", + "type": "int", + "min": 0, + "max": 256, + "default": 0, + }, "output": {"label": "Mask video", "display": "output", "type": "video"}, } @@ -106,6 +113,7 @@ def execute(self, **kwargs): return {"output": []} threshold = int(kwargs.get("threshold", 127)) + grow_pixels = max(0, int(kwargs.get("grow_pixels", 0))) sampled_masks = sample_frames(mask_frames, len(source_frames)) output = [] for source, mask in zip(source_frames, sampled_masks): @@ -114,6 +122,20 @@ def execute(self, **kwargs): mask_image = mask_image.point(lambda value: 255 if value > threshold else 0) if bool(kwargs.get("invert", False)): mask_image = ImageOps.invert(mask_image) + if grow_pixels: + import numpy as np + + try: + import cv2 + + mask_array = np.asarray(mask_image) + kernel_size = grow_pixels * 2 + 1 + kernel = np.ones((kernel_size, kernel_size), dtype=np.uint8) + mask_image = Image.fromarray(cv2.dilate(mask_array, kernel, iterations=1)).convert("L") + except ImportError: # pragma: no cover - full installs include OpenCV + from PIL import ImageFilter + + mask_image = mask_image.filter(ImageFilter.MaxFilter(grow_pixels * 2 + 1)) output.append(mask_image.convert("RGB")) return {"output": output} @@ -140,9 +162,9 @@ def execute(self, **kwargs): class ReferenceImages(NodeBase): - """Pass through image references as a VACE reference-image list.""" + """Package one or more images as a reusable video reference list.""" - label = "Wan VACE References" + label = "Video Reference Images" category = "Video Conditioning" params = { "images": {"label": "Images", "display": "input", "type": "image"}, @@ -154,3 +176,111 @@ def execute(self, **kwargs): if images in (None, ""): return {"references": []} return {"references": images if isinstance(images, list) else [images]} + + +class EdgePreprocessor(NodeBase): + """Create deterministic edge or sketch control frames locally.""" + + label = "Video Edge / Sketch Preprocessor" + category = "Video Conditioning" + resizable = True + params = { + "video": {"label": "Video", "display": "input", "type": ["video", "image"]}, + "algorithm": {"label": "Algorithm", "type": "string", "options": ["canny", "sobel", "sketch"], "default": "canny"}, + "low_threshold": {"label": "Low Threshold", "type": "int", "default": 100, "min": 0, "max": 255}, + "high_threshold": {"label": "High Threshold", "type": "int", "default": 200, "min": 0, "max": 255}, + "blur_radius": {"label": "Blur", "type": "int", "default": 3, "min": 0, "max": 31}, + "invert": {"label": "Invert", "type": "bool", "default": False}, + "output": {"label": "Control Video", "display": "output", "type": "video"}, + } + + def execute(self, **kwargs): + import numpy as np + try: + import cv2 + except ImportError as exc: + raise RuntimeError("Video edge preprocessing needs the gallery-media extra (OpenCV).") from exc + + algorithm = str(kwargs.get("algorithm") or "canny") + blur = max(0, int(kwargs.get("blur_radius") or 0)) + if blur and blur % 2 == 0: + blur += 1 + output = [] + for frame in as_frames(kwargs.get("video")): + rgb = np.asarray(to_image(frame).convert("RGB")) + gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY) + if blur: + gray = cv2.GaussianBlur(gray, (blur, blur), 0) + if algorithm == "canny": + edge = cv2.Canny( + gray, + int(kwargs.get("low_threshold") or 0), + int(kwargs.get("high_threshold") or 0), + ) + elif algorithm == "sobel": + x = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3) + y = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3) + edge = cv2.convertScaleAbs(cv2.magnitude(x, y)) + elif algorithm == "sketch": + inverted = 255 - gray + smooth = cv2.GaussianBlur(inverted, (max(3, blur or 21), max(3, blur or 21)), 0) + edge = cv2.divide(gray, 255 - smooth, scale=256) + else: + raise ValueError(f"Unsupported edge algorithm {algorithm!r}.") + if bool(kwargs.get("invert", False)): + edge = 255 - edge + output.append(Image.fromarray(edge).convert("RGB")) + return {"output": output} + + +class ObjectMaskPropagate(NodeBase): + """Propagate one authored mask with local dense optical flow.""" + + label = "Propagate Object Mask" + category = "Video Conditioning" + resizable = True + params = { + "video": {"label": "Video", "display": "input", "type": "video"}, + "first_mask": {"label": "First-frame Mask", "display": "input", "type": "image"}, + "threshold": {"label": "Mask Threshold", "type": "int", "default": 127, "min": 0, "max": 255}, + "smooth_pixels": {"label": "Smooth", "type": "int", "default": 3, "min": 0, "max": 31}, + "masks": {"label": "Mask Video", "display": "output", "type": "video"}, + "confidence": {"label": "Per-frame Confidence", "display": "output", "type": "collection"}, + } + + def execute(self, **kwargs): + import numpy as np + try: + import cv2 + except ImportError as exc: + raise RuntimeError("Object mask propagation needs the gallery-media extra (OpenCV).") from exc + + frames = [np.asarray(to_image(frame).convert("RGB")) for frame in as_frames(kwargs.get("video"))] + if not frames or kwargs.get("first_mask") is None: + raise ValueError("Propagate Object Mask needs a video and a first-frame mask.") + height, width = frames[0].shape[:2] + threshold = int(kwargs.get("threshold") or 0) + mask = np.asarray(to_image(kwargs.get("first_mask")).convert("L").resize((width, height), Image.Resampling.LANCZOS)) + mask = np.where(mask > threshold, 255, 0).astype(np.uint8) + masks = [Image.fromarray(mask).convert("RGB")] + confidence = [1.0] + previous_gray = cv2.cvtColor(frames[0], cv2.COLOR_RGB2GRAY) + grid_x, grid_y = np.meshgrid(np.arange(width, dtype=np.float32), np.arange(height, dtype=np.float32)) + smooth = max(0, int(kwargs.get("smooth_pixels") or 0)) + if smooth and smooth % 2 == 0: + smooth += 1 + for frame in frames[1:]: + current_gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) + flow = cv2.calcOpticalFlowFarneback(previous_gray, current_gray, None, 0.5, 3, 15, 3, 5, 1.2, 0) + warped = cv2.remap(mask, grid_x - flow[..., 0], grid_y - flow[..., 1], cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT) + if smooth: + warped = cv2.GaussianBlur(warped, (smooth, smooth), 0) + mask = np.where(warped > threshold, 255, 0).astype(np.uint8) + remapped_previous = cv2.remap(previous_gray, grid_x - flow[..., 0], grid_y - flow[..., 1], cv2.INTER_LINEAR) + error = np.abs(remapped_previous.astype(np.float32) - current_gray.astype(np.float32)) + region = mask > 0 + score = 1.0 - float(error[region].mean() / 255.0) if region.any() else 0.0 + confidence.append(max(0.0, min(1.0, score))) + masks.append(Image.fromarray(mask).convert("RGB")) + previous_gray = current_gray + return {"masks": masks, "confidence": confidence} diff --git a/modules/WanVACE/__init__.py b/modules/WanVACE/__init__.py deleted file mode 100644 index 15b6a64..0000000 --- a/modules/WanVACE/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .main import * diff --git a/modules/WorkflowControl/__init__.py b/modules/WorkflowControl/__init__.py new file mode 100644 index 0000000..50dc81f --- /dev/null +++ b/modules/WorkflowControl/__init__.py @@ -0,0 +1,2 @@ +"""General graph-control primitives.""" + diff --git a/modules/WorkflowControl/main.py b/modules/WorkflowControl/main.py new file mode 100644 index 0000000..c25200c --- /dev/null +++ b/modules/WorkflowControl/main.py @@ -0,0 +1,419 @@ +"""Explicit boundary and collection nodes used by visual loop containers.""" + +import itertools +import json +from typing import Any + +from modiff.NodeBase import NodeBase + + +def parse_shot_list(value: Any, *, maximum: int = 24) -> dict[str, Any]: + """Normalize authored or locally generated shot JSON into a loop-ready list.""" + + if isinstance(value, str): + source = value.strip() + if source.startswith("```"): + lines = source.splitlines() + source = "\n".join(lines[1:-1] if len(lines) > 2 else lines).strip() + try: + value = json.loads(source) + except json.JSONDecodeError: + starts = [index for index in (source.find("["), source.find("{")) if index >= 0] + start = min(starts) if starts else -1 + end = max(source.rfind("]"), source.rfind("}")) + if start < 0 or end <= start: + raise ValueError("Shot list output must contain a JSON array or an object with a shots array.") + try: + value = json.loads(source[start : end + 1]) + except json.JSONDecodeError as exc: + raise ValueError(f"Shot list contains invalid JSON: {exc.msg}.") from exc + if isinstance(value, dict): + value = value.get("shots") + if not isinstance(value, list) or not value: + raise ValueError("Shot list must contain at least one shot object.") + limit = max(1, min(100, int(maximum))) + if len(value) > limit: + raise ValueError(f"Shot list contains {len(value)} shots, above the configured maximum of {limit}.") + + shots = [] + for index, raw in enumerate(value): + if not isinstance(raw, dict): + raise ValueError(f"Shot {index + 1} must be a JSON object.") + prompt = str(raw.get("prompt") or "").strip() + if not prompt: + raise ValueError(f"Shot {index + 1} needs a non-empty prompt.") + try: + duration = float(raw.get("duration_seconds", raw.get("duration", 0))) + except (TypeError, ValueError) as exc: + raise ValueError(f"Shot {index + 1} duration must be a number of seconds.") from exc + if duration <= 0 or duration > 120: + raise ValueError(f"Shot {index + 1} duration must be greater than 0 and at most 120 seconds.") + shot = { + "index": index, + "title": str(raw.get("title") or f"Shot {index + 1}").strip(), + "prompt": prompt, + "duration_seconds": duration, + "transition": str(raw.get("transition") or ("cut" if index else "start")).strip(), + } + for name in ("audio_prompt", "reference_strategy", "notes"): + if raw.get(name) not in (None, ""): + shot[name] = str(raw[name]).strip() + shots.append(shot) + total = sum(shot["duration_seconds"] for shot in shots) + return {"shots": shots, "shot_list_json": json.dumps(shots, ensure_ascii=False), "total_duration_seconds": total} + + +class AuthorShotList(NodeBase): + """Validate an authored shot list and expose records suitable for a loop.""" + + label = "Author Shot List" + category = "Workflow Control" + resizable = True + params = { + "shots_json": { + "label": "Shots (JSON)", + "display": "textarea", + "type": "text", + "default": '[{"title":"Opening","prompt":"Describe the opening action and camera movement.","duration_seconds":5}]', + "description": "Each shot needs prompt and duration_seconds. Optional: title, transition, audio_prompt, reference_strategy, notes.", + }, + "maximum_shots": {"label": "Maximum Shots", "type": "int", "default": 12, "min": 1, "max": 100}, + "shots": {"label": "Shots", "display": "output", "type": "collection"}, + "shot_list_json": {"label": "Shot List JSON", "display": "output", "type": "text"}, + "total_duration_seconds": {"label": "Approximate Duration", "display": "output", "type": "float"}, + } + + def execute(self, **kwargs): + return parse_shot_list(kwargs.get("shots_json"), maximum=int(kwargs.get("maximum_shots") or 12)) + + +class LoopInput(NodeBase): + """Expose the initial value, then the carried value on later iterations.""" + + label = "Loop Input" + category = "Workflow Control" + params = { + "initial": {"label": "Initial Value", "display": "input", "type": "any"}, + "value": {"label": "Current Value", "display": "output", "type": "any"}, + } + + def execute(self, **kwargs): + return {"value": kwargs.get("initial")} + + +class LoopIndex(NodeBase): + """Expose zero- and one-based iteration values injected by the executor.""" + + label = "Loop Index" + category = "Workflow Control" + params = { + "index_value": {"label": "Index", "type": "int", "default": 0, "hidden": True}, + "iteration_count": {"label": "Iterations", "type": "int", "default": 1, "hidden": True}, + "index": {"label": "Index (0-based)", "display": "output", "type": "int"}, + "iteration": {"label": "Iteration (1-based)", "display": "output", "type": "int"}, + "is_first": {"label": "Is First", "display": "output", "type": "bool"}, + "is_last": {"label": "Is Last", "display": "output", "type": "bool"}, + } + + def execute(self, **kwargs): + index = max(0, int(kwargs.get("index_value") or 0)) + count = max(1, int(kwargs.get("iteration_count") or 1)) + return { + "index": index, + "iteration": index + 1, + "is_first": index == 0, + "is_last": index == count - 1, + } + + +class LoopItems(NodeBase): + """Expose one item from a collection for collection-driven loops.""" + + label = "Loop Items" + category = "Workflow Control" + params = { + "collection": {"label": "Collection", "display": "input", "type": "any"}, + "item_index": {"label": "Item Index", "type": "int", "default": 0, "hidden": True}, + "item": {"label": "Item", "display": "output", "type": "any"}, + "index": {"label": "Index", "display": "output", "type": "int"}, + "count": {"label": "Count", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + collection = kwargs.get("collection") + if isinstance(collection, dict): + values = [{"key": key, "value": value} for key, value in collection.items()] + elif isinstance(collection, (list, tuple)): + values = list(collection) + else: + raise TypeError("Loop Items needs a list, tuple, or object collection.") + index = int(kwargs.get("item_index") or 0) + if index < 0 or index >= len(values): + raise IndexError(f"Loop item index {index} is outside a collection of {len(values)} item(s).") + return {"item": values[index], "index": index, "count": len(values)} + + +class LoopResult(NodeBase): + """Mark the value to carry/collect and optionally stop the loop early.""" + + label = "Loop Result" + category = "Workflow Control" + params = { + "value_input": {"label": "Value", "display": "input", "type": "any"}, + "stop_input": {"label": "Stop", "display": "input", "type": "bool", "default": False}, + "value": {"label": "Last Value", "display": "output", "type": "any"}, + "collection": {"label": "Collected Values", "display": "output", "type": "any"}, + "stopped": {"label": "Stopped Early", "display": "output", "type": "bool"}, + } + + def execute(self, **kwargs): + value: Any = kwargs.get("value_input") + stopped = bool(kwargs.get("stop_input", False)) + return {"value": value, "collection": [value], "stopped": stopped} + + +class SeedSequence(NodeBase): + label = "Seed Sequence" + category = "Workflow Control" + params = { + "start": {"label": "Start Seed", "type": "int", "default": 0}, + "count": {"label": "Count", "type": "int", "default": 4, "min": 1, "max": 10000}, + "step": {"label": "Step", "type": "int", "default": 1}, + "seeds": {"label": "Seeds", "display": "output", "type": "any"}, + } + + def execute(self, **kwargs): + start = int(kwargs.get("start") or 0) + count = max(1, min(10000, int(kwargs.get("count") or 1))) + step = int(kwargs.get("step") or 1) + return {"seeds": [start + index * step for index in range(count)]} + + +class ParameterMatrix(NodeBase): + label = "Parameter Matrix" + category = "Workflow Control" + resizable = True + params = { + "parameters": { + "label": "Parameters (JSON)", + "display": "textarea", + "type": "text", + "default": "{}", + "description": 'Example: {"steps": [20, 30], "guidance": [3.5, 5.0]}', + }, + "max_combinations": {"label": "Maximum", "type": "int", "default": 256, "min": 1, "max": 10000}, + "mode": {"label": "Mode", "type": "string", "options": ["cartesian", "zip"], "default": "cartesian"}, + "combinations": {"label": "Combinations", "display": "output", "type": "any"}, + "count": {"label": "Count", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + raw = kwargs.get("parameters") or "{}" + try: + parameters = json.loads(raw) if isinstance(raw, str) else raw + except json.JSONDecodeError as exc: + raise ValueError(f"Parameter Matrix needs valid JSON: {exc.msg}.") from exc + if not isinstance(parameters, dict) or not parameters: + raise ValueError("Parameter Matrix needs a non-empty JSON object.") + keys = list(parameters) + values = [] + total = 1 + for key in keys: + choices = parameters[key] + if not isinstance(choices, list) or not choices: + raise ValueError(f"Parameter {key!r} must contain a non-empty JSON array.") + values.append(choices) + total *= len(choices) + mode = str(kwargs.get("mode") or "cartesian") + if mode == "cartesian": + combinations = [dict(zip(keys, combination)) for combination in itertools.product(*values)] + elif mode == "zip": + lengths = {len(choices) for choices in values if len(choices) != 1} + if len(lengths) > 1: + raise ValueError("Zip mode needs arrays of the same length; one-item arrays may be broadcast.") + count = max(map(len, values)) + combinations = [ + {key: choices[0] if len(choices) == 1 else choices[index] for key, choices in zip(keys, values)} + for index in range(count) + ] + else: + raise ValueError(f"Unsupported Parameter Matrix mode {mode!r}.") + maximum = max(1, min(10000, int(kwargs.get("max_combinations") or 256))) + if len(combinations) > maximum: + raise ValueError( + f"Parameter Matrix would create {len(combinations)} combinations, above the configured maximum of {maximum}." + ) + return {"combinations": combinations, "count": len(combinations)} + + +class FanOut(NodeBase): + """Create explicit ordered branches from one value and optional overrides.""" + + label = "Fan Out" + category = "Workflow Control" + params = { + "value": {"label": "Value", "display": "input", "type": "any"}, + "count": {"label": "Branches", "type": "int", "default": 2, "min": 1, "max": 10000}, + "branch_overrides": { + "label": "Branch Overrides (JSON)", + "display": "textarea", + "type": "text", + "default": "[]", + "description": "Optional array of objects, one per branch.", + }, + "branches": {"label": "Branches", "display": "output", "type": "collection"}, + } + + def execute(self, **kwargs): + count = max(1, min(10000, int(kwargs.get("count") or 1))) + raw = kwargs.get("branch_overrides") or "[]" + try: + overrides = json.loads(raw) if isinstance(raw, str) else raw + except json.JSONDecodeError as exc: + raise ValueError(f"Fan Out needs valid branch override JSON: {exc.msg}.") from exc + if not isinstance(overrides, list) or any(not isinstance(item, dict) for item in overrides): + raise ValueError("Fan Out branch overrides must be a JSON array of objects.") + if len(overrides) > count: + raise ValueError("Fan Out has more override objects than configured branches.") + return { + "branches": [ + { + "index": index, + "value": kwargs.get("value"), + "overrides": dict(overrides[index]) if index < len(overrides) else {}, + } + for index in range(count) + ] + } + + +class CollectionBatch(NodeBase): + label = "Batch Collection" + category = "Workflow Control" + params = { + "collection": {"label": "Collection", "display": "input", "type": "any"}, + "batch_size": {"label": "Batch Size", "type": "int", "default": 4, "min": 1, "max": 10000}, + "batches": {"label": "Batches", "display": "output", "type": "any"}, + } + + def execute(self, **kwargs): + collection = kwargs.get("collection") + if not isinstance(collection, (list, tuple)): + raise TypeError("Batch Collection needs a list or tuple.") + size = max(1, min(10000, int(kwargs.get("batch_size") or 1))) + values = list(collection) + return {"batches": [values[index : index + size] for index in range(0, len(values), size)]} + + +class CollectionFlatten(NodeBase): + label = "Flatten Collection" + category = "Workflow Control" + params = { + "collection": {"label": "Collection", "display": "input", "type": "any"}, + "flattened": {"label": "Flattened", "display": "output", "type": "any"}, + } + + def execute(self, **kwargs): + collection = kwargs.get("collection") + if not isinstance(collection, (list, tuple)): + raise TypeError("Flatten Collection needs a list or tuple.") + flattened = [] + for item in collection: + flattened.extend(item if isinstance(item, (list, tuple)) else [item]) + return {"flattened": flattened} + + +class CollectionItem(NodeBase): + """Select one ordered collection value with an explicit fallback policy.""" + + label = "Get Collection Item" + category = "Workflow Control" + params = { + "collection": {"label": "Collection", "display": "input", "type": "any"}, + "index": {"label": "Index", "display": "input", "type": "int", "default": 0}, + "out_of_range": { + "label": "Out of Range", + "type": "string", + "options": ["error", "use_first", "use_last"], + "default": "error", + }, + "item": {"label": "Item", "display": "output", "type": "any"}, + "count": {"label": "Count", "display": "output", "type": "int"}, + } + + def execute(self, **kwargs): + collection = kwargs.get("collection") + if not isinstance(collection, (list, tuple)): + raise TypeError("Get Collection Item needs a list or tuple.") + values = list(collection) + if not values: + raise ValueError("Get Collection Item needs a non-empty collection.") + index = int(kwargs.get("index") or 0) + if not 0 <= index < len(values): + policy = str(kwargs.get("out_of_range") or "error") + if policy == "use_first": + index = 0 + elif policy == "use_last": + index = len(values) - 1 + elif policy == "error": + raise IndexError(f"Collection index {index} is outside a collection of {len(values)} item(s).") + else: + raise ValueError(f"Unsupported collection fallback policy {policy!r}.") + return {"item": values[index], "count": len(values)} + + +class GetField(NodeBase): + """Read one dotted field from a loop item, job record, or preset.""" + + label = "Get Record Field" + category = "Workflow Control" + params = { + "record": {"label": "Record", "display": "input", "type": "any"}, + "field": {"label": "Field", "type": "string", "default": "value"}, + "default_value": {"label": "Default", "display": "input", "type": "any"}, + "value": {"label": "Value", "display": "output", "type": "any"}, + "found": {"label": "Found", "display": "output", "type": "bool"}, + } + + def execute(self, **kwargs): + value = kwargs.get("record") + found = True + for part in [item for item in str(kwargs.get("field") or "").split(".") if item]: + if isinstance(value, dict) and part in value: + value = value[part] + elif isinstance(value, (list, tuple)) and part.isdigit() and int(part) < len(value): + value = value[int(part)] + else: + found = False + value = kwargs.get("default_value") + break + return {"value": value, "found": found} + + +class ParameterPreset(NodeBase): + """Create a named, serializable set of ordinary workflow parameters.""" + + label = "Parameter Preset" + category = "Workflow Control" + resizable = True + params = { + "name": {"label": "Name", "type": "string", "default": "Preset"}, + "values": {"label": "Values (JSON)", "display": "textarea", "type": "text", "default": "{}"}, + "preset": {"label": "Preset", "display": "output", "type": "any"}, + "manifest": {"label": "Manifest", "display": "output", "type": "string"}, + } + + def execute(self, **kwargs): + raw = kwargs.get("values") or "{}" + try: + values = json.loads(raw) if isinstance(raw, str) else raw + except json.JSONDecodeError as exc: + raise ValueError(f"Parameter Preset needs valid JSON: {exc.msg}.") from exc + if not isinstance(values, dict): + raise ValueError("Parameter Preset values must be a JSON object.") + preset = { + "schema_version": 1, + "name": str(kwargs.get("name") or "Preset"), + "values": values, + } + return {"preset": preset, "manifest": json.dumps(preset, sort_keys=True)} diff --git a/modules/__init__.py b/modules/__init__.py index 59d650f..ee71337 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging import ast from os import scandir, path @@ -8,9 +9,9 @@ logger.info("Loading modules...") # preload common functions -from utils.huggingface import local_files_only, get_local_model_ids -from utils.torch_utils import str_to_dtype, DEVICE_LIST, DEFAULT_DEVICE, CPU_DEVICE, IS_CUDA -from modiff.modelstore import modelstore +from utils.huggingface import local_files_only, get_local_model_ids # noqa: F401 - AST registry preloads +from utils.torch_utils import str_to_dtype, DEVICE_LIST, DEFAULT_DEVICE, CPU_DEVICE, IS_CUDA # noqa: F401 - AST registry preloads +from modiff.modelstore import modelstore # noqa: F401 - AST registry preload MODULE_MAP = {} @@ -48,19 +49,6 @@ def safe_eval_ast_node_recursive(node: ast.AST, module_obj: ModuleType) -> Any: # If it's a tuple, recursively evaluate its elements elif isinstance(node, ast.Tuple): return tuple(safe_eval_ast_node_recursive(item, module_obj) for item in node.elts) - # If it's a list comprehension, evaluate it - elif isinstance(node, ast.ListComp): - try: - expr = ast.Expression(body=node) - ast.fix_missing_locations(expr) - - # Compile the expression and then evaluate it in the context of the module - code = compile(expr, filename="", mode="eval") - return eval(code, module_obj.__dict__) - except Exception as e: - logger.error(f"Error evaluating list comprehension in module '{module_obj.__name__}': {e}", exc_info=True) - return ast.unparse(node) # Fallback to string on error - # If it's a name (e.g., a variable or function name), try to resolve it. elif isinstance(node, ast.Name): try: diff --git a/pyproject.toml b/pyproject.toml index cb8bb52..46868be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,15 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. [project] name = "modiff" version = "0.1.0" description = "MoDiff is a modular Diffusers client/server application." readme = "README.md" license = "Apache-2.0" -requires-python = ">=3.12" +requires-python = ">=3.12,<3.13" dependencies = [ "accelerate>=1.4.0", "aiohttp>=3.11.12", "aiohttp-cors>=0.7.0", - "bitsandbytes>=0.46.1; sys_platform == 'linux' or sys_platform == 'win32'", "kornia>=0.8.1", "nanoid>=2.0.0", "peft>=0.17.0", @@ -20,11 +20,13 @@ dependencies = [ "torchsde>=0.2.6", "torchvision>=0.21.0", "transformers>=4.49.0", - "diffusers>=0.36.0.dev0", - "aiofiles>=25.1.0", + "diffusers @ git+https://github.com/huggingface/diffusers.git@13a7bee4878d62fccc8d25f97e480e68de96fa03", "ftfy>=6.3.1", + "huggingface-hub>=1.23.0,<2.0", "imageio>=2.37.2", "imageio-ffmpeg>=0.6.0", + "spandrel>=0.4.2", + "yt-dlp>=2026.7.4", ] [project.optional-dependencies] @@ -33,22 +35,24 @@ apple-silicon = [ "torchvision>=0.21.0; sys_platform == 'darwin'", ] cuda = [ - "bitsandbytes>=0.46.1; sys_platform == 'linux' or sys_platform == 'win32'", - "xformers; sys_platform == 'linux' or sys_platform == 'win32'", + "bitsandbytes==0.50.0; sys_platform == 'linux' or sys_platform == 'win32'", + # xFormers wheels are Torch-ABI specific. 0.0.32.post2 is the upstream + # release built for the managed NVIDIA profile's Torch 2.8 runtime; allowing + # an unbounded latest release currently resolves to a Torch 2.10+ build. + "xformers==0.0.32.post2; sys_platform == 'linux' or sys_platform == 'win32'", ] nunchaku = [ "nunchaku>=1.0.1.dev20250924; sys_platform == 'linux' or sys_platform == 'win32'" ] spandrel = ["spandrel"] -background-removal = ["transparent-background"] +gallery-media = ["opencv-python-headless>=4.11.0"] quantization = [ "dfloat11[cuda12]; sys_platform == 'linux' or sys_platform == 'win32'", "gguf", - "kernels", - "optimum-quanto", - "torchao", + "kernels==0.16.0", + "optimum-quanto==0.2.7", + "torchao==0.17.0", ] -non-commercial = ["rembg[gpu]"] [tool.ruff] @@ -64,6 +68,19 @@ quote-style = "double" # Like Black, indent with spaces, rather than tabs. indent-style = "space" +[tool.setuptools.packages.find] +# MoDiff intentionally keeps both the server package and its built-in node +# modules at the repository root. Explicit discovery prevents editable +# installs from treating runtime data/web/custom directories as packages. +include = ["modiff*", "modules*"] +exclude = ["custom*", "data*", "tests*", "web*"] + +[tool.uv] +# The accelerator-specific installer owns MoDiff's executable environment. +# This prevents `uv run` / `uv sync` from replacing a validated CUDA, ROCm, +# MPS, or CPU profile with the generic development resolution. +managed = false + [[tool.uv.index]] name = "pytorch-cu128" url = "https://download.pytorch.org/whl/cu128" @@ -80,4 +97,3 @@ nunchaku = [ { url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1dev20250924/nunchaku-1.0.1.dev20250924+torch2.8-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux'" }, { url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1dev20250924/nunchaku-1.0.1.dev20250924+torch2.8-cp312-cp312-win_amd64.whl", marker = "sys_platform == 'win32'" }, ] -diffusers = { git = "https://github.com/huggingface/diffusers" } diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 9cebaa8..0000000 --- a/requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -aiohttp -aiohttp_cors -aiofiles -nanoid -git+https://github.com/huggingface/diffusers -transformers -accelerate -safetensors -protobuf -sentencepiece -einops -xformers -kornia -av -imageio diff --git a/requirements/profiles/cpu.txt b/requirements/profiles/cpu.txt index 73b0526..06bb23f 100644 --- a/requirements/profiles/cpu.txt +++ b/requirements/profiles/cpu.txt @@ -1,4 +1,7 @@ -torch==2.8.0 --index-url https://download.pytorch.org/whl/cpu -torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cpu -torchaudio==2.8.0 --index-url https://download.pytorch.org/whl/cpu +# Requirements-file index options are global and must stay on standalone lines. +--extra-index-url https://download.pytorch.org/whl/cpu + +torch==2.8.0 +torchvision==0.23.0 +torchaudio==2.8.0 -e . diff --git a/requirements/profiles/intel-xpu.txt b/requirements/profiles/intel-xpu.txt new file mode 100644 index 0000000..b6c6a9a --- /dev/null +++ b/requirements/profiles/intel-xpu.txt @@ -0,0 +1,6 @@ +# Requirements-file index options are global and must stay on standalone lines. +--extra-index-url https://download.pytorch.org/whl/xpu + +torch==2.12.1 +torchvision==0.27.1 +-e . diff --git a/requirements/profiles/nvidia-cuda.txt b/requirements/profiles/nvidia-cuda.txt index 6c48101..7bdf0b3 100644 --- a/requirements/profiles/nvidia-cuda.txt +++ b/requirements/profiles/nvidia-cuda.txt @@ -1,4 +1,7 @@ -torch==2.8.0 --index-url https://download.pytorch.org/whl/cu128 -torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cu128 -torchaudio==2.8.0 --index-url https://download.pytorch.org/whl/cu128 +# Requirements-file index options are global and must stay on standalone lines. +--extra-index-url https://download.pytorch.org/whl/cu128 + +torch==2.8.0 +torchvision==0.23.0 +torchaudio==2.8.0 -e .[cuda] diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 0000000..d2d717a --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,2 @@ +pytest==9.0.3 +opencv-python-headless>=4.11.0 diff --git a/requirements_extras.txt b/requirements_extras.txt deleted file mode 100644 index c3bd7a7..0000000 --- a/requirements_extras.txt +++ /dev/null @@ -1,3 +0,0 @@ -spandrel -transparent-background -rembg[gpu] diff --git a/requirements_macos.txt b/requirements_macos.txt deleted file mode 100644 index 3c9e4a9..0000000 --- a/requirements_macos.txt +++ /dev/null @@ -1,21 +0,0 @@ -accelerate -aiofiles -aiohttp -aiohttp_cors -nanoid -git+https://github.com/huggingface/diffusers -huggingface-hub -transformers -safetensors -protobuf -sentencepiece -einops -kornia -imageio -imageio-ffmpeg -peft -scipy -torch -torchsde -torchvision -ftfy diff --git a/requirements_quant.txt b/requirements_quant.txt deleted file mode 100644 index 4c93cd9..0000000 --- a/requirements_quant.txt +++ /dev/null @@ -1,6 +0,0 @@ -bitsandbytes -dfloat11[cuda12] -gguf -kernels -optimum-quanto -torchao \ No newline at end of file diff --git a/run.ps1 b/run.ps1 index 7f3a3a2..939aae6 100644 --- a/run.ps1 +++ b/run.ps1 @@ -10,7 +10,8 @@ if ($profile.profile -eq "amd-rocm-linux") { if (!$env:HIP_PATH) { $env:HIP_PATH = "/opt/rocm" } $env:LD_LIBRARY_PATH = "/opt/rocm/lib" + $(if ($env:LD_LIBRARY_PATH) { ":$env:LD_LIBRARY_PATH" } else { "" }) } -& $python -c 'from modiff.hardware import get_hardware_snapshot; from modiff.runtime_profile import runtime_profile; import sys; sys.exit(0 if runtime_profile(get_hardware_snapshot(refresh=True))["execution_ready"] else 2)' +$preflightCode = "from modiff.hardware import get_hardware_snapshot; from modiff.runtime_profile import runtime_profile; import sys; sys.exit(0 if runtime_profile(get_hardware_snapshot(refresh=True))['execution_ready'] else 2)" +& $python -c $preflightCode if ($LASTEXITCODE -ne 0) { throw "Managed runtime profile is not execution-ready. Run .\install.ps1 -Accelerator auto -Repair -SystemCheck -Json." } & $python main.py @args exit $LASTEXITCODE diff --git a/run.sh b/run.sh old mode 100755 new mode 100644 index 521c3ac..e462cdf --- a/run.sh +++ b/run.sh @@ -1,20 +1,17 @@ #!/usr/bin/env bash +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. set -euo pipefail cd -- "$(dirname -- "${BASH_SOURCE[0]}")" -export PYTORCH_CUDA_ALLOC_CONF="${PYTORCH_CUDA_ALLOC_CONF:-expandable_segments:True}" - if [[ -x ./.venv/bin/python ]]; then - exec ./.venv/bin/python main.py "$@" -fi - -if command -v uv >/dev/null 2>&1; then - exec uv run main.py "$@" + ./scripts/with-runtime-env.sh ./.venv/bin/python -m modiff.preflight --fail-on-error + exec ./scripts/with-runtime-env.sh ./.venv/bin/python main.py "$@" fi -if command -v python3 >/dev/null 2>&1; then - exec python3 main.py "$@" +if [[ -f ./.modiff/install-state.json ]]; then + echo "The managed MoDiff environment is missing or unusable. Run ./install.sh --repair before starting." >&2 +else + echo "No managed MoDiff environment was found. Run ./install.sh before starting." >&2 fi - -exec python main.py "$@" +exit 2 diff --git a/scripts/lock_accelerator_wheels.py b/scripts/lock_accelerator_wheels.py index 1a30c94..9e079f4 100644 --- a/scripts/lock_accelerator_wheels.py +++ b/scripts/lock_accelerator_wheels.py @@ -4,36 +4,76 @@ import argparse import hashlib import json -import tempfile -import urllib.parse import urllib.request from pathlib import Path from modiff.runtime_profile import MANIFEST_PATH +def parse_direct_wheel_requirements(path: Path) -> tuple[list[str], str]: + """Return direct HTTPS wheel URLs and the editable project requirement. + + This maintenance command is intentionally limited to profiles made from + direct wheel URLs. Rejecting index-based and ordinary pinned profiles + prevents an accidental invocation from replacing their requirements with + only the editable project line. + """ + + urls: list[str] = [] + editable_requirements: list[str] = [] + unsupported: list[str] = [] + for raw_line in path.read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + first_token = line.split(maxsplit=1)[0] + if first_token.startswith("https://"): + urls.append(first_token) + elif line.startswith("-e "): + editable_requirements.append(line) + else: + unsupported.append(line) + + if unsupported: + raise ValueError( + "profile is not direct-wheel-only; unsupported requirement lines: " + + ", ".join(unsupported) + ) + if not urls: + raise ValueError("profile contains no direct HTTPS wheel URLs") + if len(editable_requirements) > 1: + raise ValueError("profile contains more than one editable project requirement") + return urls, editable_requirements[0] if editable_requirements else "-e ." + + def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--profile", default="amd-rocm-linux") parser.add_argument("--output", type=Path) args = parser.parse_args() manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) - profile = manifest["profiles"][args.profile] + profiles = manifest.get("profiles") + if not isinstance(profiles, dict) or args.profile not in profiles: + parser.error(f"unknown accelerator profile: {args.profile}") + profile = profiles[args.profile] requirement = Path(profile["requirements"]) - output = args.output or Path(__file__).resolve().parents[1] / requirement - urls = [line.strip().split()[0] for line in output.read_text(encoding="utf-8").splitlines() if line.strip().startswith("https://")] + repository_root = Path(__file__).resolve().parents[1] + if args.output is None and (requirement.is_absolute() or ".." in requirement.parts): + parser.error(f"profile requirements path must stay inside the repository: {requirement}") + output = args.output or repository_root / requirement + try: + urls, editable_requirement = parse_direct_wheel_requirements(output) + except (OSError, ValueError) as exc: + parser.error(str(exc)) + lines = [] - with tempfile.TemporaryDirectory(prefix="modiff-wheel-lock-") as temporary: - for url in urls: - name = urllib.parse.unquote(url.rsplit("/", 1)[-1]) - target = Path(temporary) / name - digest = hashlib.sha256() - with urllib.request.urlopen(url) as response, target.open("wb") as handle: - while chunk := response.read(8 * 1024 * 1024): - handle.write(chunk) - digest.update(chunk) - lines.append(f"{url} --hash=sha256:{digest.hexdigest()}") - lines.append("-e .") + for url in urls: + digest = hashlib.sha256() + with urllib.request.urlopen(url) as response: + while chunk := response.read(8 * 1024 * 1024): + digest.update(chunk) + lines.append(f"{url} --hash=sha256:{digest.hexdigest()}") + lines.append(editable_requirement) output.write_text("\n".join(lines) + "\n", encoding="utf-8") return 0 diff --git a/scripts/with-runtime-env.sh b/scripts/with-runtime-env.sh new file mode 100644 index 0000000..4dfc27e --- /dev/null +++ b/scripts/with-runtime-env.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Apply the installed MoDiff runtime profile environment, then run one command. +set -euo pipefail + +if (( $# == 0 )); then + echo "Usage: ./scripts/with-runtime-env.sh [args...]" >&2 + exit 2 +fi + +PROJECT_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +MANAGED_PYTHON="$PROJECT_ROOT/.venv/bin/python" +INSTALL_STATE="$PROJECT_ROOT/.modiff/install-state.json" +RUNTIME_PROFILE="${MODIFF_RUNTIME_PROFILE:-}" + +if [[ -z "$RUNTIME_PROFILE" && -x "$MANAGED_PYTHON" && -r "$INSTALL_STATE" ]]; then + RUNTIME_PROFILE="$("$MANAGED_PYTHON" - "$INSTALL_STATE" <<'PY' +import json +import sys + +try: + with open(sys.argv[1], encoding="utf-8") as handle: + value = json.load(handle) + print(value.get("profile") or "") +except (OSError, UnicodeError, ValueError, TypeError): + print("") +PY +)" +fi + +export PYTORCH_CUDA_ALLOC_CONF="${PYTORCH_CUDA_ALLOC_CONF:-expandable_segments:True}" + +if [[ "$RUNTIME_PROFILE" == "amd-rocm-linux" ]]; then + ROCM_LIBRARY_PATHS=() + [[ -d /opt/rocm/lib ]] && ROCM_LIBRARY_PATHS+=(/opt/rocm/lib) + for directory in /opt/rocm/core-*/lib; do + [[ -d "$directory" ]] && ROCM_LIBRARY_PATHS+=("$directory") + done + if (( ${#ROCM_LIBRARY_PATHS[@]} > 0 )); then + ROCM_JOINED="$(IFS=:; echo "${ROCM_LIBRARY_PATHS[*]}")" + export LD_LIBRARY_PATH="$ROCM_JOINED${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + export ROCM_PATH="${ROCM_PATH:-/opt/rocm}" + export HIP_PATH="${HIP_PATH:-/opt/rocm}" + export TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL="${TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL:-1}" + fi +fi + +exec "$@" diff --git a/tests/test_accelerator_manifest.py b/tests/test_accelerator_manifest.py index 2125d8b..a77ffa8 100644 --- a/tests/test_accelerator_manifest.py +++ b/tests/test_accelerator_manifest.py @@ -6,13 +6,16 @@ class AcceleratorManifestTests(unittest.TestCase): def test_all_public_profiles_are_release_pinned(self): manifest = load_manifest() - self.assertEqual(set(manifest["profiles"]), {"nvidia-cuda", "amd-rocm-linux", "amd-pytorch-windows", "apple-mps", "cpu"}) + self.assertEqual( + set(manifest["profiles"]), + {"nvidia-cuda", "amd-rocm-linux", "amd-pytorch-windows", "intel-xpu", "apple-mps", "cpu"}, + ) for name, profile in manifest["profiles"].items(): with self.subTest(name=name): self.assertRegex(profile["torch"], r"^\d+\.\d+\.\d+(?:\+rocm\d+\.\d+(?:\.\d+)?)?$") self.assertTrue(profile["requirements"]) self.assertTrue(profile["sources"]) - self.assertIn(profile["tier"], {"supported", "preview", "conditional"}) + self.assertIn(profile["tier"], {"supported", "preview", "conditional", "unsupported"}) if name == "amd-rocm-linux": self.assertEqual(set(profile["wheel_hashes"]), {"torch", "torchvision", "torchaudio", "triton"}) self.assertTrue(all(len(value) == 64 for value in profile["wheel_hashes"].values())) diff --git a/tests/test_accelerator_requirements.py b/tests/test_accelerator_requirements.py new file mode 100644 index 0000000..a723c66 --- /dev/null +++ b/tests/test_accelerator_requirements.py @@ -0,0 +1,35 @@ +import re +import unittest +from pathlib import Path + +from modiff.runtime_profile import load_manifest + + +ROOT = Path(__file__).parents[1] +INDEX_OPTION = re.compile(r"(?:^|\s)--(?:extra-)?index-url(?:\s|=)") + + +class AcceleratorRequirementTests(unittest.TestCase): + def test_package_index_options_are_standalone_and_match_the_manifest(self): + manifest = load_manifest() + indexed_profiles = {"nvidia-cuda", "intel-xpu", "cpu"} + + for name, profile in manifest["profiles"].items(): + with self.subTest(profile=name): + requirements = (ROOT / profile["requirements"]).read_text(encoding="utf-8").splitlines() + active_lines = [line.strip() for line in requirements if line.strip() and not line.lstrip().startswith("#")] + + for line in active_lines: + if INDEX_OPTION.search(line): + self.assertRegex( + line, + r"^--(?:extra-)?index-url(?:\s|=)", + "Package index options must be standalone requirements-file options", + ) + + if name in indexed_profiles: + self.assertIn(f"--extra-index-url {profile['index']}", active_lines) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_app_managed_auxiliary_models.py b/tests/test_app_managed_auxiliary_models.py new file mode 100644 index 0000000..7fb7f68 --- /dev/null +++ b/tests/test_app_managed_auxiliary_models.py @@ -0,0 +1,122 @@ +import unittest +from unittest.mock import patch + +import torch +from diffusers import FlowMatchEulerDiscreteScheduler + +from modules.ModularDiffusers.adapters import Lora +from modules.ModularDiffusers.loaders import apply_lora_scheduler_override +from modules.Spandrel import MODULE_MAP as SPANDREL_MODULE_MAP +from modules.Spandrel.main import Upscaler +from utils.huggingface import local_files_only + + +class AppManagedAuxiliaryModelTests(unittest.TestCase): + def test_upscaler_socket_contract_accepts_stills_and_video_frame_batches(self): + params = SPANDREL_MODULE_MAP["Upscaler"]["params"] + self.assertEqual(params["image"]["type"], ["image", "video"]) + self.assertTrue(params["image"]["required"]) + self.assertEqual(params["output"]["type"], ["image", "video"]) + + def test_execution_loaders_are_always_cache_only(self): + self.assertTrue(local_files_only("example/model")) + + def test_modular_lora_rejects_an_empty_selection(self): + with self.assertRaisesRegex(ValueError, "LoRA model is required"): + Lora("empty-lora").execute({"source": "hub", "value": ""}, 1.0) + + def test_modular_lora_resolves_a_hub_weight_only_from_app_cache(self): + with patch( + "utils.huggingface.cached_file_path", + return_value="/cache/revision/style.safetensors", + ): + result = Lora("cached-lora").execute( + {"source": "hub", "value": "example/style"}, + 0.75, + weight_name="style.safetensors", + )["lora"] + + self.assertEqual(result["lora_path"], "/cache/revision") + self.assertEqual(result["weight_name"], "style.safetensors") + + def test_modular_lora_carries_a_generic_scheduler_contract(self): + with patch("utils.huggingface.cached_file_path", return_value="/cache/revision/lightning.safetensors"): + result = Lora("lightning-lora").execute( + {"source": "hub", "value": "example/lightning"}, + 1.0, + weight_name="lightning.safetensors", + scheduler_class="FlowMatchEulerDiscreteScheduler", + scheduler_config='{"base_shift": 1.0986122886681098, "shift_terminal": null}', + )["lora"] + + self.assertEqual(result["scheduler_class"], "FlowMatchEulerDiscreteScheduler") + self.assertIsNone(result["scheduler_config"]["shift_terminal"]) + + def test_loader_applies_explicit_lora_scheduler_contract(self): + class FakePipeline: + def __init__(self): + self.scheduler = FlowMatchEulerDiscreteScheduler() + + def update_components(self, **components): + for name, component in components.items(): + setattr(self, name, component) + + pipeline = FakePipeline() + scheduler = apply_lora_scheduler_override( + pipeline, + { + "scheduler_class": "FlowMatchEulerDiscreteScheduler", + "scheduler_config": {"base_shift": 1.0986122886681098, "shift_terminal": None}, + }, + ) + + self.assertIs(pipeline.scheduler, scheduler) + self.assertAlmostEqual(scheduler.config.base_shift, 1.0986122886681098) + self.assertIsNone(scheduler.config.shift_terminal) + + def test_modular_lora_fails_if_model_manager_has_not_installed_weight(self): + with patch("utils.huggingface.cached_file_path", return_value=False): + with self.assertRaisesRegex(FileNotFoundError, "Model Manager"): + Lora("missing-lora").execute( + {"source": "hub", "value": "example/style"}, + 1.0, + weight_name="style.safetensors", + ) + + def test_hub_upscaler_requires_a_pinned_filename(self): + with self.assertRaisesRegex(ValueError, "pinned filename"): + Upscaler("unpinned-upscaler").execute( + image=object(), + model_id={"source": "hub", "value": "example/upscaler"}, + device="cpu", + ) + + def test_hub_upscaler_missing_from_app_cache_fails_before_model_load(self): + with patch("utils.huggingface.cached_file_path", return_value=False): + with patch("modules.Spandrel.main.ModelLoader") as loader: + with self.assertRaisesRegex(FileNotFoundError, "Model Manager"): + Upscaler("missing-upscaler").execute( + image=object(), + model_id={"source": "hub", "value": "example/upscaler/model.pth"}, + device="cpu", + ) + loader.assert_not_called() + + def test_upscaler_tiles_and_stitches_model_agnostic_integer_scale(self): + class FakeUpscaler: + device = "cpu" + + def __call__(self, image): + return torch.nn.functional.interpolate(image, scale_factor=2, mode="nearest") + + source = torch.arange(3 * 11 * 13, dtype=torch.float32).reshape(1, 3, 11, 13) + expected = FakeUpscaler()(source) + + actual = Upscaler._upscale_tensor_tiled(source, FakeUpscaler(), tile_size=5, tile_overlap=2) + + self.assertEqual(tuple(actual.shape), (1, 3, 22, 26)) + self.assertTrue(torch.equal(actual, expected)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_audio_operations.py b/tests/test_audio_operations.py new file mode 100644 index 0000000..b99d99e --- /dev/null +++ b/tests/test_audio_operations.py @@ -0,0 +1,207 @@ +import sys +import tempfile +import unittest +from pathlib import Path + +import numpy as np +from scipy.io import wavfile + + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from modules.Audio.main import Export, FitDuration, Join, MatchLoudness, _atempo_factors, _audio_to_numpy, _read_wav + + +class AudioExportTests(unittest.TestCase): + def test_unsigned_pcm_midpoint_is_silence_for_arrays_and_wav_files(self): + source = np.asarray([0, 128, 255], dtype=np.uint8) + converted, _sample_rate = _audio_to_numpy({"samples": source, "sample_rate": 8000}) + + with tempfile.TemporaryDirectory() as directory: + wav_path = Path(directory) / "unsigned.wav" + wavfile.write(wav_path, 8000, source) + loaded = _read_wav(wav_path) + + np.testing.assert_allclose(converted[:, 0], [-1.0, 0.0, 127 / 128], atol=1e-7) + np.testing.assert_allclose(loaded["samples"], [-1.0, 0.0, 127 / 128], atol=1e-7) + + def test_sample_rate_options_include_music_delivery_rates(self): + options = Export.params["sample_rate"]["options"] + + self.assertEqual(set(options), {"44100", "48000", "88200", "96000"}) + self.assertEqual(options["44100"], "44.1 kHz") + self.assertEqual(options["48000"], "48 kHz") + self.assertEqual(options["88200"], "88.2 kHz") + self.assertEqual(options["96000"], "96 kHz") + + def test_export_resamples_without_changing_duration_or_pitch(self): + source_rate = 48000 + target_rate = 44100 + frequency = 440 + times = np.arange(source_rate, dtype=np.float32) / source_rate + source = np.sin(2 * np.pi * frequency * times).astype(np.float32)[:, None] + + with tempfile.TemporaryDirectory(prefix="modiff-audio-export-") as temporary_dir: + output_path = Path(temporary_dir) / "export.wav" + result = Export("audio-export-sample-rate-test")( + audio={"samples": source, "sample_rate": source_rate}, + filename=str(output_path), + sample_rate=target_rate, + ) + written_rate, written = wavfile.read(output_path) + + self.assertEqual(written_rate, target_rate) + self.assertEqual(written.shape[0], target_rate) + self.assertAlmostEqual(result["duration_seconds"], 1.0, places=6) + frequencies = np.fft.rfftfreq(written.shape[0], d=1 / written_rate) + peak_frequency = float(frequencies[np.argmax(np.abs(np.fft.rfft(written)))]) + self.assertAlmostEqual(peak_frequency, frequency, delta=1) + + +class AudioFitDurationTests(unittest.TestCase): + def test_exact_window_and_positive_delay_do_not_time_warp(self): + sample_rate = 8000 + frame_count = sample_rate * 2 + source = np.linspace(-0.8, 0.8, frame_count, dtype=np.float32)[:, None] + source = np.repeat(source, 2, axis=1) + + result = FitDuration().execute( + audio={"samples": source, "sample_rate": sample_rate}, + source_start_seconds=0.25, + source_duration_seconds=1.0, + target_duration_seconds=1.0, + delay_seconds=0.1, + target_sample_rate=sample_rate, + ) + + output = result["output"]["samples"] + delay_frames = 800 + expected = source[2000 : 2000 + sample_rate - delay_frames] + self.assertEqual(output.shape, (sample_rate, 2)) + np.testing.assert_array_equal(output[:delay_frames], 0) + np.testing.assert_allclose(output[delay_frames:], expected, atol=0, rtol=0) + self.assertEqual(result["duration"], 1.0) + self.assertEqual(result["tempo_ratio"], 1.0) + self.assertEqual(result["stretch_engine"], "none") + + def test_fades_are_applied_to_shifted_content_boundaries(self): + sample_rate = 8000 + source = np.ones((sample_rate, 1), dtype=np.float32) + + result = FitDuration().execute( + audio={"samples": source, "sample_rate": sample_rate}, + source_duration_seconds=1.0, + target_duration_seconds=1.0, + delay_seconds=0.1, + target_sample_rate=sample_rate, + fade_in_seconds=0.01, + fade_out_seconds=0.02, + ) + + output = result["output"]["samples"][:, 0] + self.assertTrue(np.all(output[:800] == 0)) + self.assertAlmostEqual(float(output[800]), 0.0, places=6) + self.assertGreater(float(output[879]), 0.99) + self.assertGreater(float(output[-161]), 0.99) + self.assertAlmostEqual(float(output[-1]), 0.0, places=6) + + def test_pitch_preserving_fit_has_exact_duration_and_retains_tone(self): + sample_rate = 8000 + source_duration = 1.0 + target_duration = 0.75 + times = np.arange(round(sample_rate * source_duration), dtype=np.float32) / sample_rate + source = np.sin(2 * np.pi * 440 * times).astype(np.float32)[:, None] + + result = FitDuration().execute( + audio={"samples": source, "sample_rate": sample_rate}, + source_duration_seconds=source_duration, + target_duration_seconds=target_duration, + target_sample_rate=sample_rate, + ) + + output = result["output"]["samples"][:, 0] + analysis = output[800:-800] + frequencies = np.fft.rfftfreq(analysis.shape[0], d=1 / sample_rate) + peak_frequency = float(frequencies[np.argmax(np.abs(np.fft.rfft(analysis)))]) + self.assertEqual(output.shape[0], round(sample_rate * target_duration)) + self.assertAlmostEqual(result["tempo_ratio"], source_duration / target_duration, places=6) + self.assertIn(result["stretch_engine"], {"rubberband", "atempo"}) + self.assertAlmostEqual(peak_frequency, 440, delta=12) + + def test_atempo_factors_stay_in_the_high_quality_range(self): + for ratio in (0.1, 0.49, 1.0, 2.1, 8.0): + factors = _atempo_factors(ratio) + self.assertTrue(all(0.5 <= factor <= 2.0 for factor in factors)) + self.assertAlmostEqual(float(np.prod(factors)), ratio, places=9) + + +class AudioMatchLoudnessTests(unittest.TestCase): + def test_matches_reference_loudness_without_exceeding_peak_ceiling(self): + sample_rate = 48000 + times = np.arange(sample_rate * 6, dtype=np.float32) / sample_rate + reference = (0.45 * np.sin(2 * np.pi * 220 * times)).astype(np.float32)[:, None] + generated = (0.08 * np.sin(2 * np.pi * 220 * times)).astype(np.float32)[:, None] + + result = MatchLoudness().execute( + audio={"samples": generated, "sample_rate": sample_rate}, + reference={"samples": reference, "sample_rate": sample_rate}, + reference_window_seconds=6, + target_peak_dbfs=-1, + max_adjustment_db=20, + ) + + output = result["output"]["samples"] + self.assertEqual(output.shape, generated.shape) + self.assertLess(abs(result["output_lufs"] - result["reference_lufs"]), 0.5) + self.assertGreater(result["adjustment_db"], 10) + self.assertLessEqual(result["true_peak_dbfs"], -0.7) + + def test_preserves_internal_dynamics_with_one_constant_gain(self): + sample_rate = 48000 + times = np.arange(sample_rate * 4, dtype=np.float32) / sample_rate + carrier = np.sin(2 * np.pi * 220 * times).astype(np.float32) + envelope = np.repeat( + np.asarray([0.04, 0.12, 0.025, 0.08], dtype=np.float32), + sample_rate, + ) + generated = (carrier * envelope)[:, None] + reference = (0.35 * carrier)[:, None] + + result = MatchLoudness().execute( + audio={"samples": generated, "sample_rate": sample_rate}, + reference={"samples": reference, "sample_rate": sample_rate}, + reference_window_seconds=4, + target_peak_dbfs=-1, + max_adjustment_db=20, + ) + + output = result["output"]["samples"] + nonzero = np.abs(generated[:, 0]) > 1e-5 + sample_gain = output[nonzero, 0] / generated[nonzero, 0] + self.assertLess(float(np.max(sample_gain) - np.min(sample_gain)), 1e-4) + + +class AudioJoinTests(unittest.TestCase): + def test_appends_resampled_continuation_without_shortening_total_duration(self): + source_rate = 48000 + continuation_rate = 24000 + source = np.ones((source_rate * 2, 2), dtype=np.float32) * 0.25 + continuation = np.ones((continuation_rate, 1), dtype=np.float32) * 0.5 + + result = Join().execute( + source={"samples": source, "sample_rate": source_rate}, + continuation={"samples": continuation, "sample_rate": continuation_rate}, + boundary_fade_seconds=0.01, + ) + + output = result["output"]["samples"] + self.assertEqual(output.shape, (source_rate * 3, 2)) + self.assertEqual(result["sample_rate"], source_rate) + self.assertEqual(result["duration"], 3.0) + self.assertAlmostEqual(float(output[source_rate * 2 - 1, 0]), 0.0, places=6) + self.assertAlmostEqual(float(output[source_rate * 2, 0]), 0.0, places=6) + self.assertGreater(float(output[source_rate * 2 + 480, 0]), 0.49) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_auto_resource.py b/tests/test_auto_resource.py index 5a9b23d..69fe1e4 100644 --- a/tests/test_auto_resource.py +++ b/tests/test_auto_resource.py @@ -12,20 +12,28 @@ AUTO_MODEL_REQUIREMENTS, FLUX_KONTEXT_NVFP4_REPO, QWEN_IMAGE_EDIT_PREQUANTIZED_REPO, + QWEN_IMAGE_LAYERED_REPO, READY_PROOF_STATUSES, WAN_VACE_REPO, + Z_IMAGE_REPO, + _candidate_history_signature, + _requirements_missing_for_dict, _requirements_missing, + _runtime_key, + _validate_snapshot_shards, + auto_resource_history_key, build_auto_resource_plan, record_auto_resource_failure, record_auto_resource_success, ) from modiff.diffusers_profiles import ( # noqa: E402 ACE_STEP_REPO, - FLUX_KONTEXT_REPO, FLUX_KREA_REPO, FLUX_SCHNELL_REPO, + LTX_VIDEO_REPO, QWEN_IMAGE_2512_PREQUANTIZED_REPO, QWEN_IMAGE_2512_REPO, + WAN_T2V_1_3B_REPO, ) from modiff.auto_resource import FLUX_DEV_FP8_REPO # noqa: E402 @@ -34,6 +42,12 @@ class AutoResourcePlanTests(unittest.TestCase): + def test_resource_history_uses_stable_resource_fingerprint(self): + self.assertEqual( + _runtime_key({"fingerprint": "execution", "resourceFingerprint": "resource"}), + "resource", + ) + def _qwen_payload(self): return { "form": { @@ -62,14 +76,27 @@ def _runtime(self, *, vram_gib=16, free_gib=14, system="unit-test-runtime"): }, } - def _hardware(self, *, vram_gib=16, free_gib=14, system_ram_gib=32, disk_free_gib=128): + def _hardware( + self, + *, + vram_gib=16, + free_gib=14, + system_ram_gib=32, + disk_free_gib=128, + platform="linux", + architecture="x86_64", + capability=None, + ): return { "runtimeFingerprint": "unit-test-runtime", + "platform": platform, + "architecture": architecture, "accelerator": { "kind": "cuda", "name": "Mock CUDA", "totalBytes": vram_gib * GIB, "freeBytes": free_gib * GIB, + "capability": capability, "band": "unit-test", }, "systemMemory": { @@ -85,6 +112,33 @@ def _hardware(self, *, vram_gib=16, free_gib=14, system_ram_gib=32, disk_free_gi }, } + def _shared_rocm_hardware( + self, + *, + dedicated_gib=2, + accessible_gib=96, + system_ram_gib=128, + disk_free_gib=256, + ): + hardware = self._hardware( + vram_gib=dedicated_gib, + free_gib=max(0, dedicated_gib - 0.25), + system_ram_gib=system_ram_gib, + disk_free_gib=disk_free_gib, + ) + hardware["accelerator"].update( + { + "backend": "rocm", + "vendor": "amd", + "memoryKind": "shared", + "dedicatedTotalBytes": dedicated_gib * GIB, + "sharedTotalBytes": accessible_gib * GIB, + "accessibleTotalBytes": accessible_gib * GIB, + "band": "shared_memory", + } + ) + return hardware + def _normalized_mps_runtime(self): return { "fingerprint": "unit-test-mps-runtime", @@ -174,6 +228,18 @@ def _write_active_download_marker(self, cache_dir, repo): blobs.mkdir(parents=True, exist_ok=True) (blobs / "unit.incomplete").write_bytes(b"partial") + def test_standalone_pth_snapshot_is_a_complete_app_managed_artifact(self): + with tempfile.TemporaryDirectory() as temp_dir: + snapshot = Path(temp_dir) / "snapshot" + snapshot.mkdir() + (snapshot / "RealESRGAN_x4plus.pth").write_bytes(b"unit-test") + + status = _validate_snapshot_shards(snapshot) + + self.assertTrue(status["complete"]) + self.assertEqual(status["missingFiles"], []) + self.assertIn("direct weight files", status["reason"]) + def _write_incomplete_index_snapshot(self, cache_dir, repo, revision="unit"): snapshot = self._repo_cache_path(cache_dir, repo) / "snapshots" / revision (snapshot / "text_encoder").mkdir(parents=True, exist_ok=True) @@ -261,6 +327,41 @@ def test_qwen_on_constrained_cuda_prefers_prequantized_diffusers_artifact(self): self.assertNotIn("probe", selected) self.assertFalse(selected["requiresLocalProbe"]) self.assertEqual(plan["readiness"], "ready") + self.assertEqual(plan["schemaVersion"], 2) + self.assertEqual(plan["compatibility"]["state"], "ready") + self.assertEqual(plan["compatibility"]["source"], "backend_auto_planner") + + def test_constrained_auto_selects_only_compatible_recipe_and_explains_every_rejection(self): + plan = self._plan( + self._qwen_payload(), + runtime=self._runtime(vram_gib=16, free_gib=14), + repos=[QWEN_IMAGE_2512_REPO, QWEN_IMAGE_2512_PREQUANTIZED_REPO], + hardware=self._hardware(vram_gib=16, free_gib=14, system_ram_gib=32), + ) + + selected = plan["selectedCandidate"] + self.assertEqual(plan["status"], "ready") + self.assertEqual(selected["resolvedArtifact"], QWEN_IMAGE_2512_PREQUANTIZED_REPO) + self.assertIn(selected["proof"]["status"], READY_PROOF_STATUSES) + self.assertTrue(selected["installed"]) + self.assertTrue(selected["artifactStatus"]["complete"]) + self.assertEqual(selected["offloadMode"], "model_cpu") + self.assertFalse(selected["requirementsMissing"]) + + rejected = [ + candidate + for candidate in plan["candidates"] + if candidate["id"] != selected["id"] and candidate["proof"]["status"] not in READY_PROOF_STATUSES + ] + self.assertTrue(rejected) + for candidate in rejected: + explanation = ( + candidate.get("skipReason") + or (candidate.get("proof") or {}).get("message") + or "; ".join(candidate.get("requirementsMissing") or []) + or "; ".join(candidate.get("knownBadReasons") or []) + ) + self.assertTrue(explanation, candidate["id"]) def test_wan_uses_minimum_for_admission_and_keeps_recommended_metadata(self): plan = self._plan( @@ -277,6 +378,66 @@ def test_wan_uses_minimum_for_admission_and_keeps_recommended_metadata(self): self.assertEqual(selected["resolvedArtifact"], WAN_VACE_REPO) self.assertIn("hardwareSnapshot", plan) + def test_wan_high_memory_auto_avoids_unnecessary_cpu_offload(self): + plan = self._plan( + { + "form": { + "modelType": "WanVACEPipeline", + "mode": "text_to_video", + "offloadMode": "model_cpu", + } + }, + runtime=self._runtime(vram_gib=48, free_gib=44), + repos=[WAN_VACE_REPO], + hardware=self._hardware(vram_gib=48, free_gib=44, system_ram_gib=64), + ) + + self.assertEqual(plan["status"], "ready") + self.assertEqual(plan["selectedCandidate"]["offloadMode"], "none") + + def test_wan_preservation_modes_use_strength_capable_video_to_video_pipeline(self): + for mode in ("video_to_video", "video_color_edit"): + with self.subTest(mode=mode): + plan = self._plan( + {"form": {"modelType": "WanVideoPipeline", "mode": mode}}, + runtime=self._runtime(vram_gib=24, free_gib=22), + repos=[WAN_VACE_REPO, WAN_T2V_1_3B_REPO], + hardware=self._hardware(vram_gib=24, free_gib=22, system_ram_gib=48), + ) + + self.assertEqual(plan["status"], "ready") + selected = plan["selectedCandidate"] + self.assertEqual(selected["resolvedArtifact"], WAN_T2V_1_3B_REPO) + self.assertEqual(selected["pipelineClass"], "WanVideoToVideoPipeline") + self.assertEqual(selected["executionPath"], "direct-diffusers-video") + + def test_base_wan_text_generation_uses_the_registered_wan_pipeline(self): + plan = self._plan( + {"form": {"modelType": "WanVideoPipeline", "mode": "text_to_video"}}, + runtime=self._runtime(vram_gib=24, free_gib=22), + repos=[WAN_T2V_1_3B_REPO], + hardware=self._hardware(vram_gib=24, free_gib=22, system_ram_gib=48), + ) + + self.assertEqual(plan["status"], "ready") + self.assertEqual(plan["selectedCandidate"]["pipelineClass"], "WanPipeline") + + def test_ltx_uses_generic_video_execution_path_and_offload(self): + plan = self._plan( + {"form": {"modelType": "LTXVideoPipeline", "mode": "text_to_video"}}, + runtime=self._runtime(vram_gib=24, free_gib=21), + repos=[LTX_VIDEO_REPO], + hardware=self._hardware(vram_gib=24, free_gib=21, system_ram_gib=48), + ) + + self.assertEqual(plan["status"], "ready") + selected = plan["selectedCandidate"] + self.assertEqual(selected["executionPath"], "direct-diffusers-video") + self.assertEqual(selected["pipelineClass"], "LTXConditionPipeline") + self.assertEqual(selected["generation"]["numFrames"], 81) + self.assertEqual(selected["generation"]["steps"], 8) + self.assertEqual(selected["generation"]["guidanceScale"], 1.0) + def test_nominal_capacity_tiers_allow_small_reported_total_shortfalls(self): hardware = self._hardware(vram_gib=15.99, free_gib=14, system_ram_gib=31.8) @@ -299,6 +460,39 @@ def test_qwen_edit_prefers_apache_prequantized_install_on_nominal_16gb_cuda(self self.assertEqual(plan["status"], "needs_setup") self.assertEqual(plan["selectedInstallTarget"]["repo"], QWEN_IMAGE_EDIT_PREQUANTIZED_REPO) self.assertEqual(plan["candidates"][0]["resolvedArtifact"], QWEN_IMAGE_EDIT_PREQUANTIZED_REPO) + self.assertEqual(plan["compatibility"]["state"], "needs_model") + self.assertEqual(plan["compatibility"]["action"]["repo"], QWEN_IMAGE_EDIT_PREQUANTIZED_REPO) + + def test_qwen_edit_community_artifact_requires_explicit_workflow_confirmation(self): + hardware = self._hardware(vram_gib=15.99, free_gib=14, system_ram_gib=31.8) + unconfirmed = self._plan( + {"form": {"modelType": "QwenImageEditModularPipeline", "mode": "edit_image"}}, + runtime=self._runtime(vram_gib=15.99, free_gib=14), + repos=[QWEN_IMAGE_EDIT_PREQUANTIZED_REPO], + hardware=hardware, + ) + candidate = next( + item for item in unconfirmed["candidates"] + if item["resolvedArtifact"] == QWEN_IMAGE_EDIT_PREQUANTIZED_REPO + ) + self.assertIsNone(unconfirmed["selectedCandidate"]) + self.assertEqual(candidate["proof"]["status"], "manual_only") + self.assertEqual(candidate["healthBadge"], "Community option") + + confirmed = self._plan( + { + "form": { + "modelType": "QwenImageEditModularPipeline", + "mode": "edit_image", + "confirmedCommunityArtifact": QWEN_IMAGE_EDIT_PREQUANTIZED_REPO, + } + }, + runtime=self._runtime(vram_gib=15.99, free_gib=14), + repos=[QWEN_IMAGE_EDIT_PREQUANTIZED_REPO], + hardware=hardware, + ) + self.assertEqual(confirmed["selectedCandidate"]["resolvedArtifact"], QWEN_IMAGE_EDIT_PREQUANTIZED_REPO) + self.assertEqual(confirmed["selectedCandidate"]["proof"]["source"], "user_community_confirmation") def test_normalized_runtime_mps_snapshot_satisfies_cuda_or_mps(self): plan = self._plan( @@ -335,6 +529,31 @@ def test_legacy_runtime_mps_state_precedes_live_cpu_fallback(self): self.assertEqual(plan["hardware"]["accelerator"]["kind"], "mps") self.assertIsNone(plan["hardware"]["accelerator"]["totalBytes"]) + def test_z_image_auto_uses_intel_xpu_without_cuda_offload_hooks(self): + hardware = self._hardware(vram_gib=12, free_gib=10, system_ram_gib=32, platform="windows") + hardware["accelerator"].update( + { + "kind": "xpu", + "backend": "xpu", + "vendor": "intel", + "name": "Intel Arc Graphics", + "memoryKind": "shared", + } + ) + plan = self._plan( + {"form": {"modelType": "ZImageModularPipeline", "mode": "text_to_image"}}, + repos=[Z_IMAGE_REPO], + hardware=hardware, + ) + + self.assertEqual(plan["status"], "ready") + self.assertEqual(plan["compatibility"]["state"], "ready") + self.assertNotIn("Not suitable", plan["compatibility"]["summary"]) + selected = plan["selectedCandidate"] + self.assertEqual(selected["offloadMode"], "none") + self.assertFalse(selected["autoOffload"]) + self.assertIsNone(selected["deviceMap"]) + def test_qwen_official_bf16_is_not_auto_ready_on_constrained_cuda_without_prequantized_artifact(self): plan = self._plan( self._qwen_payload(), @@ -345,7 +564,7 @@ def test_qwen_official_bf16_is_not_auto_ready_on_constrained_cuda_without_prequa self.assertEqual(plan["status"], "needs_setup") self.assertIsNone(plan["selectedCandidate"]) - official = next(candidate for candidate in plan["candidates"] if candidate["artifact"] == QWEN_IMAGE_2512_REPO) + official = next(candidate for candidate in plan["candidates"] if candidate["id"] == "qwen-t2i-official-bf16-native") self.assertEqual(official["proof"]["status"], "skipped") self.assertTrue(any("GPU memory" in item for item in official["requirementsMissing"])) @@ -364,6 +583,89 @@ def test_qwen_official_bf16_can_be_ready_on_high_resource_cuda(self): self.assertEqual(selected["generation"]["width"], 1328) self.assertEqual(selected["generation"]["height"], 1328) + def test_qwen_native_candidate_preserves_requested_portrait_dimensions(self): + payload = self._qwen_payload() + payload["form"].update({"width": 768, "height": 1344}) + plan = self._plan( + payload, + runtime=self._runtime(vram_gib=98, free_gib=96), + repos=[QWEN_IMAGE_2512_REPO], + hardware=self._hardware(vram_gib=98, free_gib=96, system_ram_gib=120), + ) + + selected = plan["selectedCandidate"] + self.assertEqual(selected["id"], "qwen-t2i-official-bf16-native") + self.assertEqual(selected["generation"]["width"], 768) + self.assertEqual(selected["generation"]["height"], 1344) + + def test_qwen_official_bf16_stays_on_device_when_vram_has_headroom(self): + plan = self._plan( + self._qwen_payload(), + runtime=self._runtime(vram_gib=98, free_gib=96), + repos=[QWEN_IMAGE_2512_REPO], + hardware=self._hardware(vram_gib=98, free_gib=96, system_ram_gib=120), + ) + + self.assertEqual(plan["status"], "ready") + self.assertEqual(plan["selectedCandidate"]["resolvedArtifact"], QWEN_IMAGE_2512_REPO) + self.assertEqual(plan["selectedCandidate"]["offloadMode"], "none") + self.assertEqual(plan["selectedCandidate"]["deviceMap"], "cuda") + + def test_declared_qwen_edit_plus_profile_uses_generic_full_residency_metadata(self): + repo = "Qwen/Qwen-Image-Edit-2511" + plan = self._plan( + {"form": {"modelType": "QwenImageEditPlusModularPipeline", "mode": "edit_image", "offloadMode": "model_cpu"}}, + runtime=self._runtime(vram_gib=98, free_gib=96), + repos=[repo], + hardware=self._hardware(vram_gib=98, free_gib=96, system_ram_gib=120), + ) + + self.assertEqual(plan["status"], "ready") + self.assertEqual(plan["selectedCandidate"]["resolvedArtifact"], repo) + self.assertEqual(plan["selectedCandidate"]["offloadMode"], "none") + self.assertEqual(plan["selectedCandidate"]["deviceMap"], "cuda") + + def test_qwen_control_uses_native_residency_on_98_gib(self): + plan = self._plan( + { + "form": { + "modelType": "QwenImageModularPipeline", + "mode": "control_image", + "offloadMode": "none", + } + }, + runtime=self._runtime(vram_gib=98, free_gib=96), + repos=[QWEN_IMAGE_2512_REPO], + hardware=self._hardware(vram_gib=98, free_gib=96, system_ram_gib=120), + ) + + self.assertEqual(plan["status"], "ready") + selected = plan["selectedCandidate"] + self.assertEqual(selected["resolvedArtifact"], QWEN_IMAGE_2512_REPO) + self.assertEqual(selected["offloadMode"], "none") + self.assertEqual(selected["deviceMap"], "cuda") + + def test_qwen_control_lower_memory_runtime_keeps_model_cpu_offload(self): + plan = self._plan( + { + "form": { + "modelType": "QwenImageModularPipeline", + "mode": "control_image", + "offloadMode": "model_cpu", + } + }, + runtime=self._runtime(vram_gib=64, free_gib=60), + repos=[QWEN_IMAGE_2512_REPO], + hardware=self._hardware(vram_gib=64, free_gib=60, system_ram_gib=96), + ) + + self.assertEqual(plan["status"], "ready") + self.assertEqual(plan["compatibility"]["state"], "ready") + self.assertNotIn("Not suitable", plan["compatibility"]["summary"]) + selected = plan["selectedCandidate"] + self.assertEqual(selected["offloadMode"], "model_cpu") + self.assertIsNone(selected["deviceMap"]) + def test_auto_plan_does_not_use_planned_or_probe_statuses(self): plan = self._plan( self._qwen_payload(), @@ -394,7 +696,7 @@ def test_qwen_incomplete_prequantized_snapshot_does_not_enable_auto_run(self): self.assertTrue(any("incomplete" in item.lower() for item in prequantized["requirementsMissing"])) self.assertIn("text_encoder/model-00001-of-00002.safetensors", prequantized["artifactStatus"]["missingFiles"]) - def test_ace_audio_auto_candidate_uses_diffusers_audio_path_and_offload(self): + def test_ace_audio_auto_candidate_uses_direct_cuda_load_on_16gb(self): plan = self._plan( {"form": {"modelType": "AceStepAudioPipeline", "mode": "text_to_audio", "audioDuration": 30}}, runtime=self._runtime(vram_gib=16, free_gib=14), @@ -407,12 +709,166 @@ def test_ace_audio_auto_candidate_uses_diffusers_audio_path_and_offload(self): self.assertEqual(selected["executionPath"], "direct-diffusers-audio") self.assertEqual(selected["pipelineClass"], "AceStepPipeline") self.assertEqual(selected["resolvedArtifact"], ACE_STEP_REPO) - self.assertIn(selected["offloadMode"], {"model_cpu", "sequential_cpu", "group_cpu", "group_disk"}) + self.assertEqual(selected["offloadMode"], "none") + self.assertEqual(selected["deviceMap"], "cuda") self.assertEqual(selected["generation"]["audioDuration"], 30) self.assertEqual(selected["generation"]["steps"], 8) self.assertEqual(selected["generation"]["guidanceScale"], 1) self.assertEqual(selected["generation"]["shift"], 3) self.assertIn(selected["proof"]["status"], READY_PROOF_STATUSES) + self.assertEqual(selected["requirements"]["coldLoadTarget"]["maxSeconds"], 120) + self.assertEqual(AUTO_MODEL_REQUIREMENTS["AceStepAudioPipeline"]["coldLoadTarget"], { + "deviceName": "NVIDIA GeForce RTX 4080", + "maxSeconds": 120, + "recipe": { + "dtype": "bfloat16", + "offloadMode": "none", + "deviceMap": "cuda", + }, + }) + + def test_ace_audio_shared_rocm_uses_proven_offload_capacity_instead_of_local_vram(self): + plan = self._plan( + { + "form": { + "modelType": "AceStepAudioPipeline", + "mode": "audio_continuation", + "audioDuration": 75, + "extensionDuration": 15, + } + }, + repos=[ACE_STEP_REPO], + hardware=self._shared_rocm_hardware(), + ) + + self.assertEqual(plan["status"], "ready") + selected = plan["selectedCandidate"] + self.assertEqual(selected["offloadMode"], "model_cpu") + self.assertIsNone(selected["deviceMap"]) + self.assertEqual(selected["generation"]["audioDuration"], 75) + self.assertEqual(selected["generation"]["extensionDuration"], 15) + self.assertNotIn("GPU memory requires", " ".join(selected["requirementsMissing"])) + + def test_every_offloaded_auto_profile_uses_shared_accessible_capacity_but_not_for_full_residency(self): + hardware = self._shared_rocm_hardware(accessible_gib=128, system_ram_gib=128, disk_free_gib=512) + checked = [] + for key, requirements in AUTO_MODEL_REQUIREMENTS.items(): + supported = requirements.get("supportedOffloadModes") or [] + offload_mode = next((mode for mode in supported if mode != "none"), None) + minimum = requirements.get("minimum") + if not offload_mode or not isinstance(minimum, dict): + continue + with self.subTest(profile=key, offload_mode=offload_mode): + missing = _requirements_missing_for_dict(hardware, minimum, offload_mode=offload_mode) + self.assertFalse(any(item.startswith("GPU memory requires") for item in missing), missing) + full_residency_missing = _requirements_missing_for_dict(hardware, minimum, offload_mode="none") + if int(minimum.get("vramBytes") or 0) > 2 * GIB: + self.assertTrue( + any(item.startswith("GPU memory requires") for item in full_residency_missing), + full_residency_missing, + ) + checked.append(key) + + self.assertEqual(set(checked), set(AUTO_MODEL_REQUIREMENTS)) + + def test_exact_local_success_can_override_a_stale_static_vram_floor_when_observed_peaks_fit(self): + with tempfile.TemporaryDirectory() as data_dir: + hardware = self._hardware(vram_gib=2, free_gib=1.5, system_ram_gib=32, disk_free_gib=128) + payload = { + "form": { + "modelType": "AceStepAudioPipeline", + "mode": "audio_continuation", + "audioDuration": 75, + "extensionDuration": 15, + } + } + blocked = self._plan( + payload, + repos=[ACE_STEP_REPO], + hardware=hardware, + data_dir=data_dir, + ) + self.assertEqual(blocked["status"], "needs_setup") + candidate = blocked["candidates"][0] + record_auto_resource_success( + data_dir, + runtime_fingerprint={"resourceFingerprint": hardware["runtimeFingerprint"]}, + runtime_hints={"resourceMode": "auto", "autoResourcePlan": candidate}, + measurement={ + "peakAllocatedBytes": int(0.5 * GIB), + "peakReservedBytes": int(0.9 * GIB), + "processRssBytes": 14 * GIB, + "backend": "rocm", + "device": "cuda:0", + }, + ) + + proven = self._plan( + payload, + repos=[ACE_STEP_REPO], + hardware=hardware, + data_dir=data_dir, + ) + + self.assertEqual(proven["status"], "ready") + self.assertEqual(proven["selectedCandidate"]["proof"]["status"], "live_proven") + self.assertEqual(proven["selectedCandidate"]["requirementsMissing"], []) + + def test_every_declared_high_memory_profile_can_disable_unnecessary_offload(self): + cases = { + "ZImageModularPipeline": "text_to_image", + "LTXVideoPipeline": "text_to_video", + "AceStepAudioPipeline": "text_to_audio", + "FluxSchnellPipeline": "text_to_image", + "FluxDevPipeline": "text_to_image", + "Flux2KleinPipeline": "text_to_image", + "FluxKreaPipeline": "text_to_image", + "FluxKontextPipeline": "edit_image", + "FluxFillPipeline": "inpaint", + "FluxDepthPipeline": "control_image", + "FluxCannyPipeline": "control_image", + "FluxReduxPipeline": "edit_image", + } + for model_type, mode in cases.items(): + with self.subTest(model_type=model_type): + requirements = AUTO_MODEL_REQUIREMENTS[model_type] + self.assertIn("none", requirements["supportedOffloadModes"]) + self.assertTrue(requirements.get("fullResidency")) + plan = self._plan( + { + "form": { + "modelType": model_type, + "mode": mode, + "offloadMode": "model_cpu", + } + }, + runtime=self._runtime(vram_gib=98, free_gib=96), + repos=[requirements["defaultRepo"]], + hardware=self._hardware(vram_gib=98, free_gib=96, system_ram_gib=120), + ) + self.assertEqual(plan["status"], "ready") + self.assertEqual(plan["selectedCandidate"]["resolvedArtifact"], requirements["defaultRepo"]) + self.assertEqual(plan["selectedCandidate"]["offloadMode"], "none") + self.assertEqual(plan["selectedCandidate"]["deviceMap"], "cuda") + + def test_generic_auto_ignores_a_stale_expert_no_offload_value_on_constrained_hardware(self): + requirements = AUTO_MODEL_REQUIREMENTS["AceStepAudioPipeline"] + plan = self._plan( + { + "form": { + "modelType": "AceStepAudioPipeline", + "mode": "text_to_audio", + "offloadMode": "none", + } + }, + runtime=self._runtime(vram_gib=12, free_gib=10), + repos=[requirements["defaultRepo"]], + hardware=self._hardware(vram_gib=12, free_gib=10, system_ram_gib=32), + ) + + self.assertEqual(plan["status"], "ready") + self.assertEqual(plan["selectedCandidate"]["offloadMode"], "model_cpu") + self.assertIsNone(plan["selectedCandidate"]["deviceMap"]) def test_flux_schnell_is_auto_ready_on_16gb_cuda_when_installed(self): plan = self._plan( @@ -444,9 +900,11 @@ def test_flux_dev_on_16gb_targets_quantized_artifact_for_install(self): self.assertEqual(candidate["resolvedArtifact"], FLUX_DEV_FP8_REPO) self.assertEqual(candidate["installTarget"]["repo"], FLUX_DEV_FP8_REPO) self.assertEqual(candidate["installTarget"]["actionLabel"], "Install quantized artifact") - self.assertEqual(candidate["quantizationMode"], "quanto_float8") + self.assertEqual(candidate["quantizationMode"], "none") + self.assertEqual(candidate["loadedQuantization"], "fp8") + self.assertEqual(candidate["artifactResolution"]["resolved"]["format"], "fp8") - def test_flux_kontext_on_16gb_targets_nvfp4_artifact_for_install(self): + def test_flux_kontext_nvfp4_is_blocked_without_blackwell(self): plan = self._plan( {"form": {"modelType": "FluxKontextPipeline", "mode": "edit_image"}}, runtime=self._runtime(vram_gib=16, free_gib=14), @@ -455,12 +913,30 @@ def test_flux_kontext_on_16gb_targets_nvfp4_artifact_for_install(self): ) self.assertEqual(plan["status"], "needs_setup") - self.assertEqual(plan["selectedInstallTarget"]["repo"], FLUX_KONTEXT_NVFP4_REPO) + self.assertIsNone(plan["selectedInstallTarget"]) candidate = next(item for item in plan["candidates"] if item["resolvedArtifact"] == FLUX_KONTEXT_NVFP4_REPO) - self.assertEqual(candidate["quantizationMode"], "torchao_float8") - self.assertEqual(candidate["healthBadge"], "Needs setup") + self.assertEqual(candidate["quantizationMode"], "none") + self.assertEqual(candidate["loadedQuantization"], "nvfp4") + self.assertEqual(candidate["healthBadge"], "Not suitable locally") + self.assertIn("Blackwell", candidate["skipReason"]) + + def test_flux_kontext_nvfp4_can_be_offered_on_blackwell_linux(self): + plan = self._plan( + {"form": {"modelType": "FluxKontextPipeline", "mode": "edit_image"}}, + runtime=self._runtime(vram_gib=16, free_gib=14), + repos=[], + hardware=self._hardware( + vram_gib=16, + free_gib=14, + system_ram_gib=32, + platform="linux", + capability=(10, 0), + ), + ) + + self.assertEqual(plan["selectedInstallTarget"]["repo"], FLUX_KONTEXT_NVFP4_REPO) - def test_flux_krea_has_guarded_on_load_quantized_candidate(self): + def test_flux_krea_does_not_runtime_quantize_in_auto(self): plan = self._plan( {"form": {"modelType": "FluxKreaPipeline", "mode": "text_to_image"}}, runtime=self._runtime(vram_gib=16, free_gib=14), @@ -468,12 +944,23 @@ def test_flux_krea_has_guarded_on_load_quantized_candidate(self): hardware=self._hardware(vram_gib=16, free_gib=14, system_ram_gib=32), ) + self.assertEqual(plan["status"], "needs_setup") + self.assertIsNone(plan["selectedCandidate"]) + self.assertFalse(any(candidate["quantizationMode"] != "none" for candidate in plan["candidates"])) + + def test_qwen_layered_on_high_memory_prefers_native_bf16_without_offload(self): + plan = self._plan( + {"form": {"modelType": "QwenImageLayeredModularPipeline", "mode": "layer_decomposition"}}, + runtime=self._runtime(vram_gib=100, free_gib=94), + repos=[QWEN_IMAGE_LAYERED_REPO], + hardware=self._hardware(vram_gib=100, free_gib=94, system_ram_gib=96), + ) + self.assertEqual(plan["status"], "ready") selected = plan["selectedCandidate"] - self.assertEqual(selected["resolvedArtifact"], FLUX_KREA_REPO) - self.assertEqual(selected["qualityTier"], "on-load-quantized-guarded") - self.assertEqual(selected["quantizationMode"], "quanto_float8") - self.assertIn(selected["proof"]["status"], READY_PROOF_STATUSES) + self.assertEqual(selected["qualityTier"], "native-bf16-high-memory") + self.assertEqual(selected["quantizationMode"], "none") + self.assertEqual(selected["offloadMode"], "none") def test_corrupt_wrong_size_and_active_artifact_requires_repair(self): plan = self._plan( @@ -532,6 +1019,12 @@ def test_auto_history_demotes_failed_candidate_and_success_upgrades_it(self): data_dir, runtime_fingerprint=self._runtime(vram_gib=16, free_gib=14), runtime_hints=runtime_hints, + measurement={ + "elapsedSeconds": 12.5, + "backend": "cuda", + "device": "cuda:0", + "peakAllocatedBytes": 7 * GIB, + }, ) live_plan = self._plan( {"form": {"modelType": "FluxSchnellPipeline", "mode": "text_to_image"}}, @@ -542,6 +1035,190 @@ def test_auto_history_demotes_failed_candidate_and_success_upgrades_it(self): ) self.assertEqual(live_plan["status"], "ready") self.assertEqual(live_plan["selectedCandidate"]["proof"]["status"], "live_proven") + history = live_plan["selectedCandidate"]["successHistory"] + self.assertEqual(history["lastMeasurement"]["elapsedSeconds"], 12.5) + self.assertEqual(history["maxObservedPeakAllocatedBytes"], 7 * GIB) + + def test_auto_history_key_is_workload_shape_specific(self): + base = { + "modelType": "LTXVideoPipeline", + "mode": "text_to_video", + "artifact": LTX_VIDEO_REPO, + "dtype": "bfloat16", + "offloadMode": "model_cpu", + "generation": {"width": 768, "height": 512, "numFrames": 49, "steps": 30}, + } + runtime = self._runtime(vram_gib=16, free_gib=14) + longer = {**base, "generation": {**base["generation"], "numFrames": 97}} + + self.assertNotEqual( + auto_resource_history_key(base, runtime_fingerprint=runtime), + auto_resource_history_key(longer, runtime_fingerprint=runtime), + ) + + def test_auto_history_keys_use_media_specific_workloads(self): + runtime = self._runtime() + audio = { + "modelType": "AceStepAudioPipeline", + "mode": "audio_continuation", + "artifact": ACE_STEP_REPO, + "dtype": "bfloat16", + "offloadMode": "model_cpu", + "generation": { + "width": 1024, + "height": 1024, + "numFrames": 81, + "audioDuration": 75, + "extensionDuration": 15, + "steps": 8, + }, + } + audio_without_video_fields = { + **audio, + "generation": { + "audioDuration": 75, + "extensionDuration": 15, + "steps": 8, + }, + } + shorter_audio = { + **audio_without_video_fields, + "generation": {**audio_without_video_fields["generation"], "extensionDuration": 10}, + } + image = { + "modelType": "ZImageModularPipeline", + "mode": "text_to_image", + "artifact": Z_IMAGE_REPO, + "dtype": "bfloat16", + "offloadMode": "none", + "generation": {"width": 1024, "height": 1024, "numFrames": 81, "audioDuration": 75, "steps": 8}, + } + image_without_other_media = { + **image, + "generation": {"width": 1024, "height": 1024, "steps": 8}, + } + + self.assertEqual( + auto_resource_history_key(audio, runtime_fingerprint=runtime), + auto_resource_history_key(audio_without_video_fields, runtime_fingerprint=runtime), + ) + self.assertNotEqual( + auto_resource_history_key(audio_without_video_fields, runtime_fingerprint=runtime), + auto_resource_history_key(shorter_audio, runtime_fingerprint=runtime), + ) + self.assertEqual( + auto_resource_history_key(image, runtime_fingerprint=runtime), + auto_resource_history_key(image_without_other_media, runtime_fingerprint=runtime), + ) + + def test_legacy_audio_receipt_with_irrelevant_video_fields_is_reused_only_for_matching_audio(self): + with tempfile.TemporaryDirectory() as data_dir: + hardware = self._shared_rocm_hardware() + payload = { + "form": { + "modelType": "AceStepAudioPipeline", + "mode": "audio_continuation", + "audioDuration": 75, + "extensionDuration": 15, + } + } + baseline = self._plan( + payload, + repos=[ACE_STEP_REPO], + hardware=hardware, + data_dir=data_dir, + ) + candidate = baseline["selectedCandidate"] + legacy_candidate = json.loads(json.dumps(candidate)) + legacy_candidate["generation"].pop("extensionDuration", None) + legacy_candidate["generation"]["numFrames"] = 81 + legacy_signature = { + **{ + key: value + for key, value in _candidate_history_signature( + legacy_candidate, + hardware=hardware, + ).items() + if key != "workload" + }, + "workload": {"width": 1024, "height": 1024, "numFrames": 81, "steps": 8}, + } + history_path = Path(data_dir) / "auto_resource" / "history.json" + history_path.parent.mkdir(parents=True, exist_ok=True) + history_path.write_text( + json.dumps( + { + "version": 1, + "entries": { + "legacy-audio": { + "key": "legacy-audio", + "signature": legacy_signature, + "candidate": legacy_candidate, + "successCount": 1, + "lastSuccessAt": 100, + "lastStatus": "live_proven", + "lastMeasurement": { + "peakReservedBytes": int(0.9 * GIB), + "processRssBytes": 14 * GIB, + }, + } + }, + } + ), + encoding="utf-8", + ) + + migrated = self._plan( + payload, + repos=[ACE_STEP_REPO], + hardware=hardware, + data_dir=data_dir, + ) + different_duration = self._plan( + { + "form": { + "modelType": "AceStepAudioPipeline", + "mode": "audio_continuation", + "audioDuration": 60, + "extensionDuration": 15, + } + }, + repos=[ACE_STEP_REPO], + hardware=hardware, + data_dir=data_dir, + ) + + self.assertEqual(migrated["selectedCandidate"]["proof"]["status"], "live_proven") + self.assertEqual(migrated["selectedCandidate"]["compatibleHistoryKey"], "legacy-audio") + self.assertNotEqual(different_duration["selectedCandidate"]["proof"]["status"], "live_proven") + + def test_auto_history_key_is_exact_optimization_recipe_specific(self): + runtime = self._runtime() + base = { + "modelType": "ZImageModularPipeline", + "mode": "text_to_image", + "resolvedArtifact": Z_IMAGE_REPO, + "dtype": "bfloat16", + "offloadMode": "none", + "attentionBackend": "auto", + "regionalCompile": False, + "denoiserCache": "none", + "channelsLast": False, + "layerwiseCasting": False, + } + baseline = auto_resource_history_key(base, runtime_fingerprint=runtime) + for field, value in ( + ("attentionBackend", "flash"), + ("regionalCompile", True), + ("denoiserCache", "first_block"), + ("channelsLast", True), + ("layerwiseCasting", True), + ): + self.assertNotEqual( + baseline, + auto_resource_history_key({**base, field: value}, runtime_fingerprint=runtime), + field, + ) def test_each_current_studio_model_has_requirements_metadata(self): expected = { @@ -552,9 +1229,13 @@ def test_each_current_studio_model_has_requirements_metadata(self): "QwenImageEditPlusModularPipeline", "QwenImageLayeredModularPipeline", "WanVACEPipeline", + "WanVideoPipeline", + "WanVideoPipeline:text_to_video", + "LTXVideoPipeline", "AceStepAudioPipeline", "FluxSchnellPipeline", "FluxDevPipeline", + "Flux2KleinPipeline", "FluxKreaPipeline", "FluxKontextPipeline", "FluxFillPipeline", @@ -562,13 +1243,43 @@ def test_each_current_studio_model_has_requirements_metadata(self): "FluxCannyPipeline", "FluxReduxPipeline", } - self.assertTrue(expected.issubset(set(AUTO_MODEL_REQUIREMENTS))) + self.assertEqual(expected, set(AUTO_MODEL_REQUIREMENTS)) for key in expected: entry = AUTO_MODEL_REQUIREMENTS[key] self.assertTrue(entry.get("defaultRepo") or entry.get("manualOnlyReason"), key) if entry.get("manualOnlyReason"): self.assertIn("Auto", entry["manualOnlyReason"]) + def test_resource_planner_accepts_supported_ram_vram_os_matrix(self): + ram_tiers = (8, 16, 32, 64, 96) + vram_tiers = (None, 8, 16, 24, 32, 48, 96) + platforms = ("windows", "linux", "macos") + for platform_name in platforms: + for ram_gib in ram_tiers: + for vram_gib in vram_tiers: + with self.subTest(platform=platform_name, ram=ram_gib, vram=vram_gib): + hardware = self._hardware( + vram_gib=vram_gib or 0, + free_gib=max(0, (vram_gib or 0) - 1), + system_ram_gib=ram_gib, + platform=platform_name, + architecture="arm64" if platform_name == "macos" else "x86_64", + ) + if vram_gib is None: + hardware["accelerator"].update({ + "kind": "mps" if platform_name == "macos" else "cpu", + "totalBytes": None, + "freeBytes": None, + }) + plan = self._plan( + {"form": {"modelType": "ZImageModularPipeline", "mode": "text_to_image"}}, + hardware=hardware, + ) + self.assertFalse(plan["error"]) + self.assertTrue(plan["candidates"]) + self.assertEqual(plan["hardware"]["platform"], platform_name) + self.assertTrue(all(candidate["quantizationMode"] == "none" for candidate in plan["candidates"])) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_deterministic_mode.py b/tests/test_deterministic_mode.py new file mode 100644 index 0000000..416b17f --- /dev/null +++ b/tests/test_deterministic_mode.py @@ -0,0 +1,129 @@ +import os +import random +import sys +import unittest +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import patch + +import numpy as np + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from modiff.server import WebServer + + +class DeterministicModeTests(unittest.TestCase): + def setUp(self): + self.server = object.__new__(WebServer) + self.graph = { + "deterministicMode": {"enabled": True, "seed": 8201, "strict": False}, + "nodes": {}, + } + + def test_non_strict_mode_locks_rng_without_forcing_deterministic_kernels(self): + with ( + patch("torch.manual_seed") as manual_seed, + patch("torch.cuda.is_available", return_value=True), + patch("torch.cuda.manual_seed_all") as manual_seed_all, + patch("torch.use_deterministic_algorithms") as deterministic_algorithms, + ): + applied = WebServer._apply_deterministic_mode(self.server, self.graph) + + manual_seed.assert_called_once_with(8201) + manual_seed_all.assert_called_once_with(8201) + deterministic_algorithms.assert_not_called() + self.assertFalse(applied["strict"]) + self.assertTrue(applied["settings"]["torch_manual_seed"]) + self.assertFalse(applied["settings"]["torch_deterministic_algorithms"]) + + def test_strict_mode_still_enables_deterministic_kernels(self): + self.graph["deterministicMode"]["strict"] = True + with ( + patch("torch.manual_seed"), + patch("torch.cuda.is_available", return_value=False), + patch("torch.use_deterministic_algorithms") as deterministic_algorithms, + ): + applied = WebServer._apply_deterministic_mode(self.server, self.graph) + + deterministic_algorithms.assert_called_once_with(True, warn_only=False) + self.assertTrue(applied["strict"]) + self.assertTrue(applied["settings"]["torch_deterministic_algorithms"]) + + def test_strict_mode_fails_closed_when_torch_cannot_enforce_it(self): + self.graph["deterministicMode"]["strict"] = True + with ( + patch("torch.manual_seed"), + patch("torch.cuda.is_available", return_value=False), + patch("torch.use_deterministic_algorithms", side_effect=RuntimeError("unsupported kernel")), + ): + with self.assertRaisesRegex(RuntimeError, "Strict deterministic Torch settings"): + WebServer._apply_deterministic_mode(self.server, self.graph) + + def test_strict_mode_requires_a_fixed_seed(self): + self.graph["deterministicMode"] = {"enabled": True, "strict": True} + + with self.assertRaisesRegex(ValueError, "requires a fixed seed"): + WebServer._apply_deterministic_mode(self.server, self.graph) + + def test_process_wide_execution_state_is_restored_after_a_run(self): + restored = {} + cuda = SimpleNamespace( + is_initialized=lambda: False, + set_rng_state_all=lambda value: restored.update(cuda_rng=value), + set_per_process_memory_fraction=lambda fraction, index: restored.update(memory_fraction=(fraction, index)), + ) + cudnn = SimpleNamespace(benchmark=True, deterministic=False, allow_tf32=True) + matmul = SimpleNamespace(allow_tf32=True) + fake_torch = SimpleNamespace( + get_rng_state=lambda: "torch-before", + set_rng_state=lambda value: restored.update(torch_rng=value), + are_deterministic_algorithms_enabled=lambda: False, + is_deterministic_algorithms_warn_only_enabled=lambda: False, + use_deterministic_algorithms=lambda enabled, warn_only=False: restored.update( + deterministic=(enabled, warn_only) + ), + cuda=cuda, + backends=SimpleNamespace(cudnn=cudnn, cuda=SimpleNamespace(matmul=matmul)), + ) + + before_random = random.getstate() + before_numpy = np.random.get_state() + prior_hash_seed = os.environ.pop("PYTHONHASHSEED", None) + try: + with patch( + "modiff.server.import_module", + side_effect=lambda name: fake_torch if name == "torch" else np, + ): + state = WebServer._capture_execution_process_state(self.server) + random.seed(999) + np.random.seed(999) + os.environ["PYTHONHASHSEED"] = "999" + cudnn.benchmark = False + cudnn.deterministic = True + cudnn.allow_tf32 = False + matmul.allow_tf32 = False + WebServer._restore_execution_process_state( + self.server, + state, + {"runtimeHints": {"device": "cuda:0"}}, + ) + finally: + if prior_hash_seed is not None: + os.environ["PYTHONHASHSEED"] = prior_hash_seed + else: + os.environ.pop("PYTHONHASHSEED", None) + + self.assertEqual(random.getstate(), before_random) + self.assertTrue(np.array_equal(np.random.get_state()[1], before_numpy[1])) + self.assertEqual(restored["torch_rng"], "torch-before") + self.assertEqual(restored["deterministic"], (False, False)) + self.assertEqual(restored["memory_fraction"], (1.0, 0)) + self.assertTrue(cudnn.benchmark) + self.assertFalse(cudnn.deterministic) + self.assertTrue(cudnn.allow_tf32) + self.assertTrue(matmul.allow_tf32) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_diffusers_adapters.py b/tests/test_diffusers_adapters.py new file mode 100644 index 0000000..a13cd8c --- /dev/null +++ b/tests/test_diffusers_adapters.py @@ -0,0 +1,125 @@ +import unittest +import tempfile +from pathlib import Path + +from modules.DiffusersAdapters.main import ( + LoRAComparisonJobs, + LoRAFuseUnfuse, + LoRAHotswap, + LoRAMergeArtifact, + LoRAUnloadReset, + apply_lora_mix, +) + + +class FakePipeline: + def __init__(self): + self.loaded = [] + self.active = None + self.adapters = {"transformer": ["existing"]} + self.fused = [] + self.deleted = [] + self.unloaded = 0 + self.transformer = FakeMergeComponent() + + def get_list_adapters(self): + return self.adapters + + def load_lora_weights(self, path, **kwargs): + self.loaded.append((path, kwargs)) + name = kwargs["adapter_name"] + if name not in self.adapters["transformer"]: + self.adapters["transformer"].append(name) + + def set_adapters(self, names, weights): + self.active = (names, weights) + + def fuse_lora(self, **kwargs): + self.fused.append(kwargs) + + def unfuse_lora(self, **kwargs): + self.fused.append({"unfuse": kwargs}) + + def delete_adapters(self, name): + self.deleted.append(name) + + def unload_lora_weights(self): + self.unloaded += 1 + + +class FakeMergeComponent: + def __init__(self): + self.merges = [] + self.saves = [] + + def add_weighted_adapter(self, names, weights, merged_name, **kwargs): + self.merges.append((names, weights, merged_name, kwargs)) + + def save_pretrained(self, directory, **kwargs): + self.saves.append((directory, kwargs)) + + +def adapter(name, scale=1.0): + return {"lora_path": f"/{name}", "weight_name": f"{name}.safetensors", "adapter_name": name, "scale": scale} + + +class DiffusersAdapterTests(unittest.TestCase): + def test_stack_loads_missing_adapters_and_activates_independent_weights(self): + pipeline = FakePipeline() + result = apply_lora_mix(pipeline, [adapter("existing", 0.25), adapter("style", 0.75)]) + + self.assertEqual([item[1]["adapter_name"] for item in pipeline.loaded], ["style"]) + self.assertEqual(pipeline.active, (["existing", "style"], [0.25, 0.75])) + self.assertEqual(result["adapter_names"], ["existing", "style"]) + + def test_hotswap_requires_an_existing_slot_and_uses_in_place_api(self): + pipeline = FakePipeline() + LoRAHotswap().execute(pipeline=pipeline, replacement=adapter("replacement", 0.6), slot_name="existing") + + self.assertTrue(pipeline.loaded[0][1]["hotswap"]) + self.assertEqual(pipeline.loaded[0][1]["adapter_name"], "existing") + self.assertEqual(pipeline.active, (["existing"], [0.6])) + + def test_fuse_reset_and_comparison_jobs_preserve_explicit_user_choices(self): + pipeline = FakePipeline() + LoRAFuseUnfuse().execute( + pipeline=pipeline, + operation="fuse", + adapter_names="existing,style", + components="transformer", + scale=0.8, + safe_fusing=True, + ) + LoRAUnloadReset().execute(pipeline=pipeline, adapter_names="style") + jobs = LoRAComparisonJobs().execute(prompt="portrait", mixes='[{"existing": 1.0}, {"style": 0.7}]', seed=42) + + self.assertEqual(pipeline.fused[0]["adapter_names"], ["existing", "style"]) + self.assertEqual(pipeline.deleted, ["style"]) + self.assertEqual(jobs["jobs"][1]["mix"], {"style": 0.7}) + self.assertEqual(jobs["jobs"][1]["seed"], 42) + + def test_merge_artifact_uses_peft_method_and_writes_provenance(self): + pipeline = FakePipeline() + pipeline.adapters = {"transformer": ["existing", "style"]} + with tempfile.TemporaryDirectory() as directory: + destination = Path(directory) / "merged" + result = LoRAMergeArtifact().execute( + pipeline=pipeline, + component="transformer", + adapter_names="existing,style", + weights="1.0,0.7", + merge_method="dare_ties", + density=0.4, + merged_name="final_mix", + output_directory=str(destination), + ) + + merge = pipeline.transformer.merges[0] + self.assertEqual(merge[0], ["existing", "style"]) + self.assertEqual(merge[3], {"combination_type": "dare_ties", "density": 0.4}) + self.assertTrue((destination / "modiff_merge_manifest.json").is_file()) + self.assertEqual(result["artifact_path"], str(destination)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_diffusers_audio.py b/tests/test_diffusers_audio.py index c921783..53ae3ed 100644 --- a/tests/test_diffusers_audio.py +++ b/tests/test_diffusers_audio.py @@ -1,18 +1,32 @@ import sys +import tempfile import unittest from pathlib import Path from types import SimpleNamespace +from unittest.mock import patch import numpy as np sys.path.insert(0, str(Path(__file__).resolve().parents[1])) -from modules.DiffusersAudio.main import Generate # noqa: E402 +from modules.DiffusersAudio.main import ( # noqa: E402 + AUDIO_SAMPLE_RATE_OPTIONS, + FuseAdapters, + Generate, + LoadAdapter, + LoadPipeline, + SetAdapters, + audio_to_numpy, + audio_to_tensor, + crop_tail, +) +from modiff.server import to_bytes # noqa: E402 class FakeAceStepPipeline: device = "cpu" + sample_rate = 48000 def __init__(self): self.call_kwargs = None @@ -22,7 +36,245 @@ def __call__(self, bpm=None, **kwargs): return SimpleNamespace(audios=np.zeros((1, 480), dtype=np.float32)) +class FakeSourceConditionedAceStepPipeline: + device = "cpu" + sample_rate = 48000 + + def __init__(self): + self.call_kwargs = None + + def __call__( + self, + prompt=None, + lyrics=None, + audio_duration=None, + task_type=None, + src_audio=None, + reference_audio=None, + audio_cover_strength=None, + **kwargs, + ): + self.call_kwargs = { + **kwargs, + "prompt": prompt, + "lyrics": lyrics, + "audio_duration": audio_duration, + "task_type": task_type, + "src_audio": src_audio, + "reference_audio": reference_audio, + "audio_cover_strength": audio_cover_strength, + } + return SimpleNamespace(audios=np.zeros((1, 480), dtype=np.float32)) + + class DiffusersAudioGenerateTests(unittest.TestCase): + def test_graph_contract_distinguishes_required_and_optional_audio_inputs(self): + for node_class in (LoadAdapter, SetAdapters, FuseAdapters, Generate): + with self.subTest(node=node_class.__name__): + self.assertTrue(node_class.params["pipeline"]["required"]) + + self.assertFalse(Generate.params["source_audio"]["required"]) + self.assertFalse(Generate.params["reference_audio"]["required"]) + self.assertTrue(Generate.params["lora_scale"]["hidden"]) + self.assertIn("per-call multiplier", Generate.params["lora_scale"]["description"]) + self.assertIn("ignored by ACE-Step", Generate.params["stable_audio_steps"]["description"]) + self.assertIn("ignored by ACE-Step", Generate.params["stable_audio_guidance"]["description"]) + self.assertIn("ignored by ACE-Step", Generate.params["num_waveforms"]["description"]) + + def test_ace_step_lora_load_set_and_fuse_contracts(self): + events = [] + + class Pipeline: + _modiff_audio_pipeline_class = "AceStepPipeline" + + def unload_lora_weights(self): + events.append("unload") + + def load_lora_weights(self, path, **kwargs): + events.append(("load", path, kwargs)) + + def set_adapters(self, names, weights): + events.append(("set", names, weights)) + + def fuse_lora(self, **kwargs): + events.append(("fuse", kwargs)) + + pipeline = Pipeline() + LoadAdapter("audio-lora").execute( + pipeline=pipeline, + adapter_path={"source": "local", "value": "/models/audio-style"}, + adapter_name="style", + scale=0.6, + ) + SetAdapters("audio-blend").execute( + pipeline=pipeline, + adapter_names="style", + adapter_weights="0.4", + ) + FuseAdapters("audio-fuse").execute(pipeline=pipeline, enabled=True, safe_fusing=True) + + self.assertEqual(events[0], "unload") + self.assertEqual(events[1][0], "load") + self.assertEqual(events[2], ("set", ["style"], [0.6])) + self.assertEqual(events[3], ("set", ["style"], [0.4])) + self.assertEqual(events[4], ("fuse", {"safe_fusing": True})) + + def test_audio_lora_rejects_non_ace_pipeline(self): + with self.assertRaisesRegex(ValueError, "AceStepPipeline"): + LoadAdapter("wrong-audio-lora").execute( + pipeline=SimpleNamespace(_modiff_audio_pipeline_class="StableAudioPipeline"), + adapter_path={"source": "local", "value": "/models/audio-style"}, + ) + + def test_stable_audio_uses_native_generation_rate_and_requested_delivery_rate(self): + class FakeStableAudio: + _modiff_audio_pipeline_class = "StableAudioPipeline" + device = "cpu" + vae = SimpleNamespace(config={"sampling_rate": 44100}) + + def __init__(self): + self.call_kwargs = None + + def __call__(self, **kwargs): + self.call_kwargs = kwargs + return SimpleNamespace(audios=np.zeros((2, 2, 44100), dtype=np.float32)) + + pipeline = FakeStableAudio() + result = Generate().execute( + pipeline=pipeline, + prompt="Clear wooden impacts in a quiet room", + negative_prompt="low quality", + audio_duration=1, + stable_audio_steps=120, + stable_audio_guidance=0, + num_waveforms=2, + sample_rate=48000, + ) + + self.assertEqual(pipeline.call_kwargs["audio_end_in_s"], 1) + self.assertEqual(pipeline.call_kwargs["num_inference_steps"], 120) + self.assertEqual(pipeline.call_kwargs["guidance_scale"], 0) + self.assertEqual(pipeline.call_kwargs["num_waveforms_per_prompt"], 2) + self.assertEqual(result["sample_rate_out"], 48000) + self.assertEqual(result["duration_seconds"], 1) + self.assertEqual(result["audio"]["samples"].shape[-1], 48000) + self.assertEqual(len(result["audio_variations"]), 2) + self.assertTrue(all(item["samples"].shape == (2, 48000) for item in result["audio_variations"])) + self.assertIs(result["audio"], result["audio_variations"][0]) + + def test_explicit_zero_audio_controls_are_forwarded(self): + pipeline = FakeSourceConditionedAceStepPipeline() + node = Generate("ace-zero-values-test") + node.progress = lambda *args, **kwargs: None + source = {"samples": np.zeros((2, 480), dtype=np.float32), "sample_rate": 48000} + + result = node.execute( + pipeline=pipeline, + task_type="cover", + source_audio=source, + prompt="A silent control fixture", + audio_duration=0.01, + guidance_scale=0, + shift=0, + audio_cover_strength=0, + ) + + self.assertEqual(pipeline.call_kwargs["guidance_scale"], 0) + self.assertEqual(pipeline.call_kwargs["shift"], 0) + self.assertEqual(pipeline.call_kwargs["audio_cover_strength"], 0) + self.assertEqual(result["audio_variations"], [result["audio"]]) + + def test_unsigned_pcm_midpoint_normalizes_to_zero(self): + samples, _sample_rate = audio_to_numpy( + {"samples": np.asarray([0, 128, 255], dtype=np.uint8), "sample_rate": 8000} + ) + + np.testing.assert_allclose(samples[0], [-1.0, 0.0, 127 / 128], atol=1e-7) + + def test_audio_frame_tail_is_cropped_to_requested_duration(self): + audio = { + "samples": np.zeros((2, 24000 * 2), dtype=np.float32), + "sample_rate": 24000, + "duration_seconds": 2.0, + } + cropped = crop_tail(audio, 0.0, 1.0) + self.assertEqual(cropped["samples"].shape, (2, 24000)) + self.assertEqual(cropped["duration_seconds"], 1.0) + + def test_shared_audio_contract_serializes_to_wav_bytes(self): + encoded = to_bytes( + "audio", + {"samples": np.asarray([[0.0, 0.5, -0.5]], dtype=np.float32), "sample_rate": 24000}, + ) + self.assertEqual(encoded[:4], b"RIFF") + self.assertEqual(encoded[8:12], b"WAVE") + + with tempfile.NamedTemporaryFile(suffix=".wav") as output: + output.write(encoded) + output.flush() + self.assertEqual(to_bytes("audio", output.name), encoded) + + def test_loader_rejects_unsupported_mode_before_resolving_pipeline(self): + node = LoadPipeline("ace-mode-test") + with self.assertRaisesRegex(ValueError, "does not support video_to_video"): + node.execute(pipeline_class="AceStepPipeline", mode="video_to_video") + + def test_no_offload_audio_pipeline_loads_directly_on_cuda(self): + loaded = {} + + class FakePipeline: + @classmethod + def from_pretrained(cls, repo, **kwargs): + loaded.update({"repo": repo, "kwargs": kwargs}) + return cls() + + node = LoadPipeline("ace-direct-load-test") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("modules.DiffusersAudio.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersAudio.main.apply_pipeline_offload"), + ): + node.execute( + model_id="org/ace-step", + pipeline_class="AceStepPipeline", + mode="text_to_audio", + device="cuda:0", + auto_offload=False, + offload_mode="none", + ) + + self.assertEqual(loaded["repo"], "org/ace-step") + self.assertEqual(loaded["kwargs"]["device_map"], "cuda") + self.assertIsNone(loaded["kwargs"]["revision"]) + + def test_curated_audio_pipeline_uses_catalog_revision(self): + loaded = {} + + class FakePipeline: + @classmethod + def from_pretrained(cls, repo, **kwargs): + loaded.update({"repo": repo, "kwargs": kwargs}) + return cls() + + node = LoadPipeline("ace-revision-test") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("modules.DiffusersAudio.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersAudio.main.apply_pipeline_offload"), + ): + node.execute( + model_id="ACE-Step/acestep-v15-xl-turbo-diffusers", + pipeline_class="AceStepPipeline", + mode="text_to_audio", + device="cpu", + auto_offload=False, + offload_mode="none", + ) + + self.assertEqual(loaded["kwargs"]["revision"], "200ba991ae448051e14b0183157e35c2d27c9fb0") + def test_xl_turbo_schema_uses_distilled_defaults(self): steps = Generate.params["num_inference_steps"] guidance = Generate.params["guidance_scale"] @@ -33,6 +285,28 @@ def test_xl_turbo_schema_uses_distilled_defaults(self): self.assertIn("guidance-distilled", guidance["description"]) self.assertIn("above 1", guidance["description"]) + def test_generate_sample_rate_is_a_four_option_delivery_selector(self): + self.assertEqual( + Generate.params["sample_rate"]["options"], + AUDIO_SAMPLE_RATE_OPTIONS, + ) + + def test_ace_output_is_resampled_to_requested_delivery_rate(self): + pipeline = FakeAceStepPipeline() + node = Generate("ace-sample-rate-test") + node.progress = lambda *args, **kwargs: None + + result = node.execute( + pipeline=pipeline, + prompt="A short instrumental cue", + audio_duration=0.01, + sample_rate=96000, + ) + + self.assertEqual(result["sample_rate_out"], 96000) + self.assertEqual(result["audio"]["sample_rate"], 96000) + self.assertEqual(result["audio"]["samples"].shape[-1], 960) + def test_execute_forwards_xl_turbo_defaults_when_values_are_omitted(self): pipeline = FakeAceStepPipeline() node = Generate("ace-defaults-test") @@ -44,6 +318,34 @@ def test_execute_forwards_xl_turbo_defaults_when_values_are_omitted(self): self.assertEqual(pipeline.call_kwargs["num_inference_steps"], 8) self.assertEqual(pipeline.call_kwargs["guidance_scale"], 1.0) + def test_execute_reports_indeterminate_progress_before_ace_pipeline_starts(self): + events = [] + + class ProgressAwarePipeline(FakeAceStepPipeline): + def __call__(self, bpm=None, **kwargs): + events.append(("pipeline",)) + return super().__call__(bpm=bpm, **kwargs) + + pipeline = ProgressAwarePipeline() + node = Generate("ace-initial-progress-test") + node.progress = lambda value, **metadata: events.append(("progress", value, metadata)) + + node.execute( + pipeline=pipeline, + prompt="A short instrumental cue", + audio_duration=1, + num_inference_steps=8, + sample_rate=48000, + ) + + self.assertEqual(events[0][0], "progress") + self.assertEqual(events[0][1], -1) + self.assertEqual(events[0][2]["phase"], "denoising") + self.assertEqual(events[0][2]["message"], "Generating audio (text2music)") + self.assertEqual(events[0][2]["current_step"], 0) + self.assertEqual(events[0][2]["total_steps"], 8) + self.assertEqual(events[1], ("pipeline",)) + def test_execute_normalizes_string_bpm_for_ace_metadata(self): pipeline = FakeAceStepPipeline() node = Generate("ace-bpm-test") @@ -60,6 +362,49 @@ def test_execute_normalizes_string_bpm_for_ace_metadata(self): self.assertEqual(pipeline.call_kwargs["bpm"], 170) self.assertIsInstance(pipeline.call_kwargs["bpm"], int) + def test_cover_routes_source_track_to_reference_audio_without_optional_audio_code_modules(self): + pipeline = FakeSourceConditionedAceStepPipeline() + node = Generate("ace-cover-reference-test") + node.progress = lambda *args, **kwargs: None + source = {"samples": np.zeros((2, 480), dtype=np.float32), "sample_rate": 48000} + + node.execute( + pipeline=pipeline, + task_type="cover", + source_audio=source, + prompt="A restrained acoustic variation", + audio_duration=0.01, + ) + + self.assertIsNone(pipeline.call_kwargs["src_audio"]) + self.assertIsNotNone(pipeline.call_kwargs["reference_audio"]) + + def test_repaint_routes_source_track_to_src_audio_without_implicit_timbre_reference(self): + pipeline = FakeSourceConditionedAceStepPipeline() + node = Generate("ace-repaint-source-test") + node.progress = lambda *args, **kwargs: None + source = {"samples": np.zeros((2, 480), dtype=np.float32), "sample_rate": 48000} + + node.execute( + pipeline=pipeline, + task_type="repaint", + source_audio=source, + prompt="Repair the selected interval", + audio_duration=0.01, + repainting_start=0, + repainting_end=0.01, + ) + + self.assertIsNotNone(pipeline.call_kwargs["src_audio"]) + self.assertIsNone(pipeline.call_kwargs["reference_audio"]) + + def test_audio_input_is_resampled_to_the_pipeline_native_rate(self): + source = {"samples": np.zeros((2, 44100), dtype=np.float32), "sample_rate": 44100} + + tensor = audio_to_tensor(source, device="cpu", target_sample_rate=48000) + + self.assertEqual(tuple(tensor.shape), (2, 48000)) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_diffusers_image_registry.py b/tests/test_diffusers_image_registry.py index 6ed2bbf..4b112c8 100644 --- a/tests/test_diffusers_image_registry.py +++ b/tests/test_diffusers_image_registry.py @@ -1,12 +1,125 @@ +import hashlib import json +import tempfile import unittest +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import Mock, patch + +import numpy as np +from PIL import Image import modules as module_registry from modiff.server import WebServer -from modules.DiffusersImage import ControlGenerate, Edit, Inpaint, MODULE_MAP +from modules.DiffusersImage import ControlGenerate, Edit, Inpaint, LoadAdapter, LoadPipeline, MODULE_MAP +from modules.DiffusersImage.main import ( + FLUX_DEV_REPO, + FluxReduxPipelineBundle, + add_progress_callback, + output_image_dimensions, + quant_config_for, +) +from modules.DiffusersImage.main import IMAGE_PIPELINE_ADAPTERS class DiffusersImageRegistryTests(unittest.TestCase): + def test_output_dimensions_support_pil_numpy_and_torch_layouts(self): + self.assertEqual(output_image_dimensions([Image.new("RGB", (31, 19))], "pil"), (31, 19)) + self.assertEqual(output_image_dimensions(np.zeros((2, 19, 31, 3)), "np"), (31, 19)) + fake_tensor = SimpleNamespace(shape=(2, 3, 19, 31), size=lambda: 2 * 3 * 19 * 31) + self.assertEqual(output_image_dimensions(fake_tensor, "pt"), (31, 19)) + + def test_torchao_int8_weight_only_passes_an_aobase_config_instance(self): + int8_config = object() + torchao_config = object() + with ( + patch.dict( + "sys.modules", + { + "torchao": SimpleNamespace(), + "torchao.quantization": SimpleNamespace( + Int8WeightOnlyConfig=Mock(return_value=int8_config), + ), + }, + ), + patch("diffusers.TorchAoConfig", return_value=torchao_config) as config, + ): + result = quant_config_for("torchao_int8_weight_only", None, ["proj_out"]) + + self.assertIs(result, torchao_config) + self.assertIs(config.call_args.kwargs["quant_type"], int8_config) + self.assertEqual(config.call_args.kwargs["modules_to_not_convert"], ["proj_out"]) + + def test_quanto_float8_uses_the_current_weights_dtype_argument(self): + received = {} + + class FakeQuantoConfig: + def __init__(self, **kwargs): + received.update(kwargs) + + with patch("diffusers.QuantoConfig", FakeQuantoConfig): + quant_config_for("quanto_float8", None) + + self.assertEqual(received, {"weights_dtype": "float8", "modules_to_not_convert": None}) + + def test_quanto_int8_uses_weight_only_int8(self): + received = {} + + class FakeQuantoConfig: + def __init__(self, **kwargs): + received.update(kwargs) + + with patch("diffusers.QuantoConfig", FakeQuantoConfig): + quant_config_for("quanto_int8", None, ["norm"]) + + self.assertEqual(received, {"weights_dtype": "int8", "modules_to_not_convert": ["norm"]}) + + def test_torchao_float8_passes_an_aobase_config_instance(self): + import sys + from types import ModuleType + + received = {} + + class FakeFloat8WeightOnlyConfig: + pass + + class FakeTorchAoConfig: + def __init__(self, **kwargs): + received.update(kwargs) + + fake_quantization = ModuleType("torchao.quantization") + fake_quantization.Float8WeightOnlyConfig = FakeFloat8WeightOnlyConfig + fake_torchao = ModuleType("torchao") + fake_torchao.quantization = fake_quantization + + with ( + patch("diffusers.TorchAoConfig", FakeTorchAoConfig), + patch.dict( + sys.modules, + {"torchao": fake_torchao, "torchao.quantization": fake_quantization}, + ), + ): + quant_config_for("torchao_float8", None) + + self.assertIsInstance(received["quant_type"], FakeFloat8WeightOnlyConfig) + + def test_torchao_float8_explains_when_optional_dependency_is_missing(self): + import builtins + + real_import = builtins.__import__ + + def import_without_torchao(name, *args, **kwargs): + if name == "torchao.quantization": + raise ImportError("torchao unavailable") + return real_import(name, *args, **kwargs) + + with ( + patch("diffusers.TorchAoConfig", object), + patch("builtins.__import__", side_effect=import_without_torchao), + ): + with self.assertRaisesRegex(RuntimeError, "optional torchao quantization package"): + quant_config_for("torchao_float8", None) + def test_inherited_nodes_are_registered_with_their_live_contracts(self): expected_inputs = { "Edit": {"pipeline", "image"}, @@ -23,6 +136,13 @@ def test_inherited_nodes_are_registered_with_their_live_contracts(self): self.assertTrue(required_inputs.issubset(entry["params"])) self.assertEqual(entry["params"]["images"]["display"], "output") + def test_graph_contract_marks_mode_independent_image_inputs_as_required(self): + self.assertTrue(Edit.params["pipeline"]["required"]) + self.assertTrue(Edit.params["image"]["required"]) + self.assertTrue(Inpaint.params["mask_image"]["required"]) + self.assertTrue(ControlGenerate.params["control_image"]["required"]) + self.assertTrue(LoadAdapter.params["pipeline"]["required"]) + def test_registered_classes_can_be_constructed(self): for node_class in (Edit, Inpaint, ControlGenerate): with self.subTest(node=node_class.__name__): @@ -30,6 +150,824 @@ def test_registered_classes_can_be_constructed(self): self.assertEqual(node.node_id, "registry-probe") self.assertTrue(node.resizable) + def test_unsupported_mode_fails_before_pipeline_resolution(self): + node = LoadPipeline("mode-probe") + with self.assertRaisesRegex(ValueError, "does not support outpaint"): + node.execute(model_id="example/model", pipeline_class="FluxPipeline", mode="outpaint") + + def test_flux2_klein_supports_generation_and_reference_edits(self): + node = LoadPipeline("flux2-mode-probe") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + + class FakePipeline: + @classmethod + def from_pretrained(cls, _repo, **_kwargs): + return cls() + + with ( + patch("modules.DiffusersImage.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersImage.main.apply_pipeline_offload"), + ): + for mode in ("text_to_image", "edit_image", "multi_image_reference_edit"): + node.execute( + model_id="black-forest-labs/FLUX.2-klein-4B", + pipeline_class="Flux2KleinPipeline", + mode=mode, + auto_offload=False, + offload_mode="none", + ) + + def test_flux2_klein_reuses_one_resident_loader_when_only_mode_changes(self): + loaded = [] + + class FakePipeline: + @classmethod + def from_pretrained(cls, repo, **_kwargs): + loaded.append(repo) + return cls() + + node = LoadPipeline("flux2-cache-probe") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + common = { + "model_id": "black-forest-labs/FLUX.2-klein-4B", + "pipeline_class": "Flux2KleinPipeline", + "auto_offload": False, + "offload_mode": "none", + } + with ( + patch("modules.DiffusersImage.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersImage.main.apply_pipeline_offload"), + ): + first = node(mode="text_to_image", **common) + second = node(mode="edit_image", **common) + + self.assertIs(first["pipeline"], second["pipeline"]) + self.assertEqual(loaded, ["black-forest-labs/FLUX.2-klein-4B"]) + self.assertEqual(node.params["mode"], "edit_image") + self.assertFalse(node._has_changed) + + def test_cross_workflow_loader_reuse_removes_residual_lora(self): + unloads = [] + pipeline = type("Pipeline", (), {"unload_lora_weights": lambda _self: unloads.append(True)})() + node = LoadPipeline("image-loader-reuse") + node.output["pipeline"] = pipeline + + node.prepare_for_workflow_reuse() + + self.assertEqual(unloads, [True]) + self.assertIs(node.output["pipeline"], pipeline) + + def test_two_repositories_use_the_same_generic_loader_contract(self): + loaded = [] + + class FakePipeline: + @classmethod + def from_pretrained(cls, repo, **kwargs): + loaded.append(repo) + return cls() + + node = LoadPipeline("repo-switch-probe") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("modules.DiffusersImage.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersImage.main.apply_pipeline_offload"), + ): + for repo in ("org/flux-compatible-a", "org/flux-compatible-b"): + result = node.execute( + model_id=repo, + pipeline_class="FluxPipeline", + mode="text_to_image", + auto_offload=False, + offload_mode="none", + ) + self.assertEqual(result["resolved_artifact"], repo) + + self.assertEqual(loaded, ["org/flux-compatible-a", "org/flux-compatible-b"]) + + def test_execution_recipe_controls_load_placement_attention_and_offload(self): + loaded = {} + offload = {} + + class FakeTransformer: + def __init__(self): + self.backends = [] + + def set_attention_backend(self, backend): + self.backends.append(backend) + + class FakePipeline: + def __init__(self): + self.transformer = FakeTransformer() + + @classmethod + def from_pretrained(cls, repo, **kwargs): + loaded.update({"repo": repo, "kwargs": kwargs}) + return cls() + + def capture_offload(_pipeline, **kwargs): + offload.update(kwargs) + + quant_config = object() + recipe = { + "quantization_config": quant_config, + "device_map": {"transformer": 0, "text_encoder_2": "cpu"}, + "max_memory": {0: "16GiB", "cpu": "48GiB"}, + "offload_mode": "group_cpu", + "device": "cuda:0", + "attention_backend": "native", + "attention_components": ["transformer"], + "vae_slicing": False, + "vae_tiling": False, + } + + node = LoadPipeline("recipe-loader-probe") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("modules.DiffusersImage.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersImage.main.apply_pipeline_offload", side_effect=capture_offload), + ): + with self.assertRaisesRegex(ValueError, "PipelineQuantizationConfig"): + node.execute( + model_id="org/runtime-recipe-model", + pipeline_class="FluxPipeline", + mode="text_to_image", + execution_recipe=recipe, + ) + recipe["quantization_config"] = None + result = node.execute( + model_id="org/runtime-recipe-model", + pipeline_class="FluxPipeline", + mode="text_to_image", + execution_recipe=recipe, + ) + + self.assertNotIn("quantization_config", loaded["kwargs"]) + self.assertEqual(loaded["kwargs"]["device_map"], recipe["device_map"]) + self.assertEqual(loaded["kwargs"]["max_memory"], recipe["max_memory"]) + self.assertEqual(offload["mode"], "group_cpu") + self.assertEqual(offload["device"], "cuda:0") + self.assertEqual(result["pipeline"].transformer.backends, ["native"]) + + def test_execution_recipe_applies_cache_and_compile_to_direct_image_pipeline(self): + applied = {} + + class FakePipeline: + @classmethod + def from_pretrained(cls, _repo, **_kwargs): + return cls() + + recipe = { + "offload_mode": "none", + "device": "cuda:0", + "denoiser_cache": "first_block", + "cache_threshold": 0.08, + "regional_compile": True, + "compile_components": ["transformer"], + } + node = LoadPipeline("direct-image-runtime-recipe") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("modules.DiffusersImage.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersImage.main.apply_pipeline_offload"), + patch( + "modules.DiffusersRuntime.main.apply_execution_recipe_to_pipeline", + side_effect=lambda pipeline, runtime_recipe: applied.update( + {"pipeline": pipeline, "recipe": runtime_recipe} + ) + or {"cache": {"requested": runtime_recipe["denoiser_cache"]}}, + ), + ): + result = node.execute( + model_id="org/direct-image-model", + pipeline_class="FluxPipeline", + mode="text_to_image", + execution_recipe=recipe, + enable_vae_slicing=False, + enable_vae_tiling=False, + ) + + self.assertIs(applied["pipeline"], result["pipeline"]) + self.assertEqual(applied["recipe"]["denoiser_cache"], "first_block") + self.assertEqual(applied["recipe"]["cache_threshold"], 0.08) + self.assertTrue(applied["recipe"]["regional_compile"]) + self.assertFalse(applied["recipe"]["vae_slicing"]) + self.assertFalse(applied["recipe"]["vae_tiling"]) + self.assertEqual( + result["pipeline"]._modiff_runtime_config, + {"cache": {"requested": "first_block"}}, + ) + + def test_direct_image_defaults_still_apply_vae_memory_without_a_recipe_node(self): + captured = {} + + class FakePipeline: + @classmethod + def from_pretrained(cls, _repo, **_kwargs): + return cls() + + node = LoadPipeline("direct-image-default-runtime") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("modules.DiffusersImage.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersImage.main.apply_pipeline_offload"), + patch( + "modules.DiffusersRuntime.main.apply_execution_recipe_to_pipeline", + side_effect=lambda _pipeline, runtime_recipe: captured.update(runtime_recipe) or {}, + ), + ): + node.execute( + model_id="org/direct-image-model", + pipeline_class="FluxPipeline", + mode="text_to_image", + auto_offload=False, + offload_mode="none", + enable_vae_slicing=False, + enable_vae_tiling=True, + ) + + self.assertFalse(captured["vae_slicing"]) + self.assertTrue(captured["vae_tiling"]) + + def test_direct_device_map_streams_a_no_offload_pipeline_to_cuda_during_load(self): + loaded = {} + offload = {} + + class FakePipeline: + @classmethod + def from_pretrained(cls, repo, **kwargs): + loaded.update({"repo": repo, "kwargs": kwargs}) + return cls() + + node = LoadPipeline("direct-device-map-probe") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("modules.DiffusersImage.main.pipeline_class_from_name", return_value=FakePipeline), + patch( + "modules.DiffusersImage.main.apply_pipeline_offload", + side_effect=lambda _pipeline, **kwargs: offload.update(kwargs), + ), + ): + node.execute( + model_id="org/native-model", + pipeline_class="FluxPipeline", + mode="text_to_image", + device="cuda:0", + device_map="cuda", + auto_offload=False, + offload_mode="none", + ) + + self.assertEqual(loaded["kwargs"]["device_map"], "cuda") + self.assertEqual(offload["mode"], "none") + self.assertEqual(offload["device"], "cuda:0") + + def test_direct_device_map_overrides_a_neutral_recipe_default(self): + loaded = {} + + class FakePipeline: + @classmethod + def from_pretrained(cls, _repo, **kwargs): + loaded.update(kwargs) + return cls() + + node = LoadPipeline("direct-map-precedence-probe") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("modules.DiffusersImage.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersImage.main.apply_pipeline_offload"), + ): + node.execute( + model_id="org/native-model", + pipeline_class="FluxPipeline", + mode="text_to_image", + execution_recipe={"device_map": "none", "offload_mode": "none", "device": "cuda:0"}, + device_map="cuda", + ) + + self.assertEqual(loaded["device_map"], "cuda") + + def test_flux_redux_loader_composes_prior_with_app_cached_flux_base(self): + loaded = [] + + class FakePrior: + @classmethod + def from_pretrained(cls, repo, **kwargs): + loaded.append(("prior", repo, kwargs)) + return cls() + + class FakeBase: + text_encoder = "clip" + text_encoder_2 = "t5" + tokenizer = "clip-tokenizer" + tokenizer_2 = "t5-tokenizer" + + @classmethod + def from_pretrained(cls, repo, **kwargs): + loaded.append(("base", repo, kwargs)) + return cls() + + def register_modules(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + + node = LoadPipeline("redux-loader-probe") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("diffusers.FluxPriorReduxPipeline", FakePrior), + patch("diffusers.FluxPipeline", FakeBase), + patch("modules.DiffusersImage.main.apply_pipeline_offload"), + ): + result = node.execute( + model_id="black-forest-labs/FLUX.1-Redux-dev", + pipeline_class="FluxReduxPipeline", + mode="edit_image", + revision="redux-commit", + auto_offload=False, + offload_mode="none", + ) + + self.assertIsInstance(result["pipeline"], FluxReduxPipelineBundle) + self.assertEqual(loaded[0][0:2], ("base", FLUX_DEV_REPO)) + self.assertEqual(loaded[0][2]["revision"], "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21") + self.assertEqual(loaded[1][0:2], ("prior", "black-forest-labs/FLUX.1-Redux-dev")) + self.assertEqual(loaded[1][2]["revision"], "redux-commit") + self.assertEqual(loaded[1][2]["text_encoder"], "clip") + self.assertEqual(loaded[1][2]["text_encoder_2"], "t5") + self.assertEqual(loaded[1][2]["tokenizer"], "clip-tokenizer") + self.assertEqual(loaded[1][2]["tokenizer_2"], "t5-tokenizer") + self.assertTrue(loaded[0][2]["local_files_only"]) + self.assertTrue(loaded[1][2]["local_files_only"]) + self.assertIsNone(result["pipeline"].base.text_encoder) + self.assertIsNone(result["pipeline"].base.text_encoder_2) + + def test_curated_image_loader_uses_catalog_pin_but_preserves_explicit_revision(self): + loaded = [] + + class FakePipeline: + @classmethod + def from_pretrained(cls, repo, **kwargs): + loaded.append((repo, kwargs)) + return cls() + + node = LoadPipeline("image-revision-probe") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("modules.DiffusersImage.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersImage.main.apply_pipeline_offload"), + ): + node.execute( + model_id="black-forest-labs/FLUX.1-schnell", + pipeline_class="FluxPipeline", + mode="text_to_image", + auto_offload=False, + offload_mode="none", + ) + node.execute( + model_id="black-forest-labs/FLUX.1-schnell", + pipeline_class="FluxPipeline", + mode="text_to_image", + revision="reviewed-user-revision", + auto_offload=False, + offload_mode="none", + ) + + self.assertEqual(loaded[0][1]["revision"], "741f7c3ce8b383c54771c7003378a50191e9efe9") + self.assertEqual(loaded[1][1]["revision"], "reviewed-user-revision") + + def test_flux_redux_bundle_delegates_multiple_reference_fusion_to_diffusers(self): + import torch + + calls = {} + + class PriorOutput: + # FluxPriorReduxPipeline already reduces the reference batch to + # one weighted conditioning sample. + prompt_embeds = torch.tensor([[[3.0, 5.0]]]) + pooled_prompt_embeds = torch.tensor([[4.0, 6.0]]) + + class FakePrior: + def __call__(self, **kwargs): + calls["prior"] = kwargs + return PriorOutput() + + class FakeBase: + device = "cpu" + + def __call__(self, **kwargs): + calls["base"] = kwargs + return type("Result", (), {"images": [Image.new("RGB", (32, 32), "white")]})() + + bundle = FluxReduxPipelineBundle(FakePrior(), FakeBase()) + references = [Image.new("RGB", (16, 16), "red"), Image.new("RGB", (16, 16), "blue")] + result = Edit("redux-edit-probe").execute( + pipeline=bundle, + image=references, + prompt="combine material and silhouette", + width=32, + height=32, + num_inference_steps=4, + guidance_scale=2.5, + ) + + self.assertIs(calls["prior"]["image"], references) + self.assertEqual(calls["prior"]["prompt"], "combine material and silhouette") + self.assertEqual(calls["prior"]["prompt_embeds_scale"], [1.0, 1.0]) + self.assertEqual(calls["prior"]["pooled_prompt_embeds_scale"], [1.0, 1.0]) + torch.testing.assert_close(calls["base"]["prompt_embeds"], torch.tensor([[[3.0, 5.0]]])) + torch.testing.assert_close(calls["base"]["pooled_prompt_embeds"], torch.tensor([[4.0, 6.0]])) + self.assertEqual(calls["base"]["width"], 32) + self.assertEqual(calls["base"]["height"], 32) + self.assertEqual(result["images"][0].size, (32, 32)) + + def test_flux_redux_bundle_scales_secondary_references_through_generic_conditioning(self): + import torch + + calls = {} + + class PriorOutput: + prompt_embeds = torch.tensor([[[1.0, 3.0]]]) + pooled_prompt_embeds = torch.tensor([[2.0, 4.0]]) + + class FakePrior: + def __call__(self, **kwargs): + calls["prior"] = kwargs + return PriorOutput() + + class FakeBase: + device = "cpu" + + def __call__(self, **kwargs): + return type("Result", (), {"images": [Image.new("RGB", (32, 32), "white")]})() + + bundle = FluxReduxPipelineBundle(FakePrior(), FakeBase()) + references = [Image.new("RGB", (16, 16), "red"), Image.new("RGB", (16, 16), "blue")] + Edit("redux-weight-probe").execute( + pipeline=bundle, + image=references, + prompt="architecture with a restrained material cue", + width=32, + height=32, + num_inference_steps=4, + guidance_scale=2.5, + reference_strength=0.2, + ) + + self.assertEqual(calls["prior"]["prompt_embeds_scale"], [1.0, 0.2]) + self.assertEqual(calls["prior"]["pooled_prompt_embeds_scale"], [1.0, 0.2]) + + def test_flux_kontext_stitches_multiple_references_through_generic_edit(self): + received = {} + + class FakeKontextPipeline: + _execution_device = "cpu" + _modiff_image_adapter = IMAGE_PIPELINE_ADAPTERS["FluxKontextPipeline"] + + def __call__(self, **kwargs): + received.update(kwargs) + return type("Result", (), {"images": [Image.new("RGB", (32, 32), "white")]})() + + references = [Image.new("RGB", (16, 16), "red"), Image.new("RGB", (8, 16), "blue")] + result = Edit("kontext-multi-probe").execute( + pipeline=FakeKontextPipeline(), + image=references, + prompt="use first for identity and second for style", + width=32, + height=32, + num_inference_steps=2, + ) + + self.assertIsInstance(received["image"], Image.Image) + self.assertEqual(received["image"].size, (24, 16)) + self.assertEqual(received["image"].getpixel((0, 0)), (255, 0, 0)) + self.assertEqual(received["image"].getpixel((23, 0)), (0, 0, 255)) + self.assertEqual(result["images"][0].size, (32, 32)) + + def test_qwen_inpaint_uses_generic_guidance_and_crop_aliases(self): + received = {} + + class FakeResult: + images = [Image.new("RGB", (16, 16), "white")] + + class FakeQwenPipeline: + _execution_device = "cpu" + + def __call__( + self, + *, + image, + mask_image, + prompt, + num_inference_steps, + generator, + output_type, + return_dict, + true_cfg_scale, + padding_mask_crop, + strength, + ): + received.update( + true_cfg_scale=true_cfg_scale, + padding_mask_crop=padding_mask_crop, + strength=strength, + ) + return FakeResult() + + node = Inpaint("qwen-generic-probe") + result = node.execute( + pipeline=FakeQwenPipeline(), + image=Image.new("RGB", (16, 16), "black"), + mask_image=Image.new("L", (16, 16), "white"), + prompt="replace the object", + num_inference_steps=2, + guidance_scale=4.0, + padding_mask_crop=128, + strength=1.0, + output_type="pil", + ) + + self.assertEqual(received, {"true_cfg_scale": 4.0, "padding_mask_crop": 128, "strength": 1.0}) + self.assertEqual(result["images"][0].size, (16, 16)) + + def test_zero_padding_mask_crop_maps_to_diffusers_no_crop(self): + received = {} + + class Pipeline: + def __call__(self, padding_mask_crop=None, **kwargs): + return None + + adapter = IMAGE_PIPELINE_ADAPTERS["QwenImageEditInpaintPipeline"] + adapter.apply_generation_parameters( + Pipeline(), + {"padding_mask_crop": 0}, + received, + ) + + self.assertNotIn("padding_mask_crop", received) + + def test_qwen_generic_loader_uses_the_official_default_repository(self): + loaded = [] + + class FakePipeline: + @classmethod + def from_pretrained(cls, repo, **_kwargs): + loaded.append(repo) + return cls() + + node = LoadPipeline("qwen-loader-probe") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("modules.DiffusersImage.main.pipeline_class_from_name", return_value=FakePipeline), + patch("modules.DiffusersImage.main.apply_pipeline_offload"), + ): + node.execute( + pipeline_class="QwenImageEditInpaintPipeline", + mode="inpaint", + auto_offload=False, + offload_mode="none", + ) + + self.assertEqual(loaded, ["Qwen/Qwen-Image-Edit"]) + + def test_generic_inpaint_preserves_every_black_mask_pixel(self): + self.assertEqual(Inpaint.params["output_type"]["options"], ["pil"]) + + class FakeResult: + images = [Image.new("RGB", (4, 2), (200, 210, 220))] + + class FakePipeline: + _execution_device = "cpu" + + def __call__(self, **_kwargs): + return FakeResult() + + source = Image.new("RGB", (4, 2), (10, 20, 30)) + mask = Image.new("L", (4, 2), 0) + for x in (2, 3): + for y in (0, 1): + mask.putpixel((x, y), 255) + + result = Inpaint("mask-contract-probe").execute( + pipeline=FakePipeline(), + image=source, + mask_image=mask, + prompt="replace", + num_inference_steps=1, + output_type="pil", + ) + + self.assertEqual(result["images"][0].getpixel((0, 0)), (10, 20, 30)) + self.assertEqual(result["images"][0].getpixel((3, 1)), (200, 210, 220)) + + def test_generic_step_callback_propagates_interrupt_to_pipeline(self): + class FakePipeline: + _interrupt = False + _num_timesteps = 4 + + def __call__(self, *, callback_on_step_end=None, callback_on_step_end_tensor_inputs=None): + pass + + node = Inpaint("interrupt-probe") + node._interrupt = True + progress = [] + node.progress = lambda *args, **kwargs: progress.append((args, kwargs)) + pipeline = FakePipeline() + call_kwargs = {} + + add_progress_callback(node, pipeline, call_kwargs, 4) + self.assertEqual(progress[0][0], (0,)) + self.assertEqual(progress[0][1]["phase"], "denoising") + self.assertEqual(progress[0][1]["current_step"], 0) + self.assertEqual(progress[0][1]["total_steps"], 4) + with self.assertRaisesRegex(InterruptedError, "interrupted by the user"): + call_kwargs["callback_on_step_end"](pipeline, 0, 1, {}) + + self.assertTrue(pipeline._interrupt) + + def test_generic_execution_exposes_active_pipeline_during_inference(self): + node = Inpaint("active-pipeline-probe") + observed = [] + + class FakeResult: + images = [Image.new("RGB", (16, 16), "white")] + + class FakePipeline: + _execution_device = "cpu" + + def __call__(self, **_kwargs): + observed.append(node._active_pipeline is self) + return FakeResult() + + node.execute( + pipeline=FakePipeline(), + image=Image.new("RGB", (16, 16), "black"), + mask_image=Image.new("L", (16, 16), "white"), + prompt="replace", + num_inference_steps=1, + ) + + self.assertEqual(observed, [True]) + self.assertIsNone(node._active_pipeline) + + def test_adapter_uses_only_the_app_managed_cached_weight(self): + calls = [] + + class FakePipeline: + def load_lora_weights(self, path, **kwargs): + calls.append((path, kwargs)) + + with patch("utils.huggingface.cached_file_path", return_value="/cache/revision/adapter.safetensors"): + LoadAdapter("adapter-probe").execute( + pipeline=FakePipeline(), + adapter_path={"source": "hub", "value": "unit/adapter"}, + weight_name="adapter.safetensors", + adapter_name="gallery", + scale=0.8, + ) + + self.assertEqual(calls[0][0], "/cache/revision") + self.assertEqual(calls[0][1]["weight_name"], "adapter.safetensors") + + def test_adapter_missing_from_app_cache_fails_before_pipeline_load(self): + class FakePipeline: + def load_lora_weights(self, *_args, **_kwargs): + raise AssertionError("must not download or load") + + with patch("utils.huggingface.cached_file_path", return_value=False): + with self.assertRaisesRegex(FileNotFoundError, "Model Manager"): + LoadAdapter("missing-adapter-probe").execute( + pipeline=FakePipeline(), + adapter_path={"source": "hub", "value": "unit/adapter"}, + weight_name="adapter.safetensors", + ) + + def test_adapter_verifies_pinned_hash_and_replaces_previous_pipeline_adapters(self): + events = [] + + class FakePipeline: + def unload_lora_weights(self): + events.append("unload") + + def load_lora_weights(self, path, **kwargs): + events.append(("load", path, kwargs)) + + def set_adapters(self, names, weights): + events.append(("activate", names, weights)) + + with tempfile.TemporaryDirectory() as directory: + adapter_file = Path(directory) / "adapter.safetensors" + adapter_file.write_bytes(b"pinned adapter bytes") + expected = hashlib.sha256(adapter_file.read_bytes()).hexdigest() + with patch("utils.huggingface.cached_file_path", return_value=str(adapter_file)): + LoadAdapter("verified-adapter-probe").execute( + pipeline=FakePipeline(), + adapter_path={"source": "hub", "value": "unit/adapter"}, + weight_name=adapter_file.name, + expected_sha256=expected, + adapter_name="theme", + scale=0.75, + ) + + self.assertEqual(events[0], "unload") + self.assertEqual(events[1][0], "load") + self.assertEqual(events[2], ("activate", ["theme"], [0.75])) + + def test_chained_adapters_preserve_prior_adapter_names_and_scales(self): + events = [] + + class FakePipeline: + def unload_lora_weights(self): + events.append("unload") + + def load_lora_weights(self, path, **kwargs): + events.append(("load", path, kwargs)) + + def set_adapters(self, names, weights): + events.append(("activate", names, weights)) + + pipeline = FakePipeline() + with tempfile.TemporaryDirectory() as directory: + first = Path(directory) / "first.safetensors" + second = Path(directory) / "second.safetensors" + first.write_bytes(b"first") + second.write_bytes(b"second") + with patch( + "utils.huggingface.cached_file_path", + side_effect=[str(first), str(second)], + ): + LoadAdapter("first-adapter").execute( + pipeline=pipeline, + adapter_path={"source": "hub", "value": "unit/first"}, + weight_name=first.name, + adapter_name="cinematic", + scale=0.8, + replace_existing=True, + ) + LoadAdapter("second-adapter").execute( + pipeline=pipeline, + adapter_path={"source": "hub", "value": "unit/second"}, + weight_name=second.name, + adapter_name="render_3d", + scale=0.18, + replace_existing=False, + ) + + self.assertEqual(events.count("unload"), 1) + self.assertEqual(events[-1], ("activate", ["cinematic", "render_3d"], [0.8, 0.18])) + self.assertEqual(pipeline._modiff_adapter_scales, {"cinematic": 0.8, "render_3d": 0.18}) + + def test_adapter_scale_zero_remains_zero(self): + activations = [] + + class FakePipeline: + def load_lora_weights(self, *_args, **_kwargs): + pass + + def set_adapters(self, names, weights): + activations.append((names, weights)) + + with patch("utils.huggingface.cached_file_path", return_value="/cache/adapter.safetensors"): + LoadAdapter("zero-scale-adapter").execute( + pipeline=FakePipeline(), + adapter_path={"source": "hub", "value": "unit/adapter"}, + weight_name="adapter.safetensors", + adapter_name="optional", + scale=0, + replace_existing=False, + ) + + self.assertEqual(activations, [(["optional"], [0.0])]) + + def test_adapter_hash_mismatch_does_not_mutate_pipeline(self): + class FakePipeline: + def unload_lora_weights(self): + raise AssertionError("hash validation must happen before pipeline mutation") + + def load_lora_weights(self, *_args, **_kwargs): + raise AssertionError("hash validation must happen before adapter loading") + + with tempfile.TemporaryDirectory() as directory: + adapter_file = Path(directory) / "adapter.safetensors" + adapter_file.write_bytes(b"unexpected bytes") + with patch("utils.huggingface.cached_file_path", return_value=str(adapter_file)): + with self.assertRaisesRegex(ValueError, "pinned SHA-256"): + LoadAdapter("invalid-adapter-probe").execute( + pipeline=FakePipeline(), + adapter_path={"source": "hub", "value": "unit/adapter"}, + weight_name=adapter_file.name, + expected_sha256="0" * 64, + ) + class FakeRequest: match_info = {} diff --git a/tests/test_diffusers_offload.py b/tests/test_diffusers_offload.py index 6cdf0be..2c50835 100644 --- a/tests/test_diffusers_offload.py +++ b/tests/test_diffusers_offload.py @@ -15,15 +15,30 @@ OFFLOAD_MODE_NONE, OFFLOAD_MODE_SEQUENTIAL_CPU, apply_component_group_offload, + apply_model_offload, apply_pipeline_offload, + configure_components_manager_offload, + normalize_execution_device, normalize_offload_mode, + reset_pipeline_device_map_for_runtime, + supports_accelerator_cpu_offload, ) from modiff.diffusers_profiles import QWEN_IMAGE_2512_PREQUANTIZED_REPO, public_execution_profiles from modules.ModularDiffusers.denoise import embeddings_are_missing, embeddings_missing_error from modules.ModularDiffusers.embeddings import extract_prompt_embeddings from modules.ModularDiffusers.loaders import normalize_quant_config_input -from modules.ModularDiffusers.loaders import RequiredComponentLoadError, load_components_strict -from modules.QwenImage.main import build_qwen_pipeline_quantization_config, coerce_pipeline_quantization_config +from modules.ModularDiffusers.loaders import ( + AutoModelLoader, + ModelsLoader, + RequiredComponentLoadError, + component_reuse_compatible, + load_components_strict, + place_pipeline_components, + record_pipeline_component_runtime_policy, + reusable_standalone_component, + should_incrementally_group_offload, +) +from modules.DiffusersImage.main import build_qwen_pipeline_quantization_config, coerce_pipeline_quantization_config class FakePipelineState: @@ -59,6 +74,18 @@ def enable_sequential_cpu_offload(self, device=None): self.calls.append(("sequential_cpu", str(device))) +class FakeDeviceMappedPipeline(FakePipeline): + hf_device_map = {"transformer": 0, "text_encoder": "cpu"} + + def reset_device_map(self): + self.calls.append(("reset_device_map", None)) + self.hf_device_map = None + + +class FakeResidentDeviceMappedPipeline(FakeDeviceMappedPipeline): + hf_device_map = {"transformer": 0, "text_encoder": "cuda:0"} + + class FakeCudaRuntime: def __init__(self, *, free_bytes=14 * 1024 ** 3, total_bytes=16 * 1024 ** 3): self.free_bytes = free_bytes @@ -111,6 +138,54 @@ def register_components(self, **components): class DiffusersOffloadSmokeTest(unittest.TestCase): + def test_generic_component_filter_does_not_assign_an_uninstalled_repository(self): + node = AutoModelLoader("generic-component-filter") + + with patch.object(node, "set_field_params") as set_field_params: + node.set_filters({"model_type": "controlnet"}, None) + + model_call = next(call for call in set_field_params.call_args_list if call.args[0] == "model_id") + params = model_call.args[1] + self.assertEqual( + params["fieldOptions"]["filter"]["hub"]["className"], + ["ControlNetModel", "QwenImageControlNetModel", "FluxControlNetModel"], + ) + self.assertNotIn("value", params) + self.assertNotIn("default", params) + + def test_generic_pipeline_filter_preserves_selection_until_user_chooses_an_installed_repository(self): + node = ModelsLoader("generic-pipeline-filter") + node.model_types_loaded = True + + with patch.object(node, "set_field_params") as set_field_params: + node.set_filters({"model_type": "QwenImageModularPipeline"}, None) + + repo_call = next(call for call in set_field_params.call_args_list if call.args[0] == "repo_id") + params = repo_call.args[1] + self.assertEqual( + params["fieldOptions"]["filter"]["hub"]["className"], + ["QwenImageModularPipeline"], + ) + self.assertNotIn("value", params) + self.assertNotIn("default", params) + + def test_auto_model_loader_rejects_pipeline_class_before_weight_load(self): + node = AutoModelLoader("invalid-component-loader") + + with patch("modules.ModularDiffusers.loaders.ComponentSpec.load") as load: + with self.assertRaisesRegex(ValueError, "requires a component type"): + node.execute( + model_type="QwenImageModularPipeline", + model_id={"source": "hub", "value": "InstantX/Qwen-Image-ControlNet-Union"}, + dtype=torch.bfloat16, + trust_remote_code=False, + device="cuda:0", + auto_offload=True, + offload_mode=OFFLOAD_MODE_MODEL_CPU, + ) + + load.assert_not_called() + def tearDown(self): shutil.rmtree(Path("data") / "offload" / "diffusers" / "smoke-node", ignore_errors=True) @@ -118,6 +193,22 @@ def test_normalizes_legacy_auto_cpu(self): self.assertEqual(normalize_offload_mode("auto_cpu", auto_offload=True), OFFLOAD_MODE_MODEL_CPU) self.assertEqual(normalize_offload_mode(OFFLOAD_MODE_GROUP_CPU, auto_offload=True), OFFLOAD_MODE_GROUP_CPU) self.assertEqual(normalize_offload_mode(OFFLOAD_MODE_GROUP_CPU, auto_offload=False), OFFLOAD_MODE_NONE) + self.assertEqual( + normalize_offload_mode(OFFLOAD_MODE_MODEL_CPU, auto_offload=True, device="cpu:0"), + OFFLOAD_MODE_NONE, + ) + self.assertEqual( + normalize_offload_mode(OFFLOAD_MODE_GROUP_CPU, auto_offload=True, device="mps"), + OFFLOAD_MODE_NONE, + ) + self.assertEqual( + normalize_offload_mode(OFFLOAD_MODE_MODEL_CPU, auto_offload=True, device="cuda"), + OFFLOAD_MODE_MODEL_CPU, + ) + self.assertEqual(str(normalize_execution_device("cuda")), "cuda:0") + self.assertTrue(supports_accelerator_cpu_offload("cuda:1")) + self.assertFalse(supports_accelerator_cpu_offload("cpu")) + self.assertFalse(supports_accelerator_cpu_offload("mps")) def test_pipeline_model_and_sequential_cpu_offload(self): model_pipeline = FakePipeline() @@ -140,31 +231,346 @@ def test_pipeline_model_and_sequential_cpu_offload(self): self.assertTrue(sequential_result.applied) self.assertEqual(sequential_pipeline.calls, [("sequential_cpu", "cuda:0")]) - def test_group_cpu_and_disk_component_offload(self): - group_pipeline = FakePipeline() - group_result = apply_component_group_offload( - group_pipeline, - component_names=["transformer"], + def test_pipeline_cpu_and_mps_execution_never_install_cpu_offload_hooks(self): + for device in ("cpu:0", "mps"): + for mode in ( + OFFLOAD_MODE_MODEL_CPU, + OFFLOAD_MODE_SEQUENTIAL_CPU, + OFFLOAD_MODE_GROUP_CPU, + OFFLOAD_MODE_GROUP_DISK, + ): + with self.subTest(device=device, mode=mode): + pipeline = FakePipeline() + result = apply_pipeline_offload( + pipeline, + mode=mode, + device=device, + node_id="smoke-node", + ) + + self.assertEqual(result.mode, OFFLOAD_MODE_NONE) + self.assertEqual(result.method, "to_device") + self.assertEqual(pipeline.calls, [("to", device)]) + + def test_standalone_model_cpu_execution_never_installs_group_hooks(self): + model = torch.nn.Linear(2, 2) + with patch( + "diffusers.hooks.apply_group_offloading", + side_effect=AssertionError("CPU execution must not install a group-offload hook"), + ): + result = apply_model_offload( + model, + component_name="transformer", + mode=OFFLOAD_MODE_MODEL_CPU, + device="cpu:0", + node_id="smoke-node", + ) + + self.assertEqual(result.mode, OFFLOAD_MODE_NONE) + self.assertEqual(result.method, "to_device") + self.assertEqual(model.weight.device.type, "cpu") + + def test_real_components_manager_skips_auto_offload_for_cpu(self): + from diffusers import ComponentsManager + + manager = ComponentsManager() + result = configure_components_manager_offload( + manager, + mode=OFFLOAD_MODE_MODEL_CPU, + device="cpu:0", + ) + + self.assertEqual(result.mode, OFFLOAD_MODE_NONE) + self.assertEqual(result.method, "components_manager_no_offload") + self.assertFalse(manager._auto_offload_enabled) + + def test_models_loader_cpu_contract_reaches_model_load_without_enabling_auto_offload(self): + class StopAtModelLoad(RuntimeError): + pass + + class CpuComponentsManager: + _auto_offload_enabled = False + _auto_offload_device = None + + def enable_auto_cpu_offload(self, **_kwargs): + raise NotImplementedError( + "`enable_auto_cpu_offload()` relies on the `mem_get_info()` method. " + "It's not implemented for cpu." + ) + + def disable_auto_cpu_offload(self): + raise AssertionError("An inactive manager should not need disabling.") + + def _lookup_ids(self, **_kwargs): + return [] + + def remove_from_collection(self, *_args, **_kwargs): + return None + + manager = CpuComponentsManager() + node = ModelsLoader("cpu-loader") + with ( + patch("modules.ModularDiffusers.loaders.components", manager), + patch( + "modules.ModularDiffusers.loaders.ModularPipeline.from_pretrained", + side_effect=StopAtModelLoad("model load reached"), + ), + ): + with self.assertRaisesRegex(StopAtModelLoad, "model load reached"): + node.execute( + model_type="QwenImagePipeline", + repo_id={"source": "hub", "value": "Qwen/Qwen-Image-2512"}, + device="cpu:0", + dtype=torch.float32, + auto_offload=True, + offload_mode=OFFLOAD_MODE_MODEL_CPU, + ) + + self.assertEqual(node._loader_diagnostics["normalized_offload_mode"], OFFLOAD_MODE_NONE) + + def test_resident_modular_components_report_each_accelerator_copy(self): + class RecordingModule(torch.nn.Module): + def __init__(self, name): + super().__init__() + self.name = name + self.devices = [] + + def to(self, device): + self.devices.append(device) + return self + + text_encoder = RecordingModule("text_encoder") + transformer = RecordingModule("transformer") + pipeline = type( + "ResidentPipeline", + (), + { + "components": { + "tokenizer": object(), + "text_encoder": text_encoder, + "transformer": transformer, + } + }, + )() + progress = [] + + names = place_pipeline_components( + pipeline, + "cuda:0", + lambda name, index, total: progress.append((name, index, total)), + ) + + self.assertEqual(names, ["text_encoder", "transformer"]) + self.assertEqual(progress, [("text_encoder", 1, 2), ("transformer", 2, 2)]) + self.assertEqual(text_encoder.devices, ["cuda:0"]) + self.assertEqual(transformer.devices, ["cuda:0"]) + + def test_standalone_component_reuse_requires_matching_identity_dtype_and_runtime_policy(self): + class ResidentManager: + def __init__(self, model): + self.model = model + + def _lookup_ids(self, **_kwargs): + return {"controlnet_1"} + + def get_one(self, *, component_id): + self.last_component_id = component_id + return self.model + + model = torch.nn.Linear(2, 2, dtype=torch.bfloat16) + model._diffusers_load_id = "repo/controlnet|null|null|null" + model._modiff_offload_mode = OFFLOAD_MODE_NONE + model._modiff_execution_device = "cpu" + manager = ResidentManager(model) + + reused = reusable_standalone_component( + manager, + name="controlnet", + load_id=model._diffusers_load_id, + dtype=torch.bfloat16, + offload_mode=OFFLOAD_MODE_NONE, device="cpu", - mode=OFFLOAD_MODE_GROUP_CPU, + ) + self.assertEqual(reused, ("controlnet_1", model)) + self.assertEqual(manager.last_component_id, "controlnet_1") + + self.assertIsNone( + reusable_standalone_component( + manager, + name="controlnet", + load_id=model._diffusers_load_id, + dtype=torch.float16, + offload_mode=OFFLOAD_MODE_NONE, + device="cpu", + ) + ) + self.assertIsNone( + reusable_standalone_component( + manager, + name="controlnet", + load_id=model._diffusers_load_id, + dtype=torch.bfloat16, + offload_mode=OFFLOAD_MODE_MODEL_CPU, + device="cpu", + ) + ) + + def test_shared_component_reuse_requires_matching_offload_device_and_disk_owner(self): + model = torch.nn.Linear(2, 2, dtype=torch.bfloat16) + model._modiff_offload_mode = OFFLOAD_MODE_GROUP_CPU + model._modiff_execution_device = "cuda:0" + model._modiff_offload_node_id = None + + self.assertTrue( + component_reuse_compatible( + model, + dtype=torch.bfloat16, + requested_quantization=None, + offload_mode=OFFLOAD_MODE_GROUP_CPU, + device="cuda:0", + node_id="loader-a", + ) + ) + self.assertFalse( + component_reuse_compatible( + model, + dtype=torch.bfloat16, + requested_quantization=None, + offload_mode=OFFLOAD_MODE_GROUP_DISK, + device="cuda:0", + node_id="loader-a", + ) + ) + self.assertFalse( + component_reuse_compatible( + model, + dtype=torch.bfloat16, + requested_quantization=None, + offload_mode=OFFLOAD_MODE_GROUP_CPU, + device="cuda:1", + node_id="loader-a", + ) + ) + + pipeline = type("SharedPipeline", (), {"components": {"transformer": model}})() + record_pipeline_component_runtime_policy( + pipeline, + offload_mode=OFFLOAD_MODE_GROUP_DISK, + device="cuda:0", + node_id="loader-a", + ) + self.assertTrue( + component_reuse_compatible( + model, + dtype=torch.bfloat16, + requested_quantization=None, + offload_mode=OFFLOAD_MODE_GROUP_DISK, + device="cuda:0", + node_id="loader-a", + ) + ) + self.assertFalse( + component_reuse_compatible( + model, + dtype=torch.bfloat16, + requested_quantization=None, + offload_mode=OFFLOAD_MODE_GROUP_DISK, + device="cuda:0", + node_id="loader-b", + ) + ) + + def test_device_map_is_reset_before_runtime_offload(self): + pipeline = FakeDeviceMappedPipeline() + + result = apply_pipeline_offload( + pipeline, + mode=OFFLOAD_MODE_MODEL_CPU, + device="cuda:0", node_id="smoke-node", - scope="smoke", ) + + self.assertTrue(result.applied) + self.assertEqual( + pipeline.calls, + [("reset_device_map", None), ("model_cpu", "cuda:0")], + ) + + def test_resident_device_map_is_preserved_without_offload(self): + pipeline = FakeResidentDeviceMappedPipeline() + + result = apply_pipeline_offload( + pipeline, + mode=OFFLOAD_MODE_NONE, + device="cuda:0", + node_id="smoke-node", + ) + + self.assertTrue(result.applied) + self.assertEqual(result.method, "preserve_device_map") + self.assertEqual(pipeline.calls, []) + self.assertEqual(pipeline.hf_device_map, {"transformer": 0, "text_encoder": "cuda:0"}) + + def test_explicit_cuda_device_map_is_preserved_without_offload(self): + pipeline = FakePipeline() + pipeline.hf_device_map = "cuda" + + result = apply_pipeline_offload( + pipeline, + mode=OFFLOAD_MODE_NONE, + device="cuda:0", + node_id="loader", + ) + + self.assertEqual(result.method, "preserve_device_map") + self.assertEqual(pipeline.calls, []) + self.assertEqual(pipeline.hf_device_map, "cuda") + + def test_device_map_without_reset_support_fails_before_movement(self): + pipeline = FakePipeline() + pipeline.hf_device_map = {"transformer": 0} + + with self.assertRaisesRegex(RuntimeError, "cannot reset that placement"): + reset_pipeline_device_map_for_runtime(pipeline) + + self.assertEqual(pipeline.calls, []) + + def test_cpu_execution_bypasses_group_cpu_and_disk_component_hooks(self): + group_pipeline = FakePipeline() + with patch( + "diffusers.hooks.apply_group_offloading", + side_effect=AssertionError("CPU execution must not install a group-offload hook"), + ): + group_result = apply_component_group_offload( + group_pipeline, + component_names=["transformer"], + device="cpu", + mode=OFFLOAD_MODE_GROUP_CPU, + node_id="smoke-node", + scope="smoke", + ) self.assertTrue(group_result.applied) + self.assertEqual(group_result.mode, OFFLOAD_MODE_NONE) + self.assertEqual(group_result.method, "to_device") self.assertEqual(group_result.components, ["transformer"]) disk_pipeline = FakePipeline() - disk_result = apply_component_group_offload( - disk_pipeline, - component_names=["transformer"], - device="cpu", - mode=OFFLOAD_MODE_GROUP_DISK, - node_id="smoke-node", - scope="smoke", - ) + with patch( + "diffusers.hooks.apply_group_offloading", + side_effect=AssertionError("CPU execution must not install a group-offload hook"), + ): + disk_result = apply_component_group_offload( + disk_pipeline, + component_names=["transformer"], + device="cpu", + mode=OFFLOAD_MODE_GROUP_DISK, + node_id="smoke-node", + scope="smoke", + ) self.assertTrue(disk_result.applied) - self.assertTrue(disk_result.disk_path) - self.assertTrue(Path(disk_result.disk_path).exists()) + self.assertEqual(disk_result.mode, OFFLOAD_MODE_NONE) + self.assertEqual(disk_result.method, "to_device") + self.assertIsNone(disk_result.disk_path) def test_vae_group_offload_uses_leaf_hooks_for_direct_encode_decode(self): pipeline = FakePipeline() @@ -205,7 +611,7 @@ def test_quant_config_string_is_rejected_with_actionable_error(self): def test_public_execution_profiles_include_direct_qwen_fallback(self): profiles = {profile["id"]: profile for profile in public_execution_profiles()} qwen_profile = profiles["qwen-image:t2i-direct"] - self.assertEqual(qwen_profile["backend_path"], "modules.QwenImage.LoadPipeline") + self.assertEqual(qwen_profile["backend_path"], "modules.DiffusersImage.LoadPipeline") self.assertEqual(qwen_profile["pipeline_class"], "QwenImagePipeline") self.assertEqual(qwen_profile["fallback_repo"], QWEN_IMAGE_2512_PREQUANTIZED_REPO) self.assertEqual(qwen_profile["default_quantized_components"], []) @@ -214,6 +620,18 @@ def test_public_execution_profiles_include_direct_qwen_fallback(self): self.assertIn(OFFLOAD_MODE_GROUP_DISK, qwen_profile["retry_offload_modes"]) def test_qwen_pipeline_quant_config_supports_component_fallback(self): + from importlib.util import find_spec + + if find_spec("bitsandbytes") is None: + with self.assertRaisesRegex(RuntimeError, "BitsAndBytes 4-bit quantization is not installed"): + build_qwen_pipeline_quantization_config( + components=["transformer"], + quantization_mode="bnb_4bit", + compute_dtype=torch.bfloat16, + quant_type="nf4", + double_quant=True, + ) + return quant_config = build_qwen_pipeline_quantization_config( components=["transformer", "text_encoder"], quantization_mode="bnb_4bit", @@ -261,6 +679,26 @@ def test_strict_component_loading_raises_for_required_component_oom(self): self.assertIn("CUDA out of memory", str(context.exception)) self.assertEqual(diagnostics["components_failed"][0]["name"], "text_encoder") + def test_incremental_group_offload_is_selected_by_quantized_components_not_pipeline_name(self): + self.assertTrue( + should_incrementally_group_offload( + use_group_offload=True, + quant_config={"transformer": "bnb_4bit", "text_encoder": "bnb_4bit"}, + ) + ) + self.assertFalse( + should_incrementally_group_offload( + use_group_offload=False, + quant_config={"transformer": "bnb_4bit"}, + ) + ) + self.assertFalse( + should_incrementally_group_offload( + use_group_offload=True, + quant_config={"vae": "bnb_4bit"}, + ) + ) + def test_strict_component_loading_keeps_optional_component_failure_diagnostic(self): pipeline = FakeStrictPipeline({ "optional_encoder": FakeComponentSpec("optional_encoder", RuntimeError("optional failed")), @@ -427,14 +865,14 @@ def test_cuda_budget_can_still_be_enforced_explicitly(self): self.assertAlmostEqual(fake_torch.cuda.fractions[0][0], 0.125) self.assertEqual(fake_torch.cuda.fractions[0][1], 0) - def test_direct_qwen_loader_participates_in_generic_resource_retry(self): + def test_generic_qwen_loader_participates_in_resource_retry(self): from modiff.server import WebServer server = object.__new__(WebServer) graph = { "nodes": { "qwen-loader": { - "module": "modules.QwenImage", + "module": "modules.DiffusersImage", "action": "LoadPipeline", "params": { "offload_mode": {"value": OFFLOAD_MODE_MODEL_CPU}, @@ -449,15 +887,201 @@ def test_direct_qwen_loader_participates_in_generic_resource_retry(self): self.assertEqual(updated, ["qwen-loader"]) self.assertEqual(graph["nodes"]["qwen-loader"]["params"]["offload_mode"]["value"], OFFLOAD_MODE_GROUP_DISK) + def test_native_auto_plan_applies_direct_cuda_device_map_to_image_loader(self): + from modiff.server import WebServer + + server = object.__new__(WebServer) + graph = { + "nodes": { + "recipe": { + "module": "modules.DiffusersRuntime", + "action": "DiffusersExecutionRecipe", + "params": { + "device_map": {"value": "none"}, + "offload_mode": {"value": OFFLOAD_MODE_MODEL_CPU}, + }, + }, + "qwen-loader": { + "module": "modules.DiffusersImage", + "action": "LoadPipeline", + "params": { + "model_id": {"value": {"source": "hub", "value": "old-model"}}, + "pipeline_class": {"value": "QwenImagePipeline"}, + "device_map": {"value": "none"}, + "offload_mode": {"value": OFFLOAD_MODE_MODEL_CPU}, + "auto_offload": {"value": True}, + "execution_recipe": {"sourceId": "recipe", "sourceKey": "execution_recipe"}, + }, + }, + }, + } + + updated = WebServer._apply_resource_retry_plan_to_graph( + server, + graph, + { + "executionPath": "direct-diffusers-image", + "pipelineClass": "QwenImagePipeline", + "modelRepo": "Qwen/Qwen-Image-2512", + "offloadMode": OFFLOAD_MODE_NONE, + "deviceMap": "cuda", + }, + ) + + params = graph["nodes"]["qwen-loader"]["params"] + self.assertEqual(updated, ["recipe", "qwen-loader"]) + self.assertEqual(params["device_map"]["value"], "cuda") + self.assertEqual(params["offload_mode"]["value"], OFFLOAD_MODE_NONE) + self.assertFalse(params["auto_offload"]["value"]) + self.assertEqual(graph["nodes"]["recipe"]["params"]["device_map"]["value"], "cuda") + self.assertEqual(graph["nodes"]["recipe"]["params"]["offload_mode"]["value"], OFFLOAD_MODE_NONE) + + def test_structured_audio_plan_does_not_rewrite_independent_video_loader(self): + from modiff.server import WebServer + + server = object.__new__(WebServer) + graph = { + "nodes": { + "audio-recipe": { + "module": "modules.DiffusersRuntime", + "action": "DiffusersExecutionRecipe", + "params": { + "device_map": {"value": "none"}, + "offload_mode": {"value": OFFLOAD_MODE_MODEL_CPU}, + }, + }, + "audio-loader": { + "module": "modules.DiffusersAudio", + "action": "LoadPipeline", + "params": { + "model_id": {"value": {"source": "hub", "value": "old-audio"}}, + "pipeline_class": {"value": "AceStepPipeline"}, + "offload_mode": {"value": OFFLOAD_MODE_MODEL_CPU}, + "auto_offload": {"value": True}, + "execution_recipe": { + "sourceId": "audio-recipe", + "sourceKey": "execution_recipe", + }, + }, + }, + "audio-generate": { + "module": "modules.DiffusersAudio", + "action": "Generate", + "params": {"audio_duration": {"value": 12}, "num_inference_steps": {"value": 4}}, + }, + "video-loader": { + "module": "modules.DiffusersVideo", + "action": "LoadPipeline", + "params": { + "model_id": {"value": {"source": "hub", "value": "Lightricks/LTX-Video"}}, + "pipeline_class": {"value": "LTXConditionPipeline"}, + "offload_mode": {"value": OFFLOAD_MODE_MODEL_CPU}, + "auto_offload": {"value": True}, + }, + }, + }, + } + + updated = WebServer._apply_resource_retry_plan_to_graph( + server, + graph, + { + "modelRepo": "ACE-Step/acestep-v15-xl-turbo-diffusers", + "pipelineClass": "AceStepPipeline", + "offloadMode": OFFLOAD_MODE_NONE, + "deviceMap": "cuda", + "generation": {"audioDuration": 24, "steps": 8}, + }, + ) + + self.assertEqual(updated, ["audio-recipe", "audio-loader"]) + self.assertEqual(graph["nodes"]["audio-recipe"]["params"]["device_map"]["value"], "cuda") + self.assertEqual(graph["nodes"]["audio-recipe"]["params"]["offload_mode"]["value"], OFFLOAD_MODE_NONE) + self.assertEqual( + graph["nodes"]["audio-loader"]["params"]["model_id"]["value"]["value"], + "ACE-Step/acestep-v15-xl-turbo-diffusers", + ) + # Auto retry plans own runtime configuration only; creative/generation + # controls remain exactly as the user configured them. + self.assertEqual(graph["nodes"]["audio-generate"]["params"]["audio_duration"]["value"], 12) + self.assertEqual( + graph["nodes"]["video-loader"]["params"]["model_id"]["value"]["value"], + "Lightricks/LTX-Video", + ) + self.assertEqual( + graph["nodes"]["video-loader"]["params"]["pipeline_class"]["value"], + "LTXConditionPipeline", + ) + + def test_auto_retry_preserves_pinned_fields_and_requires_an_unpinned_change(self): + from modiff.server import WebServer + + server = object.__new__(WebServer) + graph = { + "runtimeHints": { + "autoFieldOverrides": [ + { + "schemaVersion": 1, + "nodeId": "loader", + "fieldKey": "dtype", + "value": "float16", + }, + { + "schemaVersion": 1, + "nodeId": "loader", + "fieldKey": "offload_mode", + "value": OFFLOAD_MODE_NONE, + }, + ], + }, + "nodes": { + "loader": { + "module": "modules.DiffusersImage", + "action": "LoadPipeline", + "params": { + "dtype": {"value": "float16"}, + "offload_mode": {"value": OFFLOAD_MODE_NONE}, + }, + }, + }, + } + plan = { + "dtype": "bfloat16", + "offloadMode": OFFLOAD_MODE_MODEL_CPU, + "onCategories": ["oom"], + } + + updated = WebServer._apply_resource_retry_plan_to_graph(server, graph, plan) + retry_index, skipped = WebServer._next_applicable_retry_plan_index( + server, + graph, + [plan], + -1, + {"category": "oom", "error_code": "cuda_oom"}, + ) + + self.assertEqual(updated, []) + self.assertIsNone(retry_index) + self.assertEqual(skipped, [0]) + self.assertEqual(graph["nodes"]["loader"]["params"]["dtype"]["value"], "float16") + self.assertEqual(graph["nodes"]["loader"]["params"]["offload_mode"]["value"], OFFLOAD_MODE_NONE) + def test_execute_graph_retries_oom_with_next_offload_mode_and_diagnostics(self): from modiff.server import WebServer server = object.__new__(WebServer) server.current_task = {"task_id": "task-1"} server.interrupt_flag = False - server.queue_message = lambda *args, **kwargs: None + messages = [] + server.queue_message = messages.append server._apply_cuda_runtime_budget = lambda runtime_hints: {} - server._apply_deterministic_mode = lambda graph: None + deterministic_calls = [] + + def apply_deterministic(_graph): + deterministic_calls.append(len(deterministic_calls) + 1) + return {"enabled": True, "seed": 17, "application": deterministic_calls[-1]} + + server._apply_deterministic_mode = apply_deterministic server._runtime_fingerprint = lambda: {"fingerprint": "fake"} server._release_runtime_caches_for_retry = lambda: {"released": {}, "errors": []} server._loader_diagnostics_snapshot = lambda: {"loader-node": {"normalized_offload_mode": "group_cpu"}} @@ -472,6 +1096,7 @@ def execute_node(node_id, node, sid): graph = { "sid": "sid-1", "paths": [["loader-node"]], + "deterministicMode": {"enabled": True, "strict": False, "seed": 17}, "runtimeHints": { "source": "studio", "device": "cuda:0", @@ -493,6 +1118,10 @@ def execute_node(node_id, node, sid): result = WebServer.execute_graph(server, graph) self.assertIsNone(result) self.assertEqual(calls["count"], 2) + self.assertEqual(deterministic_calls, [1, 2]) + completed = next(message for message in messages if message.get("type") == "graph_completed") + self.assertEqual(completed["deterministicMode"]["seed"], 17) + self.assertEqual(completed["deterministicMode"]["application"], 2) self.assertEqual(graph["nodes"]["loader-node"]["params"]["offload_mode"]["value"], OFFLOAD_MODE_GROUP_DISK) diff --git a/tests/test_diffusers_profiles.py b/tests/test_diffusers_profiles.py new file mode 100644 index 0000000..6659547 --- /dev/null +++ b/tests/test_diffusers_profiles.py @@ -0,0 +1,51 @@ +import unittest + +from modiff.diffusers_profiles import DIFFUSERS_EXECUTION_PROFILES + + +class DiffusersExecutionProfileTests(unittest.TestCase): + def test_every_supported_studio_model_has_an_execution_profile(self): + expected = { + "ZImageModularPipeline", + "QwenImageModularPipeline", + "QwenImageEditModularPipeline", + "QwenImageEditPlusModularPipeline", + "QwenImageLayeredModularPipeline", + "WanVACEPipeline", + "WanVideoPipeline", + "WanImageToVideoPipeline", + "WanTI2VPipeline", + "LTXVideoPipeline", + "AceStepAudioPipeline", + "FluxSchnellPipeline", + "FluxDevPipeline", + "FluxKreaPipeline", + "FluxKontextPipeline", + "FluxFillPipeline", + "FluxDepthPipeline", + "FluxCannyPipeline", + "FluxReduxPipeline", + "Flux2KleinPipeline", + } + actual = {profile.model_type for profile in DIFFUSERS_EXECUTION_PROFILES.values()} + self.assertEqual(expected, actual) + + def test_video_profile_uses_generic_facade(self): + profile = DIFFUSERS_EXECUTION_PROFILES["wan-vace:direct"] + self.assertEqual(profile.backend_path, "modules.DiffusersVideo.LoadPipeline") + + wan_video_profile = DIFFUSERS_EXECUTION_PROFILES["wan-video-to-video:direct"] + self.assertEqual(wan_video_profile.backend_path, "modules.DiffusersVideo.LoadPipeline") + self.assertEqual(wan_video_profile.pipeline_class, "WanVideoToVideoPipeline") + + wan_text_profile = DIFFUSERS_EXECUTION_PROFILES["wan-text-to-video:direct"] + self.assertEqual(wan_text_profile.backend_path, "modules.DiffusersVideo.LoadPipeline") + self.assertEqual(wan_text_profile.pipeline_class, "WanPipeline") + + ltx_profile = DIFFUSERS_EXECUTION_PROFILES["ltx-video:direct"] + self.assertEqual(ltx_profile.backend_path, "modules.DiffusersVideo.LoadPipeline") + self.assertEqual(ltx_profile.pipeline_class, "LTXConditionPipeline") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_diffusers_runtime.py b/tests/test_diffusers_runtime.py new file mode 100644 index 0000000..9be3288 --- /dev/null +++ b/tests/test_diffusers_runtime.py @@ -0,0 +1,790 @@ +import json +import os +import tempfile +import types +import unittest +from unittest.mock import patch + +from modules import MODULE_MAP +from modules.DiffusersRuntime.main import ( + ApplyPipelineRuntimeConfig, + LoadPrequantizedDiffusersComponent, + PipelineQuantizationConfigV2, + apply_attention_backend, + build_quantization_config_v2, + build_execution_recipe, + build_runtime_capabilities, + configure_channels_last, + configure_denoiser_cache, + configure_layerwise_casting, + configure_regional_compile, + configure_vae_memory, + estimate_pipeline_memory, + execution_recipe_summary, + loader_runtime_options, + plan_execution_recipes, + release_pipeline_memory, + summarize_safetensors_files, +) + + +class FakeAttentionComponent: + def __init__(self): + self.backends = [] + self.is_cache_enabled = False + self.cache_configs = [] + self.compile_calls = [] + + def set_attention_backend(self, backend): + self.backends.append(backend) + + def enable_cache(self, config): + self.cache_configs.append(config) + self.is_cache_enabled = True + + def disable_cache(self): + self.is_cache_enabled = False + + def compile_repeated_blocks(self, **kwargs): + self.compile_calls.append(kwargs) + + +class FakeVAE: + def __init__(self): + self.calls = [] + + def enable_slicing(self): + self.calls.append("enable_slicing") + + def disable_slicing(self): + self.calls.append("disable_slicing") + + def enable_tiling(self): + self.calls.append("enable_tiling") + + def disable_tiling(self): + self.calls.append("disable_tiling") + + +class FakePipeline: + def __init__(self): + self.transformer = FakeAttentionComponent() + self.vae = FakeVAE() + self.moves = [] + self.freed_hooks = 0 + + def to(self, device): + self.moves.append(device) + return self + + def maybe_free_model_hooks(self): + self.freed_hooks += 1 + + +class FakePipelineQuantizationConfig: + def __init__(self, *, quant_mapping): + self.quant_mapping = quant_mapping + + +class DiffusersRuntimeTests(unittest.TestCase): + def test_gguf_loader_pins_cataloged_artifact_and_base_config_independently(self): + calls = {} + + class FakeComponent: + @classmethod + def from_single_file(cls, path, **kwargs): + calls["component"] = (path, kwargs) + return object() + + with tempfile.NamedTemporaryFile(suffix=".gguf") as artifact_file: + node = LoadPrequantizedDiffusersComponent("gguf-revision-probe") + node.progress = lambda *args, **kwargs: None + with ( + patch("diffusers.FluxTransformer2DModel", FakeComponent), + patch("diffusers.GGUFQuantizationConfig", return_value=object()), + patch("huggingface_hub.hf_hub_download", return_value=artifact_file.name) as download, + ): + result = node.execute( + artifact={"source": "hub", "value": "city96/FLUX.1-schnell-gguf"}, + filename="flux1-schnell-Q4_0.gguf", + component_class="FluxTransformer2DModel", + config_model="black-forest-labs/FLUX.1-schnell", + ) + + self.assertEqual( + download.call_args.kwargs["revision"], + "f495746ed9c5efcf4661f53ef05401dceadc17d2", + ) + self.assertEqual( + calls["component"][1]["config_revision"], + "741f7c3ce8b383c54771c7003378a50191e9efe9", + ) + self.assertIn("@f495746ed9c5efcf4661f53ef05401dceadc17d2:", result["resolved_artifact"]) + + def test_runtime_nodes_are_registered(self): + runtime = MODULE_MAP["modules.DiffusersRuntime"] + self.assertIn("PipelineQuantizationConfigV2", runtime) + self.assertIn("ApplyPipelineRuntimeConfig", runtime) + self.assertIn("DiffusersComponentInventory", runtime) + self.assertIn("DiffusersExecutionRecipe", runtime) + self.assertIn("HardwareCapabilityProbe", runtime) + self.assertIn("PipelineMemoryEstimate", runtime) + self.assertIn("ExecutionRecipePlanner", runtime) + self.assertIn("ReleasePipelineMemory", runtime) + self.assertEqual(runtime["ApplyPipelineRuntimeConfig"]["category"], "Diffusers Runtime") + + def test_quantization_v2_builds_component_specific_configs_and_exclusions(self): + calls = [] + + def fake_config(backend, dtype, excluded): + calls.append((backend, dtype, excluded)) + return {"backend": backend, "excluded": excluded} + + with ( + patch("modules.DiffusersRuntime.main.quant_config_for", side_effect=fake_config), + patch( + "diffusers.quantizers.PipelineQuantizationConfig", + FakePipelineQuantizationConfig, + ), + ): + config, summary = build_quantization_config_v2( + backend="bnb_4bit", + components=["transformer", "text_encoder"], + dtype="bf16", + excluded_modules="proj_out, norm_out", + component_overrides={ + "text_encoder": { + "backend": "quanto_float8", + "excluded_modules": ["final_layer_norm"], + } + }, + ) + + self.assertEqual( + calls, + [ + ("bnb_4bit", "bf16", ["proj_out", "norm_out"]), + ("quanto_float8", "bf16", ["final_layer_norm"]), + ], + ) + self.assertEqual(set(config.quant_mapping), {"transformer", "text_encoder"}) + self.assertEqual(summary["text_encoder"]["backend"], "quanto_float8") + + def test_quantization_v2_rejects_unknown_components(self): + with self.assertRaisesRegex(ValueError, "Unknown quantized components"): + build_quantization_config_v2( + backend="bnb_4bit", + components=["transformer"], + dtype="bf16", + component_overrides={"mystery_encoder": "bnb_8bit"}, + ) + + def test_quantization_v2_supports_dual_expert_video_transformers(self): + with ( + patch( + "modules.DiffusersRuntime.main.quant_config_for", + side_effect=lambda backend, dtype, excluded: {"backend": backend, "dtype": dtype}, + ), + patch( + "diffusers.quantizers.PipelineQuantizationConfig", + FakePipelineQuantizationConfig, + ), + ): + config, summary = build_quantization_config_v2( + backend="torchao_int8_weight_only", + components=["transformer", "transformer_2"], + dtype="bf16", + ) + + self.assertEqual(set(config.quant_mapping), {"transformer", "transformer_2"}) + self.assertEqual(summary["transformer_2"]["backend"], "torchao_int8_weight_only") + + def test_component_inventory_summarizes_weight_floors_without_loading_tensors(self): + summary = summarize_safetensors_files( + [ + { + "name": "transformer/model-00001.safetensors", + "source_bytes": 120, + "parameter_count": {"BF16": 40, "F32": 5}, + }, + { + "name": "transformer/model-00002.safetensors", + "source_bytes": 80, + "parameter_count": {"BF16": 20}, + }, + { + "name": "vae/model.safetensors", + "source_bytes": 30, + "parameter_count": {"F32": 3}, + }, + ] + ) + + self.assertEqual(summary["total_source_bytes"], 230) + self.assertEqual(summary["total_parameter_count"], 68) + self.assertEqual(summary["total_weight_bytes"], 152) + self.assertEqual(summary["largest_shard"]["name"], "transformer/model-00001.safetensors") + transformer = next(item for item in summary["components"] if item["component"] == "transformer") + self.assertEqual(transformer["parameter_count"], 65) + self.assertEqual(transformer["weight_bytes"], 140) + + def test_attention_backend_applies_only_to_compatible_components(self): + pipeline = FakePipeline() + result = apply_attention_backend(pipeline, "aiter") + + self.assertEqual(pipeline.transformer.backends, ["aiter"]) + self.assertEqual(result["applied"], ["transformer"]) + + def test_attention_auto_preserves_diffusers_default(self): + pipeline = FakePipeline() + result = apply_attention_backend(pipeline, "auto") + + self.assertEqual(pipeline.transformer.backends, []) + self.assertTrue(result["default_selection"]) + + def test_attention_backend_configures_both_dual_expert_transformers(self): + pipeline = FakePipeline() + pipeline.transformer_2 = FakeAttentionComponent() + + result = apply_attention_backend(pipeline, "native") + + self.assertEqual(pipeline.transformer.backends, ["native"]) + self.assertEqual(pipeline.transformer_2.backends, ["native"]) + self.assertEqual(result["applied"], ["transformer", "transformer_2"]) + + def test_execution_recipe_rejects_runtime_quantization_with_split_offload(self): + quant = FakePipelineQuantizationConfig(quant_mapping={"transformer": object()}) + with self.assertRaisesRegex(ValueError, "Create optimized copy"): + build_execution_recipe( + quantization_config=quant, + device_map="manual", + device_map_overrides='{"transformer": 0, "text_encoder_2": "cpu"}', + max_memory='{"0": "16GiB", "cpu": "48GiB"}', + offload_mode="group_cpu", + device="cuda:0", + ) + + recipe = build_execution_recipe( + device_map="manual", + device_map_overrides='{"transformer": 0, "text_encoder_2": "cpu"}', + max_memory='{"0": "16GiB", "cpu": "48GiB"}', + offload_mode="group_cpu", + device="cuda:0", + attention_backend="native", + attention_components="transformer, controlnet", + vae_slicing=True, + vae_tiling=False, + ) + + self.assertEqual(recipe["max_memory"], {0: "16GiB", "cpu": "48GiB"}) + self.assertEqual(recipe["device_map"], {"transformer": 0, "text_encoder_2": "cpu"}) + self.assertEqual(recipe["attention_components"], ["transformer", "controlnet"]) + summary = execution_recipe_summary(recipe) + self.assertNotIn("quantization_config", summary) + self.assertEqual(summary["quantized_components"], []) + + def test_manual_device_map_requires_component_placements(self): + with self.assertRaisesRegex(ValueError, "needs at least one component placement entry"): + build_execution_recipe(device_map="manual", device_map_overrides="{}") + + def test_loader_runtime_options_preserve_legacy_controls_and_connected_recipe_override(self): + legacy_recipe, legacy_device, legacy_offload, legacy_load = loader_runtime_options( + {"device": "cpu", "auto_offload": False, "offload_mode": "group_cpu"}, + default_device="cuda:0", + default_offload_mode="model_cpu", + ) + self.assertEqual(legacy_recipe, {}) + self.assertEqual(legacy_device, "cpu") + self.assertEqual(legacy_offload, "none") + self.assertEqual(legacy_load, {}) + + quant = object() + recipe = { + "device": "cuda:1", + "offload_mode": "group_cpu", + "quantization_config": quant, + "device_map": "balanced", + "max_memory": {0: "20GiB", "cpu": "64GiB"}, + } + with self.assertRaisesRegex(ValueError, "full GPU residency"): + loader_runtime_options( + {"execution_recipe": recipe, "auto_offload": False}, + default_device="cuda:0", + default_offload_mode="model_cpu", + ) + + def test_complete_pipeline_no_offload_streams_directly_to_target_accelerator(self): + with patch.dict(os.environ, {}, clear=False): + os.environ.pop("HF_ENABLE_PARALLEL_LOADING", None) + recipe, device, offload, load = loader_runtime_options( + { + "execution_recipe": { + "device": "cuda:0", + "offload_mode": "none", + "device_map": "none", + } + }, + default_device="cuda:0", + default_offload_mode="model_cpu", + direct_device_load=True, + ) + self.assertEqual(os.environ["HF_ENABLE_PARALLEL_LOADING"], "YES") + + self.assertEqual(recipe["device_map"], "none") + self.assertEqual(device, "cuda:0") + self.assertEqual(offload, "none") + self.assertEqual(load["device_map"], "cuda") + + def test_direct_device_loading_respects_explicit_parallel_loading_opt_out(self): + with patch.dict(os.environ, {"HF_ENABLE_PARALLEL_LOADING": "false"}): + _, _, offload, load = loader_runtime_options( + { + "execution_recipe": { + "device": "cuda:0", + "offload_mode": "none", + "device_map": "cuda", + } + }, + default_device="cuda:0", + default_offload_mode="model_cpu", + direct_device_load=True, + ) + self.assertEqual(os.environ["HF_ENABLE_PARALLEL_LOADING"], "false") + self.assertEqual(offload, "none") + self.assertEqual(load["device_map"], "cuda") + + def test_direct_device_loading_never_overrides_offload_or_explicit_map(self): + _, _, offload, load = loader_runtime_options( + { + "execution_recipe": { + "device": "cuda:0", + "offload_mode": "model_cpu", + "device_map": "none", + } + }, + default_device="cuda:0", + default_offload_mode="model_cpu", + direct_device_load=True, + ) + self.assertEqual(offload, "model_cpu") + self.assertNotIn("device_map", load) + + _, _, offload, load = loader_runtime_options( + { + "execution_recipe": { + "device": "cuda:1", + "offload_mode": "none", + "device_map": "balanced", + } + }, + default_device="cuda:0", + default_offload_mode="model_cpu", + direct_device_load=True, + ) + self.assertEqual(offload, "none") + self.assertEqual(load["device_map"], "balanced") + + _, _, offload, load = loader_runtime_options( + { + "execution_recipe": { + "device": "cuda:1", + "offload_mode": "none", + "device_map": "none", + } + }, + default_device="cuda:0", + default_offload_mode="model_cpu", + direct_device_load=True, + ) + self.assertEqual(offload, "none") + self.assertNotIn("device_map", load) + + def test_capability_probe_distinguishes_rocm_attention_from_nvidia_only_backends(self): + class FakeCuda: + @staticmethod + def get_device_capability(_index): + return (9, 0) + + @staticmethod + def is_bf16_supported(): + return True + + fake_torch = types.SimpleNamespace( + __version__="test-rocm", + version=types.SimpleNamespace(hip="7.2", cuda=None), + cuda=FakeCuda(), + compile=lambda fn: fn, + ) + hardware = {"devices": [{"type": "cuda", "device": "cuda:0"}]} + available = {"aiter", "torchao", "bitsandbytes"} + result = build_runtime_capabilities( + hardware, + torch_module=fake_torch, + package_available=lambda name: name in available, + ) + + self.assertEqual(result["vendor"], "amd") + self.assertTrue(result["attention_backends"]["aiter"]["available"]) + self.assertFalse(result["attention_backends"]["flash"]["available"]) + self.assertFalse(result["quantization_backends"]["torchao_float8"]["available"]) + + def test_capability_probe_explains_missing_rocm_attention_packages(self): + class FakeCuda: + @staticmethod + def get_device_capability(_index): + return (9, 0) + + @staticmethod + def is_bf16_supported(): + return True + + fake_torch = types.SimpleNamespace( + __version__="test-rocm", + version=types.SimpleNamespace(hip="7.2", cuda=None), + cuda=FakeCuda(), + compile=lambda fn: fn, + ) + result = build_runtime_capabilities( + {"devices": [{"type": "cuda", "device": "cuda:0"}]}, + torch_module=fake_torch, + package_available=lambda _name: False, + ) + + self.assertFalse(result["attention_backends"]["aiter"]["available"]) + self.assertEqual(result["attention_backends"]["aiter"]["reason"], "AITER package is not installed") + self.assertEqual( + result["attention_backends"]["sage"]["reason"], + "SageAttention package is not installed", + ) + + def test_capability_probe_treats_missing_parent_package_as_unavailable(self): + fake_torch = types.SimpleNamespace( + __version__="test-cpu", + version=types.SimpleNamespace(hip=None, cuda=None), + compile=lambda fn: fn, + ) + + def missing_parent(name): + if name == "optimum.quanto": + raise ModuleNotFoundError("No module named 'optimum'") + return None + + with patch("modules.DiffusersRuntime.main.importlib.util.find_spec", side_effect=missing_parent): + result = build_runtime_capabilities( + {"devices": [{"type": "cpu", "device": "cpu:0"}]}, + torch_module=fake_torch, + ) + + self.assertFalse(result["quantization_backends"]["quanto_float8"]["available"]) + + def test_capability_probe_requires_nvidia_89_for_torchao_fp8(self): + class FakeCuda: + @staticmethod + def get_device_capability(_index): + return (8, 9) + + @staticmethod + def is_bf16_supported(): + return True + + fake_torch = types.SimpleNamespace( + __version__="test-cuda", + version=types.SimpleNamespace(hip=None, cuda="12.8"), + cuda=FakeCuda(), + compile=lambda fn: fn, + ) + result = build_runtime_capabilities( + {"devices": [{"type": "cuda", "device": "cuda:0"}]}, + torch_module=fake_torch, + package_available=lambda name: name == "torchao", + ) + + self.assertEqual(result["vendor"], "nvidia") + self.assertTrue(result["quantization_backends"]["torchao_float8"]["available"]) + self.assertTrue(result["compile"]["available"]) + + def test_capability_probe_reports_xpu_without_claiming_cuda_kernels(self): + fake_torch = types.SimpleNamespace( + __version__="test-xpu", + version=types.SimpleNamespace(hip=None, cuda=None), + xpu=types.SimpleNamespace(is_bf16_supported=lambda: True), + compile=lambda fn: fn, + ) + result = build_runtime_capabilities( + {"devices": [{"type": "xpu", "device": "xpu:0"}]}, + torch_module=fake_torch, + package_available=lambda _name: True, + ) + + self.assertEqual(result["vendor"], "intel") + self.assertTrue(result["dtypes"]["bfloat16"]) + self.assertFalse(result["attention_backends"]["_native_flash"]["available"]) + self.assertFalse(result["quantization_backends"]["bnb_4bit"]["available"]) + + def test_memory_estimate_reports_floors_without_fabricating_peak_vram(self): + inventory = { + "total_weight_bytes": 260, + "components": [ + { + "component": "transformer", + "parameter_count": 100, + "weight_bytes": 200, + "dtype_counts": {"BF16": 100}, + }, + { + "component": "vae", + "parameter_count": 30, + "weight_bytes": 60, + "dtype_counts": {"BF16": 30}, + }, + ], + } + result = estimate_pipeline_memory( + inventory, + width=128, + height=64, + frames=9, + batch_size=2, + latent_channels=4, + spatial_compression=8, + temporal_compression=4, + dtype_bytes=2, + quantization_summary={"transformer": {"backend": "bnb_4bit"}}, + offload_mode="group_cpu", + ) + + self.assertEqual(result["weights"]["unquantized_weight_floor_bytes"], 260) + self.assertEqual(result["weights"]["idealized_weight_floor_bytes"], 110) + self.assertEqual(result["weights"]["accelerator_resident_weight_proxy_bytes"], 60) + self.assertEqual(result["shape"]["latent_tensor_bytes"], 6144) + self.assertIsNone(result["peak_accelerator_memory_bytes"]) + self.assertIn("unavailable", result["confidence"]["peak_accelerator_memory"]) + + def test_recipe_planner_ranks_preference_without_claiming_runtime_proof(self): + capabilities = { + "backend": "cuda", + "device": "cuda:0", + "dtypes": {"float16": True, "bfloat16": True}, + "memory": {"accelerator_free_bytes": 20_000}, + "quantization_backends": { + "bnb_8bit": {"available": True}, + "bnb_4bit": {"available": True}, + }, + } + inventory = { + "total_weight_bytes": 10_000, + "components": [ + {"component": "transformer", "weight_bytes": 8_000}, + {"component": "vae", "weight_bytes": 2_000}, + ], + } + plan = plan_execution_recipes(capabilities, inventory, preference="balanced") + + selected = plan["candidates"][0] + self.assertEqual(selected["profile"], "balanced") + self.assertEqual(selected["quantization_backend"], "none") + self.assertEqual(selected["proof_status"], "required") + self.assertFalse(selected["regional_compile"]) + self.assertEqual(selected["denoiser_cache"], "none") + + def test_recipe_planner_does_not_invent_quantizable_components(self): + plan = plan_execution_recipes( + { + "backend": "cpu", + "device": "cpu:0", + "dtypes": {"float32": True}, + "memory": {}, + "quantization_backends": {"quanto_float8": {"available": True}}, + }, + { + "total_weight_bytes": 100, + "components": [{"component": "root", "weight_bytes": 100}], + }, + preference="low_memory", + ) + + self.assertEqual(plan["candidates"][0]["quantization_backend"], "none") + + def test_vae_memory_configuration_can_enable_and_disable_features(self): + pipeline = FakePipeline() + enabled = configure_vae_memory(pipeline, slicing=True, tiling=True) + disabled = configure_vae_memory(pipeline, slicing=False, tiling=False) + + self.assertEqual( + pipeline.vae.calls, + ["enable_slicing", "enable_tiling", "disable_slicing", "disable_tiling"], + ) + self.assertEqual(len(enabled["applied"]), 2) + self.assertEqual(len(disabled["applied"]), 2) + + def test_first_block_cache_is_explicit_and_reversible(self): + pipeline = FakePipeline() + enabled = configure_denoiser_cache(pipeline, strategy="first_block", threshold=0.08) + disabled = configure_denoiser_cache(pipeline, strategy="none") + + self.assertEqual(enabled["applied"], ["transformer"]) + self.assertAlmostEqual(pipeline.transformer.cache_configs[0].threshold, 0.08) + self.assertEqual(disabled["disabled"], ["transformer"]) + self.assertFalse(pipeline.transformer.is_cache_enabled) + + def test_magcache_options_build_current_diffusers_config(self): + pipeline = FakePipeline() + result = configure_denoiser_cache( + pipeline, + strategy="magcache", + options={ + "threshold": 0.07, + "max_skip_steps": 2, + "retention_ratio": 0.25, + "num_inference_steps": 20, + "calibrate": True, + }, + ) + + config = pipeline.transformer.cache_configs[0] + self.assertEqual(result["config"], "MagCacheConfig") + self.assertAlmostEqual(config.threshold, 0.07) + self.assertEqual(config.max_skip_steps, 2) + self.assertEqual(config.num_inference_steps, 20) + + def test_magcache_requires_calibration_or_model_specific_ratios(self): + with self.assertRaisesRegex(ValueError, "calibrate=true"): + configure_denoiser_cache(FakePipeline(), strategy="magcache") + + def test_regional_compile_uses_diffusers_repeated_block_api(self): + pipeline = FakePipeline() + result = configure_regional_compile( + pipeline, + enabled=True, + components="transformer", + backend="inductor", + mode="reduce-overhead", + fullgraph=False, + dynamic=True, + ) + + self.assertEqual(result["applied"], ["transformer"]) + self.assertEqual( + pipeline.transformer.compile_calls, + [ + { + "backend": "inductor", + "mode": "reduce-overhead", + "fullgraph": False, + "dynamic": True, + } + ], + ) + + def test_layerwise_casting_is_explicit_and_idempotent(self): + pipeline = FakePipeline() + with patch("diffusers.hooks.apply_layerwise_casting") as apply: + first = configure_layerwise_casting( + pipeline, + enabled=True, + components="transformer", + storage_dtype="float8_e4m3fn", + compute_dtype="bfloat16", + ) + second = configure_layerwise_casting( + pipeline, + enabled=True, + components="transformer", + storage_dtype="float8_e4m3fn", + compute_dtype="bfloat16", + ) + + self.assertEqual(first["applied"], ["transformer"]) + self.assertEqual(second["alreadyApplied"], ["transformer"]) + apply.assert_called_once() + + def test_channels_last_only_mutates_selected_components_once(self): + class FakeConvolutionalComponent: + def __init__(self): + self.memory_formats = [] + + def to(self, **kwargs): + self.memory_formats.append(kwargs["memory_format"]) + return self + + pipeline = types.SimpleNamespace(unet=FakeConvolutionalComponent()) + first = configure_channels_last(pipeline, enabled=True, components="unet") + second = configure_channels_last(pipeline, enabled=True, components="unet") + + self.assertEqual(first["applied"], ["unet"]) + self.assertEqual(second["alreadyApplied"], ["unet"]) + self.assertEqual(len(pipeline.unet.memory_formats), 1) + + def test_execution_recipe_preserves_opt_in_layout_and_casting(self): + recipe = build_execution_recipe( + layerwise_casting=True, + layerwise_casting_components="transformer", + layerwise_storage_dtype="float8_e4m3fn", + layerwise_compute_dtype="bfloat16", + channels_last=True, + channels_last_components="unet,vae", + ) + + self.assertTrue(recipe["layerwise_casting"]) + self.assertEqual(recipe["layerwise_casting_components"], ["transformer"]) + self.assertTrue(recipe["channels_last"]) + self.assertEqual(recipe["channels_last_components"], ["unet", "vae"]) + + def test_pipeline_release_disables_cache_and_moves_weights_to_cpu(self): + pipeline = FakePipeline() + pipeline.transformer.is_cache_enabled = True + with patch("torch.cuda.is_available", return_value=False): + report = release_pipeline_memory(pipeline, move_to_cpu=True, disable_cache=True) + + self.assertFalse(pipeline.transformer.is_cache_enabled) + self.assertEqual(pipeline.freed_hooks, 1) + self.assertEqual(pipeline.moves, ["cpu"]) + self.assertIn("pipeline_to_cpu", report["released"]) + + def test_runtime_node_returns_same_pipeline_and_user_readable_summary(self): + pipeline = FakePipeline() + result = ApplyPipelineRuntimeConfig("runtime-test").execute( + pipeline=pipeline, + attention_backend="native", + vae_slicing=True, + vae_tiling=False, + ) + + self.assertIs(result["configured_pipeline"], pipeline) + summary = json.loads(result["summary"]) + self.assertEqual(summary["attention"]["applied"], ["transformer"]) + self.assertIn("vae", summary) + + def test_quantization_node_returns_pipeline_config(self): + with ( + patch("modules.DiffusersRuntime.main.str_to_dtype", return_value="bf16"), + patch("modules.DiffusersRuntime.main.quant_config_for", return_value={"config": True}), + patch( + "diffusers.quantizers.PipelineQuantizationConfig", + FakePipelineQuantizationConfig, + ), + ): + result = PipelineQuantizationConfigV2("quant-test").execute( + backend="bnb_4bit", + components=["transformer"], + component_overrides="{}", + ) + + self.assertEqual(set(result["quantization_config"].quant_mapping), {"transformer"}) + self.assertEqual(json.loads(result["summary"])["transformer"]["backend"], "bnb_4bit") + + def test_disabled_quantization_node_keeps_connected_flow_executable(self): + result = PipelineQuantizationConfigV2("quant-disabled").execute( + backend="none", + components=["transformer"], + component_overrides="{}", + ) + + self.assertEqual(result["quantization_config"]["backend"], "none") + self.assertTrue(result["quantization_config"]["disabled"]) + recipe = build_execution_recipe(quantization_config=result["quantization_config"]) + self.assertIsNone(recipe["quantization_config"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_diffusers_video_registry.py b/tests/test_diffusers_video_registry.py new file mode 100644 index 0000000..bc2b68e --- /dev/null +++ b/tests/test_diffusers_video_registry.py @@ -0,0 +1,1160 @@ +import inspect +import unittest +from types import SimpleNamespace +from unittest.mock import patch + +import numpy as np + +import modules as module_registry +from modules.DiffusersVideo import ( + BuildShotJobs, + Generate, + GenerateLTX2, + GenerateVideoAudio, + GenerateSequence, + GenerateShotJob, + LoadPipeline, + PlanLongVideo, +) +from modules.DiffusersVideo.main import ( + FRAMEPACK_BASE_REPO, + FRAMEPACK_VISION_REPO, + LTX_DISTILLED_TIMESTEPS, + _resolve_adapter_model_selection, + get_video_pipeline_adapter, +) + + +class DiffusersVideoRegistryTests(unittest.TestCase): + def test_quality_shot_jobs_pair_six_keyframes_with_six_five_second_shots(self): + images = [object() for _ in range(6)] + shots = [ + {"title": f"Shot {index + 1}", "prompt": f"Visible story action {index + 1}", "duration_seconds": 5} + for index in range(6) + ] + + result = BuildShotJobs().execute(shots=shots, opening_images=images, base_seed=100, fps=16) + + self.assertEqual(result["count"], 6) + self.assertEqual(result["planned_duration_seconds"], 30.375) + self.assertEqual([job["num_frames"] for job in result["jobs"]], [81] * 6) + self.assertEqual([job["seed"] for job in result["jobs"]], list(range(100, 106))) + self.assertTrue(all(job["conditioning_strength"] == 0.9 for job in result["jobs"])) + self.assertTrue(all(job["opening_image"] is images[index] for index, job in enumerate(result["jobs"]))) + + def test_quality_shot_jobs_allow_a_per_shot_conditioning_strength_override(self): + result = BuildShotJobs().execute( + shots=[ + {"prompt": "Preserve the parked car while the station door opens.", "duration_seconds": 5, "conditioning_strength": 0.8}, + {"prompt": "The same car drives away through snow.", "duration_seconds": 5}, + ], + opening_images=[object(), object()], + conditioning_strength=0.9, + ) + + self.assertEqual([job["conditioning_strength"] for job in result["jobs"]], [0.8, 0.9]) + + def test_quality_shot_jobs_preserve_explicit_zero_controls(self): + result = BuildShotJobs().execute( + shots=[{"prompt": "Release every conditioning control.", "duration_seconds": 5}], + opening_images=[object()], + guidance_scale=0, + secondary_guidance_scale=0, + conditioning_strength=0, + ) + + job = result["jobs"][0] + self.assertEqual(job["guidance_scale"], 0) + self.assertEqual(job["secondary_guidance_scale"], 0) + self.assertEqual(job["conditioning_strength"], 0) + + def test_quality_shot_jobs_reject_short_shots_and_mismatched_keyframes(self): + with self.assertRaisesRegex(ValueError, "at least 5 seconds"): + BuildShotJobs().execute( + shots=[{"prompt": "Too short", "duration_seconds": 4.9}], + opening_images=[object()], + ) + + def test_quality_text_shot_jobs_do_not_require_keyframes(self): + result = BuildShotJobs().execute( + shots=[{"prompt": "A photoreal drone crosses the restored greenhouse.", "duration_seconds": 5}], + mode="text_to_video", + fps=24, + width=1280, + height=704, + steps=50, + ) + + self.assertEqual(result["jobs"][0]["mode"], "text_to_video") + self.assertIsNone(result["jobs"][0]["opening_image"]) + self.assertEqual(result["jobs"][0]["num_frames"], 121) + with self.assertRaisesRegex(ValueError, "needs 2 opening images"): + BuildShotJobs().execute( + shots=[ + {"prompt": "First", "duration_seconds": 5}, + {"prompt": "Second", "duration_seconds": 5}, + ], + opening_images=[object()], + ) + + def test_generate_shot_job_maps_the_normalized_record_to_the_generic_generator(self): + pipeline = object() + opening = object() + ending = object() + output = {"video_out": ["frame"], "frames_out": 1} + with patch.object(Generate, "execute", return_value=output) as execute: + result = GenerateShotJob().execute( + pipeline=pipeline, + job={ + "prompt": "A purposeful camera move reveals the restored seed vault.", + "opening_image": opening, + "ending_image": ending, + "negative_prompt": "flicker", + "num_frames": 81, + "fps": 16, + "steps": 40, + "guidance_scale": 3.5, + "secondary_guidance_scale": 3.25, + "conditioning_strength": 0.7, + "seed": 77, + }, + ) + + self.assertEqual(result["fps_out"], 16) + self.assertEqual(result["width_out"], 832) + self.assertEqual(result["height_out"], 480) + self.assertIn("width_out", GenerateShotJob.params) + self.assertIn("height_out", GenerateShotJob.params) + self.assertIs(execute.call_args.kwargs["pipeline"], pipeline) + self.assertEqual(execute.call_args.kwargs["reference_images"], [opening]) + self.assertIs(execute.call_args.kwargs["last_image"], ending) + self.assertEqual(execute.call_args.kwargs["num_frames"], 81) + self.assertEqual(execute.call_args.kwargs["secondary_guidance_scale"], 3.25) + self.assertEqual(execute.call_args.kwargs["strength"], 0.7) + + def test_generate_shot_job_preserves_explicit_zero_controls(self): + with patch.object(Generate, "execute", return_value={"video_out": [], "frames_out": 0}) as execute: + GenerateShotJob().execute( + pipeline=object(), + job={ + "prompt": "Unconditioned motion fixture.", + "opening_image": object(), + "guidance_scale": 0, + "secondary_guidance_scale": 0, + "conditioning_strength": 0, + }, + ) + + self.assertEqual(execute.call_args.kwargs["guidance_scale"], 0) + self.assertEqual(execute.call_args.kwargs["secondary_guidance_scale"], 0) + self.assertEqual(execute.call_args.kwargs["strength"], 0) + + def test_long_video_planner_emits_loop_ready_ltx_chunks_and_one_framepack_job(self): + ltx = PlanLongVideo().execute( + prompt="A continuous tracking shot", + target_seconds=30, + fps=16, + strategy="ltx_continuation", + chunk_seconds=5, + overlap_seconds=0.25, + seed=40, + ) + framepack = PlanLongVideo().execute( + prompt="A continuous tracking shot", + target_seconds=30, + fps=16, + strategy="framepack_continuous", + seed=40, + ) + + self.assertGreater(ltx["job_count"], 1) + self.assertTrue(all((job["num_frames"] - 1) % 8 == 0 for job in ltx["jobs"])) + self.assertTrue(ltx["jobs"][1]["uses_previous_last_frame"]) + self.assertEqual(framepack["job_count"], 1) + self.assertEqual(framepack["planned_frames"], 480) + + def test_facade_has_normalized_contract(self): + self.assertEqual(LoadPipeline.category, "Diffusers Video") + self.assertEqual(LoadPipeline.params["pipeline"]["type"], "video_diffusion_pipeline") + self.assertIn("pipeline_class", LoadPipeline.params) + self.assertEqual(Generate.params["pipeline"]["type"], "video_diffusion_pipeline") + for name in ("video", "mask", "reference_images", "video_out"): + self.assertIn(name, Generate.params) + + def test_graph_contract_marks_core_video_inputs_without_requiring_mode_specific_media(self): + self.assertTrue(Generate.params["pipeline"]["required"]) + for name in ("last_image", "pose_video", "face_video", "background_video"): + with self.subTest(input=name): + self.assertFalse(Generate.params[name]["required"]) + self.assertFalse(BuildShotJobs.params["ending_images"]["required"]) + self.assertTrue(GenerateShotJob.params["pipeline"]["required"]) + self.assertTrue(GenerateShotJob.params["job"]["required"]) + + def test_unknown_pipeline_is_rejected_before_model_load(self): + with self.assertRaisesRegex(ValueError, "Unsupported Diffusers video pipeline"): + get_video_pipeline_adapter("UnknownVideoPipeline") + + def test_sequence_generator_reuses_one_generic_pipeline_for_multiple_shots(self): + class FakePipeline: + _modiff_video_pipeline_class = "LTXConditionPipeline" + + outputs = [ + {"video_out": ["a1", "a2"], "frames_out": 2}, + {"video_out": ["b1", "b2"], "frames_out": 2}, + ] + with patch.object(Generate, "execute", side_effect=outputs) as execute: + result = GenerateSequence().execute( + pipeline=FakePipeline(), + prompts_json='["First moving shot", "Second moving shot"]', + seed=100, + ) + self.assertEqual(result["clips"], [["a1", "a2"], ["b1", "b2"]]) + self.assertEqual(result["video_out"], ["a1", "a2", "b1", "b2"]) + self.assertEqual(result["frames_out"], 4) + self.assertEqual(result["total_frames"], 4) + self.assertEqual(execute.call_args_list[0].kwargs["seed"], 100) + self.assertEqual(execute.call_args_list[1].kwargs["seed"], 101) + + def test_sequence_generator_accepts_explicit_model_neutral_shot_seeds(self): + class FakePipeline: + _modiff_video_pipeline_class = "LTXConditionPipeline" + + outputs = [ + {"video_out": ["b1"], "frames_out": 1}, + {"video_out": ["a1"], "frames_out": 1}, + ] + with patch.object(Generate, "execute", side_effect=outputs) as execute: + result = GenerateSequence().execute( + pipeline=FakePipeline(), + prompts_json='[{"prompt":"Second visual first","seed":101},{"prompt":"First visual second","seed":100}]', + seed=900, + ) + self.assertEqual(result["video_out"], ["b1", "a1"]) + self.assertEqual(execute.call_args_list[0].kwargs["seed"], 101) + self.assertEqual(execute.call_args_list[1].kwargs["seed"], 100) + + def test_wan_vace_legacy_adapter_covers_all_studio_modes(self): + adapter = get_video_pipeline_adapter("WanVACEPipeline") + self.assertEqual(adapter.default_repo, "Wan-AI/Wan2.1-VACE-1.3B-diffusers") + self.assertIn("video_inpaint", adapter.modes) + self.assertIn("reference_to_video", adapter.modes) + + def test_wan_22_image_to_video_uses_quality_first_five_second_contract(self): + adapter = get_video_pipeline_adapter("WanImageToVideoPipeline") + self.assertEqual(adapter.default_repo, "Wan-AI/Wan2.2-I2V-A14B-Diffusers") + self.assertEqual(adapter.modes, ("image_to_video",)) + + class Output: + frames = [["frame-a", "frame-b"]] + + class FakePipeline: + _modiff_video_pipeline_class = "WanImageToVideoPipeline" + _execution_device = "cpu" + vae_scale_factor_spatial = 8 + transformer = SimpleNamespace(config=SimpleNamespace(patch_size=(1, 2, 2))) + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + pipeline = FakePipeline() + opening = object() + ending = object() + result = Generate().execute( + pipeline=pipeline, + mode="image_to_video", + reference_images=[opening], + last_image=ending, + prompt="A cinematic realistic rescue unfolds in one controlled shot.", + negative_prompt="low quality, deformed anatomy, flicker", + width=832, + height=480, + num_frames=81, + num_inference_steps=40, + guidance_scale=3.5, + secondary_guidance_scale=3.25, + seed=12, + ) + + call = pipeline.calls[0] + self.assertEqual(result["frames_out"], 2) + self.assertIs(call["image"], opening) + self.assertIs(call["last_image"], ending) + self.assertEqual(call["num_frames"], 81) + self.assertEqual(call["num_inference_steps"], 40) + self.assertEqual(call["guidance_scale"], 3.5) + self.assertEqual(call["guidance_scale_2"], 3.25) + + def test_wan_22_ti2v_5b_uses_official_five_second_defaults(self): + adapter = get_video_pipeline_adapter("WanTI2VPipeline") + self.assertEqual(adapter.default_repo, "Wan-AI/Wan2.2-TI2V-5B-Diffusers") + self.assertEqual(adapter.modes, ("text_to_video",)) + + class Output: + frames = [["frame"]] + + class FakePipeline: + _modiff_video_pipeline_class = "WanTI2VPipeline" + _execution_device = "cpu" + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + pipeline = FakePipeline() + result = Generate().execute(pipeline=pipeline, mode="text_to_video", prompt="A cinematic seed-vault rescue.") + + call = pipeline.calls[0] + self.assertEqual(result["frames_out"], 1) + self.assertEqual(call["width"], 1280) + self.assertEqual(call["height"], 704) + self.assertEqual(call["num_frames"], 121) + self.assertEqual(call["num_inference_steps"], 50) + + def test_generic_video_seed_supports_published_large_seed_examples(self): + self.assertGreaterEqual(Generate.params["seed"]["max"], 898471028164125) + + def test_wan_22_ti2v_5b_loader_preserves_native_flow_shift(self): + adapter = get_video_pipeline_adapter("WanTI2VPipeline") + vae = object() + scheduler = SimpleNamespace(config={"flow_shift": 5.0}) + pipeline = SimpleNamespace(scheduler=scheduler) + node = LoadPipeline() + + with ( + patch("diffusers.AutoencoderKLWan.from_pretrained", return_value=vae), + patch("diffusers.WanPipeline.from_pretrained", return_value=pipeline), + patch( + "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler.from_config" + ) as replace_scheduler, + patch("modules.DiffusersRuntime.main.apply_execution_recipe_to_pipeline"), + patch.object(node, "progress"), + patch.object(node, "mm_add"), + patch("modules.DiffusersVideo.main.apply_pipeline_offload"), + ): + loaded = node._load_wan_text_to_video( + adapter, + { + "model_id": {"source": "hub", "value": adapter.default_repo}, + "dtype": "bfloat16", + "device": "cpu", + "execution_recipe": {"offload_mode": "none", "device": "cpu"}, + }, + ) + + self.assertIs(loaded, pipeline) + self.assertIs(loaded.scheduler, scheduler) + self.assertEqual(loaded.scheduler.config["flow_shift"], 5.0) + replace_scheduler.assert_not_called() + + def test_wan_text_generation_can_override_flow_shift_for_a_locked_recipe(self): + original_scheduler = SimpleNamespace(config={"flow_shift": 5.0}) + replacement_scheduler = SimpleNamespace(config={"flow_shift": 8.0}) + + class Output: + frames = [["frame"]] + + class FakePipeline: + _modiff_video_pipeline_class = "WanTI2VPipeline" + _execution_device = "cpu" + + def __init__(self): + self.scheduler = original_scheduler + + def __call__(self, **_kwargs): + self.scheduler_during_call = self.scheduler + return Output() + + pipeline = FakePipeline() + with patch( + "diffusers.UniPCMultistepScheduler.from_config", return_value=replacement_scheduler + ) as replace_scheduler: + Generate().execute( + pipeline=pipeline, + mode="text_to_video", + prompt="A street musician in a subway station.", + scheduler_flow_shift=8, + ) + + replace_scheduler.assert_called_once_with(original_scheduler.config, flow_shift=8.0) + self.assertIs(pipeline.scheduler_during_call, replacement_scheduler) + self.assertIs(pipeline.scheduler, original_scheduler) + + def test_wan_21_t2v_13b_loader_uses_upstream_quality_flow_shift(self): + adapter = get_video_pipeline_adapter("WanPipeline") + vae = object() + scheduler = SimpleNamespace(config={"flow_shift": 3.0}) + replacement_scheduler = SimpleNamespace(config={"flow_shift": 8.0}) + pipeline = SimpleNamespace(scheduler=scheduler) + node = LoadPipeline() + + with ( + patch("diffusers.AutoencoderKLWan.from_pretrained", return_value=vae), + patch("diffusers.WanPipeline.from_pretrained", return_value=pipeline), + patch( + "diffusers.schedulers.scheduling_unipc_multistep.UniPCMultistepScheduler.from_config", + return_value=replacement_scheduler, + ) as replace_scheduler, + patch("modules.DiffusersRuntime.main.apply_execution_recipe_to_pipeline"), + patch.object(node, "progress"), + patch.object(node, "mm_add"), + patch("modules.DiffusersVideo.main.apply_pipeline_offload"), + ): + loaded = node._load_wan_text_to_video( + adapter, + { + "model_id": {"source": "hub", "value": adapter.default_repo}, + "dtype": "bfloat16", + "device": "cpu", + "execution_recipe": {"offload_mode": "none", "device": "cpu"}, + }, + ) + + self.assertIs(loaded.scheduler, replacement_scheduler) + replace_scheduler.assert_called_once_with(scheduler.config, flow_shift=8.0) + + def test_wan_22_quality_contract_rejects_shorter_than_five_second_clips(self): + class FakePipeline: + _modiff_video_pipeline_class = "WanImageToVideoPipeline" + _execution_device = "cpu" + vae_scale_factor_spatial = 8 + transformer = SimpleNamespace(config=SimpleNamespace(patch_size=(1, 2, 2))) + + def __call__(self, **_kwargs): + raise AssertionError("short quality request must not execute") + + with self.assertRaisesRegex(ValueError, "at least 81 frames"): + Generate().execute( + pipeline=FakePipeline(), + mode="image_to_video", + reference_images=[object()], + prompt="A realistic shot.", + width=832, + height=480, + num_frames=65, + ) + + def test_wan_22_image_to_video_loader_preserves_fp32_vae_and_recipe(self): + adapter = get_video_pipeline_adapter("WanImageToVideoPipeline") + vae = object() + pipeline = SimpleNamespace() + node = LoadPipeline() + + with ( + patch("diffusers.AutoencoderKLWan.from_pretrained", return_value=vae) as load_vae, + patch("diffusers.WanImageToVideoPipeline.from_pretrained", return_value=pipeline) as load_pipeline, + patch("modules.DiffusersRuntime.main.apply_execution_recipe_to_pipeline") as apply_recipe, + patch.object(node, "progress"), + patch.object(node, "mm_add"), + patch("modules.DiffusersVideo.main.apply_pipeline_offload"), + ): + loaded = node._load_wan_image_to_video( + adapter, + { + "model_id": {"source": "hub", "value": adapter.default_repo}, + "dtype": "bfloat16", + "device": "cpu", + "execution_recipe": { + "offload_mode": "model_cpu", + "device": "cpu", + }, + }, + ) + + self.assertIs(loaded, pipeline) + self.assertEqual(load_vae.call_args.args[0], adapter.default_repo) + self.assertEqual(str(load_vae.call_args.kwargs["torch_dtype"]), "torch.float32") + self.assertIs(load_pipeline.call_args.kwargs["vae"], vae) + self.assertNotIn("quantization_config", load_pipeline.call_args.kwargs) + apply_recipe.assert_called_once() + + def test_ltx_adapter_exposes_only_the_conditioning_modes_supported_by_diffusers(self): + adapter = get_video_pipeline_adapter("LTXConditionPipeline") + self.assertEqual(adapter.default_repo, "Lightricks/LTX-Video-0.9.8-13B-distilled") + self.assertEqual( + adapter.modes, + ("text_to_video", "image_to_video", "video_to_video", "reference_to_video"), + ) + self.assertEqual(adapter.max_prompt_tokens, 128) + self.assertIn("LTXConditionPipeline", LoadPipeline.params["pipeline_class"]["options"]) + + def test_no_offload_ltx_pipeline_loads_directly_on_cuda(self): + adapter = get_video_pipeline_adapter("LTXConditionPipeline") + pipeline = SimpleNamespace() + node = LoadPipeline("ltx-direct-load-test") + node.progress = lambda *args, **kwargs: None + node.mm_add = lambda *args, **kwargs: None + with ( + patch("diffusers.LTXConditionPipeline.from_pretrained", return_value=pipeline) as load_pipeline, + patch("modules.DiffusersRuntime.main.apply_execution_recipe_to_pipeline"), + patch("modules.DiffusersVideo.main.apply_pipeline_offload"), + ): + loaded = node._load_ltx( + adapter, + { + "model_id": adapter.default_repo, + "device": "cuda:0", + "auto_offload": False, + "offload_mode": "none", + }, + ) + + self.assertIs(loaded, pipeline) + self.assertEqual(load_pipeline.call_args.kwargs["device_map"], "cuda") + + def test_ltx_long_adapter_uses_native_sliding_windows_instead_of_independent_clip_chaining(self): + class Output: + frames = [["frame-a", "frame-b"]] + + class FakePipeline: + _modiff_video_pipeline_class = "LTXI2VLongMultiPromptPipeline" + _modiff_video_repo = "Lightricks/LTX-Video-0.9.8-13B-distilled" + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + pipeline = FakePipeline() + opening = object() + result = Generate().execute( + pipeline=pipeline, + mode="image_to_video", + reference_images=[opening], + prompt="A calm animated lighthouse shot with coherent geometry.", + width=832, + height=480, + num_frames=750, + frame_rate=25, + num_inference_steps=8, + guidance_scale=1, + temporal_tile_size=80, + temporal_overlap=24, + seed=42, + ) + + call = pipeline.calls[0] + self.assertEqual(result["frames_out"], 2) + self.assertIs(call["cond_image"], opening) + self.assertEqual(call["num_frames"], 753) + self.assertEqual(call["temporal_tile_size"], 80) + self.assertEqual(call["temporal_overlap"], 24) + self.assertEqual(call["decode_timestep"], 0.05) + self.assertEqual(call["decode_noise_scale"], 0.025) + self.assertEqual(call["callback_on_step_end_tensor_inputs"], []) + + def test_ltx_long_rejects_invalid_overlap_before_execution(self): + class FakePipeline: + _modiff_video_pipeline_class = "LTXI2VLongMultiPromptPipeline" + _modiff_video_repo = "Lightricks/LTX-Video-0.9.8-13B-distilled" + + def __call__(self, **_kwargs): + raise AssertionError("pipeline must not run") + + with self.assertRaisesRegex(ValueError, "overlap must be smaller"): + Generate().execute( + pipeline=FakePipeline(), + mode="image_to_video", + reference_images=[object()], + prompt="A stable animated shot.", + width=832, + height=480, + temporal_tile_size=80, + temporal_overlap=80, + num_inference_steps=8, + guidance_scale=1, + ) + + def test_capable_pipeline_video_audio_contract_returns_both_modalities(self): + class Output: + frames = [["frame-a", "frame-b"]] + audios = np.zeros((1, 2, 240), dtype=np.float32) + + class FakePipeline: + _modiff_video_pipeline_class = "LTX2ConditionPipeline" + _execution_device = "cpu" + vocoder = SimpleNamespace(config={"output_sampling_rate": 24000}) + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + pipeline = FakePipeline() + result = GenerateVideoAudio().execute( + pipeline=pipeline, + mode="image_to_video", + reference_images=[object()], + prompt="A continuous walking shot with natural synchronized ambience", + width=768, + height=512, + num_frames=121, + frame_rate=24, + ) + + self.assertEqual(result["frames_out"], 2) + self.assertEqual(result["sample_rate_out"], 24000) + self.assertEqual(result["audio"]["channels"], 2) + self.assertEqual(len(pipeline.calls[0]["conditions"]), 1) + + def test_video_audio_node_rejects_video_only_pipeline_before_generation(self): + class FakePipeline: + _modiff_video_pipeline_class = "WanPipeline" + + def __call__(self, **_kwargs): + raise AssertionError("video-only pipeline must not run") + + with self.assertRaisesRegex(ValueError, "does not produce synchronized audio"): + GenerateVideoAudio().execute(pipeline=FakePipeline(), mode="text_to_video", prompt="A quiet street.") + + def test_legacy_ltx2_action_uses_generic_video_audio_contract_but_is_hidden(self): + self.assertTrue(issubclass(GenerateLTX2, GenerateVideoAudio)) + self.assertTrue(module_registry.MODULE_MAP["modules.DiffusersVideo"]["GenerateLTX2"]["hidden"]) + self.assertIn("GenerateVideoAudio", module_registry.MODULE_MAP["modules.DiffusersVideo"]) + + def test_wan_animate_requires_aligned_pose_and_face_controls(self): + class Output: + frames = [["animated-a", "animated-b"]] + + class FakePipeline: + _modiff_video_pipeline_class = "WanAnimatePipeline" + _execution_device = "cpu" + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + pipeline = FakePipeline() + result = Generate().execute( + pipeline=pipeline, + mode="character_animate", + reference_images=[object()], + pose_video=["pose-a", "pose-b"], + face_video=["face-a", "face-b"], + width=1280, + height=720, + ) + + self.assertEqual(result["frames_out"], 2) + self.assertEqual(pipeline.calls[0]["mode"], "animate") + with self.assertRaisesRegex(ValueError, "same number of frames"): + Generate().execute( + pipeline=pipeline, + mode="character_animate", + reference_images=[object()], + pose_video=["pose-a"], + face_video=["face-a", "face-b"], + ) + + def test_framepack_adapter_exposes_continuous_image_to_video_contract(self): + adapter = get_video_pipeline_adapter("HunyuanVideoFramepackPipeline") + self.assertEqual(adapter.default_repo, "lllyasviel/FramePackI2V_HY") + self.assertEqual(adapter.modes, ("image_to_video",)) + self.assertIn("last_image", Generate.params) + + class Output: + frames = [["frame-a", "frame-b"]] + + class FakePipeline: + _modiff_video_pipeline_class = "HunyuanVideoFramepackPipeline" + _execution_device = "cpu" + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + pipeline = FakePipeline() + first = object() + last = object() + result = Generate().execute( + pipeline=pipeline, + mode="image_to_video", + reference_images=[first], + last_image=last, + prompt="The camera follows the subject through a continuous action.", + width=512, + height=320, + num_frames=481, + num_inference_steps=4, + framepack_sampling="inverted_anti_drifting", + latent_window_size=9, + seed=11, + ) + + self.assertEqual(result["frames_out"], 2) + self.assertIs(pipeline.calls[0]["image"], first) + self.assertIs(pipeline.calls[0]["last_image"], last) + self.assertEqual(pipeline.calls[0]["num_frames"], 481) + self.assertEqual(pipeline.calls[0]["sampling_type"], "inverted_anti_drifting") + + def test_non_wan_adapter_replaces_only_the_inherited_legacy_model_default(self): + adapter = get_video_pipeline_adapter("HunyuanVideoFramepackPipeline") + inherited = {"source": "hub", "value": "Wan-AI/Wan2.1-VACE-1.3B-diffusers"} + self.assertEqual( + _resolve_adapter_model_selection(adapter, inherited), + {"source": "hub", "value": "lllyasviel/FramePackI2V_HY"}, + ) + + explicit = {"source": "local", "value": "/models/custom-framepack"} + self.assertIs(_resolve_adapter_model_selection(adapter, explicit), explicit) + + def test_framepack_loader_composes_the_official_transformer_base_and_vision_repositories(self): + adapter = get_video_pipeline_adapter("HunyuanVideoFramepackPipeline") + transformer = object() + feature_extractor = object() + image_encoder = object() + pipeline = object() + node = LoadPipeline() + + with ( + patch( + "diffusers.HunyuanVideoFramepackTransformer3DModel.from_pretrained", return_value=transformer + ) as load_transformer, + patch( + "transformers.SiglipImageProcessor.from_pretrained", return_value=feature_extractor + ) as load_processor, + patch("transformers.SiglipVisionModel.from_pretrained", return_value=image_encoder) as load_encoder, + patch("diffusers.HunyuanVideoFramepackPipeline.from_pretrained", return_value=pipeline) as load_pipeline, + patch.object(node, "progress"), + patch.object(node, "mm_add"), + patch("modules.DiffusersVideo.main.apply_pipeline_offload"), + ): + loaded = node._load_framepack( + adapter, + { + "model_id": {"source": "hub", "value": adapter.default_repo}, + "dtype": "bfloat16", + "device": "cpu", + "auto_offload": False, + "offload_mode": "none", + }, + ) + + self.assertIs(loaded, pipeline) + self.assertEqual(load_transformer.call_args.args[0], adapter.default_repo) + self.assertEqual( + load_transformer.call_args.kwargs["revision"], + "86cef4396041b6002c957852daac4c91aaa47c79", + ) + self.assertEqual(load_processor.call_args.args[0], FRAMEPACK_VISION_REPO) + self.assertEqual( + load_processor.call_args.kwargs["revision"], + "45b801affc54ff2af4e5daf1b282e0921901db87", + ) + self.assertEqual(load_encoder.call_args.args[0], FRAMEPACK_VISION_REPO) + self.assertEqual( + load_encoder.call_args.kwargs["revision"], + "45b801affc54ff2af4e5daf1b282e0921901db87", + ) + self.assertEqual(load_pipeline.call_args.args[0], FRAMEPACK_BASE_REPO) + self.assertEqual( + load_pipeline.call_args.kwargs["revision"], + "e8c2aaa66fe3742a32c11a6766aecbf07c56e773", + ) + self.assertIs(load_pipeline.call_args.kwargs["transformer"], transformer) + self.assertIs(load_pipeline.call_args.kwargs["feature_extractor"], feature_extractor) + self.assertIs(load_pipeline.call_args.kwargs["image_encoder"], image_encoder) + + def test_framepack_rejects_last_image_for_vanilla_sampling(self): + class FakePipeline: + _modiff_video_pipeline_class = "HunyuanVideoFramepackPipeline" + _execution_device = "cpu" + + def __call__(self, **_kwargs): + raise AssertionError("invalid FramePack request must not run") + + with self.assertRaisesRegex(ValueError, "requires inverted_anti_drifting"): + Generate().execute( + pipeline=FakePipeline(), + mode="image_to_video", + reference_images=[object()], + last_image=object(), + framepack_sampling="vanilla", + ) + + def test_ltx_rejects_overlong_prompts_before_pipeline_execution(self): + class FakeTokenizer: + def __call__(self, _text, **_kwargs): + return {"input_ids": list(range(129))} + + class FakePipeline: + _modiff_video_pipeline_class = "LTXConditionPipeline" + _execution_device = "cpu" + tokenizer = FakeTokenizer() + + def __call__(self, **_kwargs): + raise AssertionError("pipeline must not run for an overlong prompt") + + with self.assertRaisesRegex(ValueError, "uses 129 tokens.*at most 128"): + Generate().execute( + pipeline=FakePipeline(), + mode="text_to_video", + prompt="overlong", + width=704, + height=480, + ) + + def test_wan_video_to_video_adapter_uses_the_strength_capable_pipeline(self): + adapter = get_video_pipeline_adapter("WanVideoToVideoPipeline") + self.assertEqual(adapter.default_repo, "Wan-AI/Wan2.1-T2V-1.3B-Diffusers") + self.assertEqual(adapter.modes, ("video_to_video", "video_color_edit")) + + class Output: + frames = [["frame-a", "frame-b"]] + + class FakePipeline: + _modiff_video_pipeline_class = "WanVideoToVideoPipeline" + _execution_device = "cpu" + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + pipeline = FakePipeline() + output = Generate().execute( + pipeline=pipeline, + mode="video_to_video", + video=["source-a", "source-b"], + prompt="Preserve geometry and apply a restrained winter grade.", + width=832, + height=480, + num_inference_steps=4, + guidance_scale=3, + strength=0.35, + seed=9, + ) + + self.assertEqual(output["frames_out"], 2) + self.assertEqual(pipeline.calls[0]["strength"], 0.35) + self.assertEqual(pipeline.calls[0]["video"], ["source-a", "source-b"]) + + def test_base_wan_text_to_video_uses_the_same_generic_node_contract(self): + adapter = get_video_pipeline_adapter("WanPipeline") + self.assertEqual(adapter.default_repo, "Wan-AI/Wan2.1-T2V-1.3B-Diffusers") + self.assertEqual(adapter.modes, ("text_to_video",)) + + class Output: + frames = [["frame-a", "frame-b"]] + + class FakePipeline: + _modiff_video_pipeline_class = "WanPipeline" + _execution_device = "cpu" + vae_scale_factor_temporal = 4 + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + pipeline = FakePipeline() + output = Generate().execute( + pipeline=pipeline, + mode="text_to_video", + prompt="A low camera tracks rapidly through windblown coastal grass.", + width=832, + height=480, + num_frames=81, + num_inference_steps=4, + guidance_scale=5, + seed=12, + ) + + self.assertEqual(output["frames_out"], 2) + self.assertEqual(pipeline.calls[0]["num_frames"], 81) + self.assertNotIn("video", pipeline.calls[0]) + + def test_wan_video_to_video_rejects_vace_only_inputs_before_execution(self): + class FakePipeline: + _modiff_video_pipeline_class = "WanVideoToVideoPipeline" + _execution_device = "cpu" + + def __call__(self, **_kwargs): + raise AssertionError("pipeline must not run for an invalid contract") + + with self.assertRaisesRegex(ValueError, "does not accept a mask"): + Generate().execute( + pipeline=FakePipeline(), + mode="video_to_video", + video=["source"], + mask=["mask"], + ) + + def test_ltx_rejects_incompatible_inputs_before_pipeline_execution(self): + class FakePipeline: + _modiff_video_pipeline_class = "LTXConditionPipeline" + _execution_device = "cpu" + + def __call__(self, **_kwargs): + raise AssertionError("pipeline must not run for an invalid contract") + + node = Generate() + with self.assertRaisesRegex(ValueError, "requires at least one reference image"): + node.execute(pipeline=FakePipeline(), mode="image_to_video", width=704, height=480) + with self.assertRaisesRegex(ValueError, "does not support the generic mask input"): + node.execute( + pipeline=FakePipeline(), + mode="text_to_video", + mask=[object()], + width=704, + height=480, + ) + + def test_ltx_image_condition_uses_the_qualified_one_frame_video_contract(self): + class Output: + frames = [["frame-a"]] + + class FakePipeline: + _modiff_video_pipeline_class = "LTXConditionPipeline" + _execution_device = "cpu" + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + source = object() + pipeline = FakePipeline() + Generate().execute( + pipeline=pipeline, + mode="image_to_video", + reference_images=[source], + prompt="Water pours while the camera moves laterally.", + width=704, + height=480, + num_frames=81, + num_inference_steps=4, + strength=0.85, + ) + + condition = pipeline.calls[0]["conditions"][0] + self.assertEqual(condition.video, [source]) + self.assertIsNone(condition.image) + self.assertEqual(condition.frame_index, 0) + self.assertEqual(condition.strength, 0.85) + self.assertNotIn("image", pipeline.calls[0]) + + def test_ltx_video_input_is_one_condition_not_one_condition_per_frame(self): + class Output: + frames = [["frame-a"]] + + class FakePipeline: + _modiff_video_pipeline_class = "LTXConditionPipeline" + _execution_device = "cpu" + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + source_frames = [object(), object(), object()] + pipeline = FakePipeline() + Generate().execute( + pipeline=pipeline, + mode="video_to_video", + video=source_frames, + prompt="Preserve motion and restyle the season.", + width=704, + height=480, + num_frames=81, + num_inference_steps=4, + strength=0.55, + ) + + conditions = pipeline.calls[0]["conditions"] + self.assertEqual(len(conditions), 1) + self.assertEqual(conditions[0].video, source_frames) + + def test_ltx_distilled_uses_its_eight_step_schedule_without_cfg(self): + class Output: + frames = [["frame-a"]] + + class FakePipeline: + _modiff_video_pipeline_class = "LTXConditionPipeline" + _modiff_video_repo = "Lightricks/LTX-Video-0.9.8-13B-distilled" + _execution_device = "cpu" + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + pipeline = FakePipeline() + Generate().execute( + pipeline=pipeline, + mode="text_to_video", + prompt="A documentary camera flies beside a waterfall.", + negative_prompt="jitter", + width=704, + height=480, + num_frames=81, + num_inference_steps=8, + guidance_scale=1, + ) + + call = pipeline.calls[0] + self.assertEqual(call["timesteps"], LTX_DISTILLED_TIMESTEPS) + self.assertIsNone(call["negative_prompt"]) + + def test_ltx_distilled_rejects_dev_schedule_parameters(self): + class FakePipeline: + _modiff_video_pipeline_class = "LTXConditionPipeline" + _modiff_video_repo = "Lightricks/LTX-Video-0.9.8-13B-distilled" + _execution_device = "cpu" + + def __call__(self, **_kwargs): + raise AssertionError("pipeline must not run with an incompatible distilled schedule") + + with self.assertRaisesRegex(ValueError, "exactly 8 inference steps"): + Generate().execute( + pipeline=FakePipeline(), + mode="text_to_video", + prompt="A documentary shot.", + width=704, + height=480, + num_inference_steps=30, + guidance_scale=1, + ) + with self.assertRaisesRegex(ValueError, "guidance scale 1"): + Generate().execute( + pipeline=FakePipeline(), + mode="text_to_video", + prompt="A documentary shot.", + width=704, + height=480, + num_inference_steps=8, + guidance_scale=3, + ) + + def test_same_ltx_graph_accepts_different_repositories_via_the_adapter_contract(self): + class Output: + frames = [["frame-a", "frame-b"]] + + class FakePipeline: + _modiff_video_pipeline_class = "LTXConditionPipeline" + _execution_device = "cpu" + + def __init__(self, repo): + self.repo = repo + self.calls = [] + + def __call__(self, **kwargs): + self.calls.append(kwargs) + return Output() + + node = Generate() + for repo in ("Lightricks/LTX-Video-0.9.8-13B-distilled", "local/qualified-ltx-repo"): + pipeline = FakePipeline(repo) + output = node.execute( + pipeline=pipeline, + mode="text_to_video", + prompt="A fixed-camera documentary shot.", + width=704, + height=480, + num_frames=96, + num_inference_steps=4, + guidance_scale=3, + seed=7, + ) + self.assertEqual(output["frames_out"], 2) + self.assertEqual(pipeline.calls[0]["num_frames"], 97) + self.assertNotIn("image", pipeline.calls[0]) + + def test_ltx_condition_injects_resolution_dependent_dynamic_scheduler_shift(self): + class Output: + frames = [["frame-a"]] + + class Scheduler: + config = { + "use_dynamic_shifting": True, + "base_image_seq_len": 1024, + "max_image_seq_len": 4096, + "base_shift": 0.95, + "max_shift": 2.05, + } + + def __init__(self): + self.calls = [] + + def set_timesteps( + self, + num_inference_steps=None, + device=None, + sigmas=None, + mu=None, + timesteps=None, + ): + self.calls.append( + ( + (num_inference_steps,), + {"device": device, "sigmas": sigmas, "mu": mu, "timesteps": timesteps}, + ) + ) + + class FakePipeline: + _modiff_video_pipeline_class = "LTXConditionPipeline" + _execution_device = "cpu" + vae_temporal_compression_ratio = 8 + vae_spatial_compression_ratio = 32 + + def __init__(self): + self.scheduler = Scheduler() + + def __call__(self, **_kwargs): + self.scheduler_signature = tuple(inspect.signature(self.scheduler.set_timesteps).parameters) + self.scheduler.set_timesteps(4) + return Output() + + pipeline = FakePipeline() + original_set_timesteps = pipeline.scheduler.set_timesteps + Generate().execute( + pipeline=pipeline, + mode="text_to_video", + prompt="A fixed-camera documentary shot.", + width=704, + height=480, + num_frames=49, + num_inference_steps=4, + guidance_scale=3, + seed=7, + ) + + expected_sequence_length = 7 * 15 * 22 + expected_mu = 0.95 + (expected_sequence_length - 1024) * (2.05 - 0.95) / (4096 - 1024) + self.assertAlmostEqual(pipeline.scheduler.calls[0][1]["mu"], expected_mu) + self.assertIn("timesteps", pipeline.scheduler_signature) + self.assertEqual(pipeline.scheduler.set_timesteps, original_set_timesteps) + + def test_only_generic_video_module_key_is_registered(self): + self.assertNotIn("modules.WanVACE", module_registry.MODULE_MAP) + self.assertIn("modules.DiffusersVideo", module_registry.MODULE_MAP) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_disk_activity.py b/tests/test_disk_activity.py new file mode 100644 index 0000000..6ba5a1e --- /dev/null +++ b/tests/test_disk_activity.py @@ -0,0 +1,48 @@ +import unittest +from pathlib import Path +from sys import path + + +path.insert(0, str(Path(__file__).resolve().parents[1])) + +from modiff.disk_activity import DiskActivityCounters, DiskActivitySampler # noqa: E402 + + +class DiskActivitySamplerTests(unittest.TestCase): + def test_reports_interval_active_time_instead_of_capacity_used(self): + samples = iter( + ( + DiskActivityCounters("disk:0", 400.0, 1_000.0, "unit-test"), + DiskActivityCounters("disk:0", 480.0, 2_000.0, "unit-test"), + ) + ) + sampler = DiskActivitySampler(lambda _path: next(samples)) + + self.assertEqual(sampler.sample("data"), (None, "unit-test")) + active_percent, source = sampler.sample("data") + + self.assertEqual(active_percent, 8.0) + self.assertEqual(source, "unit-test") + + def test_clamps_parallel_or_inconsistent_counters_to_one_hundred_percent(self): + samples = iter( + ( + DiskActivityCounters("disk:0", 0.0, 0.0, "unit-test"), + DiskActivityCounters("disk:0", 1_500.0, 1_000.0, "unit-test"), + ) + ) + sampler = DiskActivitySampler(lambda _path: next(samples)) + + sampler.sample("data") + + self.assertEqual(sampler.sample("data"), (100.0, "unit-test")) + + def test_counter_failure_is_reported_as_unavailable(self): + def fail(_path): + raise OSError("unavailable") + + self.assertEqual(DiskActivitySampler(fail).sample("data"), (None, None)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_graph_catalog_integrity.py b/tests/test_graph_catalog_integrity.py new file mode 100644 index 0000000..89bffcf --- /dev/null +++ b/tests/test_graph_catalog_integrity.py @@ -0,0 +1,193 @@ +import hashlib +import json +import unittest +from collections import defaultdict +from pathlib import Path + +from modiff.model_artifact_catalog import catalog_repository_pin, catalog_revision + + +GRAPH_ROOT = Path(__file__).resolve().parents[1] / "data" / "graphs" +WORKFLOW_MANIFEST = GRAPH_ROOT.parent / "workflow-library-manifest.json" +OUTPUT_NODE_KEYS = { + ("modules.Audio", "Export"), + ("modules.Image", "Preview"), + ("modules.Primitive", "DataViewer"), + ("modules.Video", "Export"), + ("modules.Video", "ExportWithAudio"), +} +DIFFUSERS_PIPELINE_MODULES = { + "modules.DiffusersAudio", + "modules.DiffusersImage", + "modules.DiffusersVideo", +} + + +def _field_value(field): + value = (field or {}).get("value") + if isinstance(value, dict): + return str(value.get("value") or "") + return str(value or "") + + +def _canonical_graph_digest(graph_path): + graph = json.loads(graph_path.read_text(encoding="utf-8")) + canonical = json.dumps(graph, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + return hashlib.sha256(canonical.encode("utf-8")).hexdigest() + + +def _loader_repository(node): + data = node.get("data", {}) + params = data.get("params", {}) + if data.get("action") == "DynamicBlockNode": + return _field_value(params.get("repo_id")) + if data.get("action") == "ModelsLoader": + return _field_value(params.get("repo_id")) + return _field_value(params.get("model_id")) + + +def _is_curated_loader(node): + data = node.get("data", {}) + return ( + data.get("module") in DIFFUSERS_PIPELINE_MODULES + and data.get("action") == "LoadPipeline" + ) or ( + data.get("module") == "modules.ModularDiffusers" + and data.get("action") in {"ModelsLoader", "DynamicBlockNode"} + ) + + +class GraphCatalogIntegrityTests(unittest.TestCase): + def test_curated_graphs_exclude_runtime_and_machine_state(self): + node_runtime_fields = {"measured", "selected", "dragging"} + measured_runtime_fields = {"memoryUsage", "executionTime"} + for graph_path in sorted(GRAPH_ROOT.rglob("*.json")): + graph = json.loads(graph_path.read_text(encoding="utf-8")) + graph_label = str(graph_path.relative_to(GRAPH_ROOT)) + self.assertNotIn("/cache/", json.dumps(graph), f"{graph_label} contains a runtime cache URL") + for node in graph.get("nodes", []): + self.assertEqual( + node_runtime_fields.intersection(node), + set(), + f"{graph_label}:{node.get('id')} contains editor runtime state", + ) + data = node.get("data", {}) + self.assertEqual(measured_runtime_fields.intersection(data), set(), graph_label) + self.assertFalse(data.get("isCached") is True, graph_label) + self.assertFalse(data.get("cache") is True, graph_label) + self.assertFalse(any(value != 0 for value in data.get("time", [])), graph_label) + self.assertFalse(any(value != 0 for value in data.get("memory", [])), graph_label) + device = data.get("params", {}).get("device", {}) + device_options = json.dumps(device.get("options", {})) + for machine_field in ("arch", "name", "total_memory"): + self.assertNotIn( + f'"{machine_field}"', + device_options, + f"{graph_label}:{node.get('id')} contains host-specific device inventory", + ) + + def test_hugging_face_graph_inputs_use_immutable_resolve_revisions(self): + mutable_resolve_markers = ("/resolve/main/", "/resolve/master/") + for graph_path in sorted(GRAPH_ROOT.rglob("*.json")): + graph_text = graph_path.read_text() + for marker in mutable_resolve_markers: + self.assertNotIn( + marker, + graph_text, + f"{graph_path.relative_to(GRAPH_ROOT)} contains a mutable Hugging Face URL", + ) + + def test_workflow_manifest_hashes_match_the_canonical_graphs(self): + manifest = json.loads(WORKFLOW_MANIFEST.read_text()) + workflows = [ + *manifest.get("workflows", []), + *manifest.get("experimentalWorkflows", []), + ] + for workflow in workflows: + graph_path = GRAPH_ROOT / workflow["graphPath"] + digest = _canonical_graph_digest(graph_path) + self.assertEqual(digest, workflow["graphHash"], workflow["graphPath"]) + + def test_curated_hub_loaders_store_the_exact_catalog_revision(self): + checked = 0 + for graph_path in sorted(GRAPH_ROOT.rglob("*.json")): + graph = json.loads(graph_path.read_text()) + for node in graph.get("nodes", []): + if not _is_curated_loader(node): + continue + repo = _loader_repository(node) + expected = catalog_revision(repo) + if expected is None: + continue + checked += 1 + params = node.get("data", {}).get("params", {}) + actual = str((params.get("revision") or {}).get("value") or "") + self.assertEqual(actual, expected, f"{graph_path.relative_to(GRAPH_ROOT)}: {repo}") + + if node.get("data", {}).get("action") == "DynamicBlockNode": + self.assertIs( + (params.get("trust_remote_code") or {}).get("value"), + False, + f"{graph_path.relative_to(GRAPH_ROOT)} must not silently trust remote code", + ) + + pin = catalog_repository_pin(repo) or {} + if node.get("data", {}).get("action") == "LoadPipeline": + self.assertNotEqual( + pin.get("format"), + "gguf", + f"{graph_path.relative_to(GRAPH_ROOT)} passes a GGUF component repo to a full pipeline loader", + ) + self.assertGreater(checked, 0) + + def test_every_catalog_node_contributes_to_a_visible_or_exported_output(self): + graph_paths = sorted(GRAPH_ROOT.rglob("*.json")) + self.assertGreater(len(graph_paths), 0) + + for graph_path in graph_paths: + with self.subTest(graph=graph_path.relative_to(GRAPH_ROOT)): + graph = json.loads(graph_path.read_text()) + nodes = {node["id"]: node for node in graph.get("nodes", [])} + incoming = defaultdict(list) + incident = set() + for edge in graph.get("edges", []): + if edge.get("source") not in nodes or edge.get("target") not in nodes: + continue + incoming[edge["target"]].append(edge["source"]) + incident.update((edge["source"], edge["target"])) + + outputs = [ + node_id + for node_id, node in nodes.items() + if ( + node.get("data", {}).get("module"), + node.get("data", {}).get("action"), + ) + in OUTPUT_NODE_KEYS + ] + self.assertTrue(outputs, "graph has no preview, export, or data-viewer output") + + used = set(outputs) + pending = list(outputs) + while pending: + node_id = pending.pop() + for source_id in incoming[node_id]: + if source_id in used: + continue + used.add(source_id) + pending.append(source_id) + + disabled = [ + node_id + for node_id, node in nodes.items() + if node.get("data", {}).get("uiState", {}).get("disabled") is True + ] + isolated = [node_id for node_id in nodes if node_id not in incident] + unreachable = [node_id for node_id in nodes if node_id not in used] + self.assertEqual(disabled, [], f"disabled execution nodes: {disabled}") + self.assertEqual(isolated, [], f"isolated nodes: {isolated}") + self.assertEqual(unreachable, [], f"nodes outside every output path: {unreachable}") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_graph_queue_ack.py b/tests/test_graph_queue_ack.py new file mode 100644 index 0000000..800d302 --- /dev/null +++ b/tests/test_graph_queue_ack.py @@ -0,0 +1,100 @@ +import asyncio +import json +import os +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from modiff.server import WebServer + + +class GraphQueueAcknowledgementTests(unittest.IsolatedAsyncioTestCase): + async def test_unsupervised_server_cannot_write_a_supervisor_snapshot(self): + with tempfile.TemporaryDirectory() as directory: + with patch.dict(os.environ, {}, clear=False): + os.environ.pop("MODIFF_SUPERVISOR_QUEUE_STATE", None) + server = WebServer(modules={}, work_dir=directory, data_dir=directory) + server.queue_message = lambda *args, **kwargs: None + + await server.queue_task(lambda: None, (), None, "test-session", name="Graph execution") + + self.assertIsNone(server._supervisor_queue_state_path) + self.assertFalse((Path(directory) / "runtime" / "supervisor-queue.json").exists()) + + async def test_replacement_worker_retains_supervisor_terminal_receipts(self): + with tempfile.TemporaryDirectory() as directory: + state_path = Path(directory) / "supervisor-queue.json" + state_path.write_text( + json.dumps( + { + "workerPid": 1234, + "queued": {}, + "current": None, + "recent": [ + { + "task_id": "cancelled-task", + "name": "Graph execution", + "status": "cancelled", + } + ], + } + ), + encoding="utf-8", + ) + with patch.dict(os.environ, {"MODIFF_SUPERVISOR_QUEUE_STATE": str(state_path)}): + server = WebServer(modules={}, work_dir=directory, data_dir=directory) + + self.assertEqual(server.recent_tasks[0]["task_id"], "cancelled-task") + self.assertEqual(server.recent_tasks[0]["status"], "cancelled") + + async def test_terminal_queue_receipt_retains_resource_measurement(self): + server = WebServer(modules={}) + server.current_task = { + "task_id": "qualified-task", + "name": "Graph execution", + "sid": "session", + "started_at": 1.0, + "runtimeFingerprint": "runtime-lock", + "resourceCandidateId": "qwen-native", + "runtimeMeasurement": { + "elapsedSeconds": 12.5, + "peakAllocatedBytes": 8_589_934_592, + }, + } + + current = server._current_task_snapshot() + terminal = server._record_terminal_task("completed") + + self.assertEqual(current["resourceCandidateId"], "qwen-native") + self.assertEqual(current["runtimeMeasurement"]["peakAllocatedBytes"], 8_589_934_592) + self.assertEqual(terminal["runtimeFingerprint"], "runtime-lock") + self.assertEqual(terminal["resourceCandidateId"], "qwen-native") + self.assertEqual(terminal["runtimeMeasurement"]["elapsedSeconds"], 12.5) + + async def test_graph_execution_leaves_time_for_queue_ack_before_model_work(self): + server = WebServer(modules={}) + server.loop = asyncio.get_running_loop() + server.queue_message = lambda *args, **kwargs: None + executor_started = asyncio.Event() + started_at = server.loop.time() + + async def fake_run_executor(callback, *, serialize_model_io=False): + self.assertTrue(serialize_model_io) + self.assertGreaterEqual(server.loop.time() - started_at, 0.04) + executor_started.set() + return callback() + + server.serialize_model_io = True + server._run_executor_callback = fake_run_executor + worker = asyncio.create_task(server._main_worker()) + try: + await server.queue_task(lambda: None, (), None, 'test-session', name='Graph execution') + await asyncio.wait_for(executor_started.wait(), timeout=1) + finally: + server._shutdown_event.set() + await asyncio.wait_for(worker, timeout=2) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_hardware.py b/tests/test_hardware.py index 2999505..b7590f4 100644 --- a/tests/test_hardware.py +++ b/tests/test_hardware.py @@ -3,6 +3,7 @@ import types import unittest from pathlib import Path +from unittest.mock import patch sys.path.insert(0, str(Path(__file__).resolve().parents[1])) @@ -17,16 +18,31 @@ def _raise(message): raise RuntimeError(message) -def _fake_torch(*, cuda, mps_backend=None, mps_runtime=None): +def _fake_torch(*, cuda, xpu=None, mps_backend=None, mps_runtime=None, cuda_version=None, hip_version=None): return types.SimpleNamespace( __version__="test-torch", + version=types.SimpleNamespace(cuda=cuda_version, hip=hip_version), cuda=cuda, + xpu=xpu, backends=types.SimpleNamespace(mps=mps_backend), mps=mps_runtime, ) class HardwareSnapshotTests(unittest.TestCase): + def test_torch_build_backend_versions_are_reported(self): + snapshot = get_hardware_snapshot( + torch_module=_fake_torch( + cuda=types.SimpleNamespace(is_available=lambda: False), + mps_backend=types.SimpleNamespace(is_built=lambda: False, is_available=lambda: False), + cuda_version=None, + hip_version="7.2.0", + ) + ) + + self.assertIsNone(snapshot["torch"]["cuda_version"]) + self.assertEqual(snapshot["torch"]["hip_version"], "7.2.0") + def test_torch_unavailable_returns_cpu_fallback(self): snapshot = get_hardware_snapshot(torch_module=None) @@ -155,6 +171,84 @@ def test_mps_is_represented_before_cpu(self): self.assertEqual(snapshot["devices"][0]["type"], "mps") self.assertEqual(snapshot["default_device"], "mps:0") + def test_intel_xpu_is_a_first_class_accelerator(self): + class FakeXpu: + def is_available(self): + return True + + def device_count(self): + return 1 + + def get_device_name(self, _index): + return "Mock Intel Arc" + + def get_device_properties(self, _index): + return types.SimpleNamespace(total_memory=16 * GIB) + + def mem_get_info(self, _index): + return 12 * GIB, 16 * GIB + + def memory_allocated(self, _index): + return 2 * GIB + + def memory_reserved(self, _index): + return 3 * GIB + + snapshot = get_hardware_snapshot( + torch_module=_fake_torch( + cuda=types.SimpleNamespace(is_available=lambda: False), + xpu=FakeXpu(), + mps_backend=types.SimpleNamespace(is_built=lambda: False, is_available=lambda: False), + ) + ) + + self.assertTrue(snapshot["torch"]["xpu_available"]) + self.assertEqual(snapshot["torch"]["xpu_device_count"], 1) + self.assertEqual([item["device"] for item in snapshot["devices"]], ["xpu:0", "cpu:0"]) + self.assertEqual(snapshot["default_device"], "xpu:0") + self.assertEqual(snapshot["devices"][0]["vram_free"], 12 * GIB) + + def test_rocm_apu_uses_local_vram_for_planning_instead_of_the_gtt_aperture(self): + class FakeRocm: + def is_available(self): + return True + + def device_count(self): + return 1 + + def get_device_name(self, _index): + return "AMD Radeon(TM) Graphics" + + def get_device_properties(self, _index): + return types.SimpleNamespace(total_memory=96 * GIB, gcnArchName="gfx1151:sramecc-") + + def mem_get_info(self, _index): + return 80 * GIB, 96 * GIB + + def memory_allocated(self, _index): + return 1 * GIB + + def memory_reserved(self, _index): + return 2 * GIB + + regions = [{"vram_total": 2 * GIB, "vram_used": GIB, "gtt_total": 96 * GIB, "gtt_used": 4 * GIB}] + with patch("modiff.hardware._linux_amd_memory_regions", return_value=regions): + snapshot = get_hardware_snapshot( + torch_module=_fake_torch( + cuda=FakeRocm(), + mps_backend=types.SimpleNamespace(is_built=lambda: False, is_available=lambda: False), + hip_version="7.2.0", + ) + ) + + device = snapshot["devices"][0] + self.assertEqual(device["backend"], "rocm") + self.assertEqual(device["memory_kind"], "shared") + self.assertEqual(device["planning_memory_total"], 2 * GIB) + self.assertEqual(device["vram_total"], 2 * GIB) + self.assertEqual(device["torch_vram_total"], 96 * GIB) + self.assertEqual(device["shared_memory_total"], 96 * GIB) + def test_snapshot_schema_has_stable_system_and_devices_shape(self): snapshot = get_hardware_snapshot(torch_module=None) @@ -168,6 +262,8 @@ def test_snapshot_schema_has_stable_system_and_devices_shape(self): { "os", "os_name", + "platform", + "architecture", "python_version", "python_executable", "pytorch_version", @@ -188,6 +284,17 @@ def test_snapshot_schema_has_stable_system_and_devices_shape(self): "index", "device", "name", + "vendor", + "backend", + "architecture", + "compute_capability", + "memory_kind", + "dedicated_memory_total", + "dedicated_memory_free", + "shared_memory_total", + "shared_memory_free", + "planning_memory_total", + "planning_memory_free", "vram_total", "vram_free", "torch_vram_total", diff --git a/tests/test_hf_download_concurrency.py b/tests/test_hf_download_concurrency.py new file mode 100644 index 0000000..442e579 --- /dev/null +++ b/tests/test_hf_download_concurrency.py @@ -0,0 +1,115 @@ +import asyncio +import json +import unittest + +from modiff.server import WebServer + + +class FakeRequest: + def __init__(self, **query): + self.query = query + + +class HuggingFaceDownloadConcurrencyTests(unittest.IsolatedAsyncioTestCase): + async def test_shared_memory_runtime_serializes_graph_and_download_model_io(self): + server = WebServer(modules={}) + server.loop = asyncio.get_running_loop() + server.serialize_model_io = True + called = [] + + await server.model_io_lock.acquire() + pending = asyncio.create_task( + server._run_executor_callback(lambda: called.append(True), serialize_model_io=True) + ) + await asyncio.sleep(0.02) + self.assertFalse(called) + self.assertFalse(pending.done()) + + server.model_io_lock.release() + await pending + self.assertTrue(called) + + async def test_discrete_runtime_keeps_model_io_concurrent(self): + server = WebServer(modules={}) + server.loop = asyncio.get_running_loop() + server.serialize_model_io = False + called = [] + + await server.model_io_lock.acquire() + try: + await server._run_executor_callback(lambda: called.append(True), serialize_model_io=True) + finally: + server.model_io_lock.release() + self.assertTrue(called) + + async def test_concurrent_requests_join_one_app_download(self): + server = WebServer(modules={}) + server.loop = asyncio.get_running_loop() + release = asyncio.Event() + calls = [] + + async def fake_download(repo_id, entry): + calls.append((repo_id, entry["task_id"])) + await release.wait() + return {"repo_id": repo_id, "complete": True, "repair_required": False} + + server._run_hf_download_task = fake_download + first = asyncio.create_task(server.hf_download(FakeRequest(repo_id="unit/shared-model"))) + await asyncio.sleep(0) + second = asyncio.create_task(server.hf_download(FakeRequest(repo_id="unit/shared-model"))) + await asyncio.sleep(0) + + self.assertEqual(len(calls), 1) + self.assertEqual(len(server.hf_download_tasks), 1) + release.set() + first_response, second_response = await asyncio.gather(first, second) + first_payload = json.loads(first_response.text) + second_payload = json.loads(second_response.text) + self.assertFalse(first_payload["error"]) + self.assertEqual(first_payload["task_id"], second_payload["task_id"]) + self.assertNotIn("unit/shared-model", server.hf_download_tasks) + + async def test_incomplete_app_download_requires_repair_without_deleting_partial_state(self): + server = WebServer(modules={}) + server.loop = asyncio.get_running_loop() + + async def fake_download(repo_id, entry): + return { + "repo_id": repo_id, + "complete": False, + "repair_required": True, + "validation": {"reason": "One expected shard is incomplete."}, + } + + server._run_hf_download_task = fake_download + response = await server.hf_download(FakeRequest(repo_id="unit/incomplete-model")) + payload = json.loads(response.text) + self.assertEqual(response.status, 409) + self.assertTrue(payload["repair_required"]) + self.assertIn("incomplete", payload["error"]) + + async def test_ltx_app_download_automatically_selects_only_diffusers_component_files(self): + server = WebServer(modules={}) + server.loop = asyncio.get_running_loop() + captured = {} + + async def fake_download(repo_id, entry): + captured.update(entry) + return {"repo_id": repo_id, "complete": True, "repair_required": False} + + server._run_hf_download_task = fake_download + response = await server.hf_download( + FakeRequest(repo_id="Lightricks/LTX-Video-0.9.8-13B-distilled") + ) + payload = json.loads(response.text) + + self.assertFalse(payload["error"]) + self.assertEqual(len(captured["requested_files"]), 22) + self.assertIn("model_index.json", captured["requested_files"]) + self.assertIn("transformer/diffusion_pytorch_model.safetensors.index.json", captured["requested_files"]) + self.assertIn("text_encoder/model-00004-of-00004.safetensors", captured["requested_files"]) + self.assertNotIn("ltxv-13b-0.9.8-dev.safetensors", captured["requested_files"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_hf_download_errors.py b/tests/test_hf_download_errors.py new file mode 100644 index 0000000..236d067 --- /dev/null +++ b/tests/test_hf_download_errors.py @@ -0,0 +1,61 @@ +import unittest +from types import SimpleNamespace + +from modiff.server import WebServer, classify_hf_download_error + + +class FakeResponse: + def __init__(self, status_code): + self.status_code = status_code + + +class GatedRepoError(Exception): + response = FakeResponse(403) + + +class RepositoryNotFoundError(Exception): + response = FakeResponse(404) + + +class HuggingFaceDownloadErrorTests(unittest.TestCase): + def test_gated_repository_has_actionable_non_retryable_error(self): + status, code, message, retryable = classify_hf_download_error(GatedRepoError('restricted')) + self.assertEqual(status, 403) + self.assertEqual(code, 'huggingface_access_required') + self.assertIn('read token', message) + self.assertFalse(retryable) + + def test_network_failure_is_retryable(self): + status, code, message, retryable = classify_hf_download_error(ConnectionError('connection reset')) + self.assertEqual(status, 503) + self.assertEqual(code, 'huggingface_network_error') + self.assertIn('connection reset', message) + self.assertTrue(retryable) + + def test_missing_repository_is_not_misreported_as_a_token_problem(self): + error = RepositoryNotFoundError( + 'Repository Not Found. If this is a private or gated repo, make sure you are authenticated.' + ) + status, code, message, retryable = classify_hf_download_error(error) + self.assertEqual(status, 404) + self.assertEqual(code, 'huggingface_repo_not_found') + self.assertIn('not found', message) + self.assertFalse(retryable) + + +class HuggingFaceDownloadInputTests(unittest.IsolatedAsyncioTestCase): + async def test_download_endpoint_rejects_windows_backslash_repo_escapes_before_queueing(self): + server = object.__new__(WebServer) + request = SimpleNamespace( + can_read_body=False, + query={"repo_id": r"unit\..\..\outside"}, + ) + + response = await WebServer.hf_download(server, request) + + self.assertEqual(response.status, 400) + self.assertIn(b"invalid_huggingface_repo_id", response.body) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_hf_download_progress.py b/tests/test_hf_download_progress.py index fb960dc..218788e 100644 --- a/tests/test_hf_download_progress.py +++ b/tests/test_hf_download_progress.py @@ -1,6 +1,10 @@ import sys +import hashlib +import json import tempfile +import threading import unittest +from concurrent.futures import ThreadPoolExecutor from pathlib import Path from types import SimpleNamespace from unittest.mock import patch @@ -12,6 +16,110 @@ class HuggingFaceDownloadProgressTests(unittest.TestCase): + def test_repo_exists_wrapper_delegates_to_hugging_face_hub(self): + with patch.object(huggingface, "hf_repo_exists", return_value=True) as upstream, patch.object( + huggingface.CONFIG, + "hf", + {**huggingface.CONFIG.hf, "token": "read-token"}, + ): + self.assertTrue(huggingface.repo_exists("unit/model")) + + upstream.assert_called_once_with("unit/model", token="read-token") + + def test_selected_download_ignores_unrelated_stale_partial_blob(self): + with tempfile.TemporaryDirectory() as cache_dir: + repo_id = "unit/selected" + repo_path = Path(cache_dir) / "models--unit--selected" + blobs = repo_path / "blobs" + blobs.mkdir(parents=True) + unrelated_hash = "a" * 64 + (blobs / f"{unrelated_hash}.incomplete").write_bytes(b"stale") + plan = { + "selection_limited": True, + "files": [{"name": "model.safetensors", "size": 10, "blob_hash": "b" * 64}], + } + + snapshot = huggingface._download_progress_snapshot(repo_id, cache_dir, plan) + + self.assertEqual(snapshot["active_files"], []) + + def test_full_repository_download_still_reports_any_partial_blob(self): + with tempfile.TemporaryDirectory() as cache_dir: + repo_id = "unit/full" + repo_path = Path(cache_dir) / "models--unit--full" + blobs = repo_path / "blobs" + blobs.mkdir(parents=True) + partial_hash = "a" * 64 + (blobs / f"{partial_hash}.incomplete").write_bytes(b"partial") + + snapshot = huggingface._download_progress_snapshot(repo_id, cache_dir, {"files": []}) + + self.assertEqual(snapshot["active_files"], [f"blobs/{partial_hash}.incomplete"]) + + def test_verified_public_repair_retries_a_configured_token_403_anonymously(self): + from requests import Response + from huggingface_hub.errors import HfHubHTTPError + + with tempfile.TemporaryDirectory() as cache_dir: + repo_id = "official/public" + source_repo_id = "mirror/public" + payload = b"verified bytes" + digest = hashlib.sha256(payload).hexdigest() + snapshot = Path(cache_dir) / "models--official--public" / "snapshots" / "commit" + snapshot.mkdir(parents=True) + plan = { + "files": [{"name": "model.safetensors", "size": len(payload), "blob_hash": digest}], + } + source_plan = { + "files": [{"name": "model.safetensors", "size": len(payload), "blob_hash": digest}], + } + staged = Path(cache_dir) / "public-model.safetensors" + staged.write_bytes(payload) + response = Response() + response.status_code = 403 + response.url = "https://huggingface.co/public" + forbidden = HfHubHTTPError("forbidden", response=response) + + with patch.object(huggingface, "_repo_download_plan", return_value=source_plan): + with patch.object(huggingface.CONFIG, "hf", {**huggingface.CONFIG.hf, "token": "configured"}): + with patch("huggingface_hub.hf_hub_download", side_effect=[forbidden, str(staged)]) as download: + repaired = huggingface._repair_from_verified_source( + repo_id, source_repo_id, cache_dir, plan + ) + + self.assertEqual(repaired, ["model.safetensors"]) + self.assertEqual(download.call_args_list[0].kwargs["token"], "configured") + self.assertIs(download.call_args_list[1].kwargs["token"], False) + self.assertIs(download.call_args_list[1].kwargs["force_download"], True) + + def test_verified_gated_repair_never_drops_authentication(self): + from requests import Response + from huggingface_hub.errors import HfHubHTTPError + + with tempfile.TemporaryDirectory() as cache_dir: + payload = b"private bytes" + digest = hashlib.sha256(payload).hexdigest() + snapshot = Path(cache_dir) / "models--official--private" / "snapshots" / "commit" + snapshot.mkdir(parents=True) + file_info = {"name": "model.safetensors", "size": len(payload), "blob_hash": digest} + response = Response() + response.status_code = 403 + response.url = "https://huggingface.co/private" + forbidden = HfHubHTTPError("forbidden", response=response) + + with patch.object( + huggingface, + "_repo_download_plan", + return_value={"files": [file_info], "gated": True, "private": False}, + ): + with patch("huggingface_hub.hf_hub_download", side_effect=forbidden) as download: + with self.assertRaises(HfHubHTTPError): + huggingface._repair_from_verified_source( + "official/private", "mirror/private", cache_dir, {"files": [file_info]} + ) + + self.assertEqual(download.call_count, 1) + def test_download_plan_uses_file_metadata_sizes(self): calls = [] @@ -24,8 +132,13 @@ def model_info(self, repo_id, files_metadata=False): if repo_id != "unit/test-model": raise AssertionError(repo_id) return SimpleNamespace(siblings=[ - SimpleNamespace(rfilename="model_index.json", size=2048, lfs=None), - SimpleNamespace(rfilename="transformer/model.safetensors", size=None, lfs={"size": 4096}), + SimpleNamespace(rfilename="model_index.json", size=2048, lfs=None, blob_id=None), + SimpleNamespace( + rfilename="transformer/model.safetensors", + size=None, + lfs={"size": 4096, "sha256": "a" * 64}, + blob_id=None, + ), ]) with patch.object(huggingface, "HfApi", FakeHfApi): @@ -35,6 +148,101 @@ def model_info(self, repo_id, files_metadata=False): self.assertEqual(plan["total_bytes"], 6144) self.assertEqual(plan["total_file_count"], 2) self.assertTrue(plan["size_known"]) + self.assertEqual(plan["files"][1]["blob_hash"], "a" * 64) + + def test_large_download_plan_validates_every_file_but_persists_a_bounded_preview(self): + file_count = 205 + + class FakeHfApi: + def __init__(self, *args, **kwargs): + pass + + def model_info(self, _repo_id, files_metadata=False, **_kwargs): + return SimpleNamespace( + sha="resolved-commit", + siblings=[ + SimpleNamespace(rfilename=f"parts/{index:03d}.bin", size=1, lfs=None, blob_id=None) + for index in range(file_count) + ], + ) + + with tempfile.TemporaryDirectory() as cache_dir, patch.object(huggingface, "HfApi", FakeHfApi): + plan = huggingface._repo_download_plan("unit/large") + snapshot = Path(cache_dir) / "models--unit--large" / "snapshots" / "resolved-commit" + for expected in plan["validation_files"]: + target = snapshot / expected["name"] + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(b"x") + + progress = huggingface._download_progress_snapshot("unit/large", cache_dir, plan) + huggingface._write_repo_download_plan("unit/large", cache_dir, plan) + persisted = json.loads( + (Path(cache_dir) / "models--unit--large" / ".modiff_download_plan.json").read_text() + ) + + self.assertEqual(plan["total_file_count"], file_count) + self.assertEqual(len(plan["validation_files"]), file_count) + self.assertEqual(len(plan["files"]), huggingface.HF_DOWNLOAD_PLAN_FILE_PREVIEW_LIMIT) + self.assertTrue(plan["files_truncated"]) + self.assertEqual(progress["completed_file_count"], file_count) + self.assertEqual(len(persisted["files"]), huggingface.HF_DOWNLOAD_PLAN_FILE_PREVIEW_LIMIT) + self.assertNotIn("validation_files", persisted) + + def test_repo_cache_path_rejects_windows_backslash_traversal(self): + with tempfile.TemporaryDirectory() as cache_dir: + with self.assertRaises((TypeError, ValueError)): + huggingface._repo_cache_dir(r"unit\..\..\outside", cache_dir) + + valid = huggingface._repo_cache_dir("unit/model", cache_dir) + + self.assertEqual(valid.name, "models--unit--model") + + def test_download_plan_can_select_one_pinned_artifact_file(self): + class FakeHfApi: + def __init__(self, *args, **kwargs): + pass + + def model_info(self, _repo_id, files_metadata=False): + return SimpleNamespace(siblings=[ + SimpleNamespace(rfilename="wanted.safetensors", size=8, lfs=None, blob_id=None), + SimpleNamespace(rfilename="training-1000.safetensors", size=8, lfs=None, blob_id=None), + ]) + + with patch.object(huggingface, "HfApi", FakeHfApi): + plan = huggingface._repo_download_plan("unit/adapters", ["wanted.safetensors"]) + + self.assertEqual(plan["total_file_count"], 1) + self.assertEqual(plan["files"][0]["name"], "wanted.safetensors") + + def test_download_plan_expands_hugging_face_allow_pattern_globs(self): + class FakeHfApi: + def __init__(self, *args, **kwargs): + pass + + def model_info(self, _repo_id, files_metadata=False): + return SimpleNamespace(siblings=[ + SimpleNamespace(rfilename="model_index.json", size=2, lfs=None, blob_id=None), + SimpleNamespace(rfilename="transformer/config.json", size=4, lfs=None, blob_id=None), + SimpleNamespace(rfilename="transformer/model-00001-of-00002.safetensors", size=8, lfs=None, blob_id=None), + SimpleNamespace(rfilename="training/checkpoint.safetensors", size=16, lfs=None, blob_id=None), + ]) + + with patch.object(huggingface, "HfApi", FakeHfApi): + plan = huggingface._repo_download_plan( + "unit/selective-model", + ["model_index.json", "transformer/*"], + ) + + self.assertEqual(plan["total_file_count"], 3) + self.assertEqual( + [item["name"] for item in plan["files"]], + [ + "model_index.json", + "transformer/config.json", + "transformer/model-00001-of-00002.safetensors", + ], + ) + self.assertEqual(plan["total_bytes"], 14) def test_progress_snapshot_clamps_completed_expected_files(self): with tempfile.TemporaryDirectory() as temp_dir: @@ -63,6 +271,377 @@ def test_progress_snapshot_clamps_completed_expected_files(self): self.assertEqual(snapshot["current_file"], "blobs/extra.incomplete") self.assertGreaterEqual(snapshot["file_count"], 3) + def test_progress_snapshot_uses_requested_commit_not_newest_snapshot(self): + with tempfile.TemporaryDirectory() as temp_dir: + repo_path = Path(temp_dir) / "models--unit--multi-revision" + requested = repo_path / "snapshots" / "requested-commit" + unrelated = repo_path / "snapshots" / "newer-commit" + requested.mkdir(parents=True) + unrelated.mkdir(parents=True) + (requested / "model.bin").write_bytes(b"requested") + # The unrelated revision is deliberately newer and incomplete. + plan = { + "revision": "release", + "snapshot_commit": "requested-commit", + "files": [{"name": "model.bin", "size": 9}], + "total_file_count": 1, + "total_bytes": 9, + } + + snapshot = huggingface._download_progress_snapshot("unit/multi-revision", temp_dir, plan) + + self.assertEqual(snapshot["completed_file_count"], 1) + self.assertEqual(snapshot["completed_bytes"], 9) + + def test_loader_smoke_parses_expected_diffusers_config(self): + with tempfile.TemporaryDirectory() as temp_dir: + snapshot = Path(temp_dir) / "models--unit--pipeline" / "snapshots" / "revision" + snapshot.mkdir(parents=True) + (snapshot / "model_index.json").write_text('{"_class_name":"FluxPipeline"}', encoding="utf-8") + result = huggingface._loader_config_smoke_summary( + "unit/pipeline", + temp_dir, + {"files": [{"name": "model_index.json", "size": 32}]}, + ) + + self.assertTrue(result["attempted"]) + self.assertTrue(result["complete"]) + self.assertEqual(result["class_name"], "FluxPipeline") + + def test_loader_smoke_rejects_invalid_expected_config(self): + with tempfile.TemporaryDirectory() as temp_dir: + snapshot = Path(temp_dir) / "models--unit--pipeline" / "snapshots" / "revision" + snapshot.mkdir(parents=True) + (snapshot / "model_index.json").write_text('{broken', encoding="utf-8") + result = huggingface._loader_config_smoke_summary( + "unit/pipeline", + temp_dir, + {"files": [{"name": "model_index.json", "size": 7}]}, + ) + + self.assertFalse(result["complete"]) + self.assertIn("not readable JSON", result["reason"]) + + def test_loader_smoke_uses_requested_commit_not_newest_snapshot(self): + with tempfile.TemporaryDirectory() as temp_dir: + repo = Path(temp_dir) / "models--unit--pipeline" + requested = repo / "snapshots" / "requested-commit" + unrelated = repo / "snapshots" / "newer-commit" + requested.mkdir(parents=True) + unrelated.mkdir(parents=True) + (requested / "model_index.json").write_text( + '{"_class_name":"RequestedPipeline"}', encoding="utf-8" + ) + (unrelated / "model_index.json").write_text("{broken", encoding="utf-8") + result = huggingface._loader_config_smoke_summary( + "unit/pipeline", + temp_dir, + { + "revision": "release", + "snapshot_commit": "requested-commit", + "files": [{"name": "model_index.json", "size": 35}], + }, + ) + + self.assertTrue(result["complete"]) + self.assertEqual(result["class_name"], "RequestedPipeline") + + def test_repair_preserves_valid_blobs_and_invalidates_only_wrong_size_files(self): + with tempfile.TemporaryDirectory() as temp_dir: + repo = Path(temp_dir) / "models--unit--repair" + blobs = repo / "blobs" + snapshot = repo / "snapshots" / "revision" + blobs.mkdir(parents=True) + snapshot.mkdir(parents=True) + valid_blob = blobs / "valid-hash" + invalid_blob = blobs / "invalid-hash" + valid_blob.write_bytes(b"v" * 8) + invalid_blob.write_bytes(b"x" * 3) + (snapshot / "valid.bin").symlink_to(valid_blob) + (snapshot / "invalid.bin").symlink_to(invalid_blob) + (blobs / "valid-hash.old.incomplete").write_bytes(b"redundant") + resumable = blobs / "missing-hash.session.incomplete" + resumable.write_bytes(b"partial") + zero_partial = blobs / "zero-hash.session.incomplete" + zero_partial.write_bytes(b"") + plan = { + "files": [ + {"name": "valid.bin", "size": 8}, + {"name": "invalid.bin", "size": 8}, + ] + } + + result = huggingface._prepare_snapshot_repair("unit/repair", temp_dir, plan) + self.assertTrue(valid_blob.exists()) + self.assertTrue((snapshot / "valid.bin").exists()) + self.assertFalse(invalid_blob.exists()) + self.assertFalse((snapshot / "invalid.bin").exists()) + self.assertTrue(resumable.exists()) + self.assertFalse(zero_partial.exists()) + self.assertTrue(result["removed"]) + + removed = huggingface._cleanup_redundant_incomplete_files("unit/repair", temp_dir) + self.assertFalse((blobs / "valid-hash.old.incomplete").exists()) + self.assertTrue(resumable.exists()) + self.assertEqual(removed, []) + + def test_repair_only_invalidates_files_in_requested_commit(self): + with tempfile.TemporaryDirectory() as temp_dir: + repo = Path(temp_dir) / "models--unit--multi-repair" + blobs = repo / "blobs" + requested = repo / "snapshots" / "requested-commit" + unrelated = repo / "snapshots" / "newer-commit" + blobs.mkdir(parents=True) + requested.mkdir(parents=True) + unrelated.mkdir(parents=True) + requested_blob = blobs / "requested-bad" + unrelated_blob = blobs / "unrelated" + requested_blob.write_bytes(b"bad") + unrelated_blob.write_bytes(b"also-bad") + (requested / "model.bin").symlink_to(requested_blob) + (unrelated / "model.bin").symlink_to(unrelated_blob) + plan = { + "revision": "release", + "snapshot_commit": "requested-commit", + "files": [{"name": "model.bin", "size": 16}], + } + + result = huggingface._prepare_snapshot_repair("unit/multi-repair", temp_dir, plan) + + self.assertFalse((requested / "model.bin").exists()) + self.assertFalse(requested_blob.exists()) + self.assertTrue((unrelated / "model.bin").exists()) + self.assertTrue(unrelated_blob.exists()) + self.assertTrue(result["removed"]) + + def test_repair_promotes_a_complete_verified_xet_partial(self): + with tempfile.TemporaryDirectory() as temp_dir: + repo = Path(temp_dir) / "models--unit--promote" + blobs = repo / "blobs" + snapshot = repo / "snapshots" / "revision" + blobs.mkdir(parents=True) + snapshot.mkdir(parents=True) + content = b"complete verified partial" + blob_hash = hashlib.sha256(content).hexdigest() + final_blob = blobs / blob_hash + partial = blobs / f"{blob_hash}.session.incomplete" + partial.write_bytes(content) + (snapshot / "model.bin").symlink_to(final_blob) + plan = {"files": [{"name": "model.bin", "size": len(content)}]} + + result = huggingface._prepare_snapshot_repair("unit/promote", temp_dir, plan) + + self.assertFalse(partial.exists()) + self.assertEqual(final_blob.read_bytes(), content) + self.assertEqual(result["promoted"], [f"blobs/{blob_hash}"]) + + def test_repair_discards_a_full_size_partial_with_the_wrong_hash(self): + with tempfile.TemporaryDirectory() as temp_dir: + repo = Path(temp_dir) / "models--unit--invalid-partial" + blobs = repo / "blobs" + snapshot = repo / "snapshots" / "revision" + blobs.mkdir(parents=True) + snapshot.mkdir(parents=True) + expected_content = b"expected content" + blob_hash = hashlib.sha256(expected_content).hexdigest() + partial = blobs / f"{blob_hash}.session.incomplete" + partial.write_bytes(b"corrupt! content") + self.assertEqual(partial.stat().st_size, len(expected_content)) + (snapshot / "model.bin").symlink_to(blobs / blob_hash) + plan = {"files": [{"name": "model.bin", "size": len(expected_content)}]} + + result = huggingface._prepare_snapshot_repair("unit/invalid-partial", temp_dir, plan) + + self.assertFalse(partial.exists()) + self.assertIn(f"blobs/{partial.name}", result["removed"]) + + def test_repair_uses_automatic_resume_without_forcing_valid_blob_downloads(self): + plan = {"files": [], "total_bytes": 0, "total_file_count": 0, "size_known": True} + validation = {"complete": True, "repair_required": False} + xet_flags = [] + + def record_snapshot_download(**kwargs): + from huggingface_hub import constants as hf_constants + + xet_flags.append(hf_constants.HF_HUB_DISABLE_XET) + + with tempfile.TemporaryDirectory() as temp_dir, patch.dict( + huggingface.CONFIG.hf, {"cache_dir": temp_dir, "token": None} + ), patch.object(huggingface, "_repo_download_plan", return_value=plan), patch.object( + huggingface, "_repair_validation_summary", return_value=validation + ), patch("huggingface_hub.snapshot_download", side_effect=record_snapshot_download) as snapshot_download: + from huggingface_hub import constants as hf_constants + + original_disable_xet = hf_constants.HF_HUB_DISABLE_XET + result = huggingface.download_hub_model("unit/repair", repair=True) + + self.assertTrue(result["complete"]) + self.assertFalse(snapshot_download.call_args.kwargs["force_download"]) + # huggingface_hub 1.x always resumes cache downloads and deprecated the + # explicit resume_download argument. Omitting it also avoids warning + # users during every Model Manager repair. + self.assertNotIn("resume_download", snapshot_download.call_args.kwargs) + self.assertEqual(xet_flags, [True]) + self.assertEqual(hf_constants.HF_HUB_DISABLE_XET, original_disable_xet) + + def test_repair_xet_mode_does_not_leak_into_a_concurrent_normal_download(self): + from huggingface_hub import constants as hf_constants + + plan = {"files": [], "total_bytes": 0, "total_file_count": 0, "size_known": True} + validation = {"complete": True, "repair_required": False} + repair_entered = threading.Event() + release_repair = threading.Event() + observed = [] + + def record_snapshot_download(**kwargs): + observed.append((kwargs["repo_id"], hf_constants.HF_HUB_DISABLE_XET)) + if kwargs["repo_id"] == "unit/repair": + repair_entered.set() + self.assertTrue(release_repair.wait(timeout=2)) + + with tempfile.TemporaryDirectory() as temp_dir, patch.dict( + huggingface.CONFIG.hf, {"cache_dir": temp_dir, "token": None} + ), patch.object( + huggingface, "_repo_download_plan", side_effect=lambda *_args: dict(plan) + ), patch.object( + huggingface, "_repair_validation_summary", return_value=validation + ), patch.object( + hf_constants, "HF_HUB_DISABLE_XET", False + ), patch( + "huggingface_hub.snapshot_download", side_effect=record_snapshot_download + ): + with ThreadPoolExecutor(max_workers=2) as executor: + repair_future = executor.submit(huggingface.download_hub_model, "unit/repair", None, True) + self.assertTrue(repair_entered.wait(timeout=2)) + normal_future = executor.submit(huggingface.download_hub_model, "unit/normal") + release_repair.set() + self.assertTrue(repair_future.result(timeout=3)["complete"]) + self.assertTrue(normal_future.result(timeout=3)["complete"]) + + self.assertEqual(observed, [("unit/repair", True), ("unit/normal", False)]) + self.assertFalse(hf_constants.HF_HUB_DISABLE_XET) + + def test_cataloged_download_uses_the_immutable_revision(self): + plan = {"files": [], "total_bytes": 0, "total_file_count": 0, "size_known": True} + validation = {"complete": True, "repair_required": False} + with tempfile.TemporaryDirectory() as temp_dir, patch.dict( + huggingface.CONFIG.hf, {"cache_dir": temp_dir, "token": None} + ), patch.object(huggingface, "_repo_download_plan", return_value=plan) as build_plan, patch.object( + huggingface, "_repair_validation_summary", return_value=validation + ), patch("huggingface_hub.snapshot_download") as snapshot_download: + result = huggingface.download_hub_model("black-forest-labs/FLUX.1-schnell") + + revision = "741f7c3ce8b383c54771c7003378a50191e9efe9" + self.assertTrue(result["complete"]) + build_plan.assert_called_once_with("black-forest-labs/FLUX.1-schnell", [], revision) + self.assertEqual(snapshot_download.call_args.kwargs["revision"], revision) + + def test_download_validation_uses_exact_snapshot_path_returned_by_hub(self): + plan = { + "files": [], + "total_bytes": 0, + "total_file_count": 0, + "size_known": True, + "revision": "main", + "snapshot_commit": None, + } + observed = [] + + def validate(_repo_id, _cache_dir, active_plan): + observed.append(active_plan.get("snapshot_path")) + return {"complete": True, "repair_required": False} + + with tempfile.TemporaryDirectory() as temp_dir: + exact_snapshot = Path(temp_dir) / "models--unit--exact" / "snapshots" / "resolved-commit" + exact_snapshot.mkdir(parents=True) + with patch.dict(huggingface.CONFIG.hf, {"cache_dir": temp_dir, "token": None}), patch.object( + huggingface, "_repo_download_plan", return_value=plan + ), patch.object(huggingface, "_repair_validation_summary", side_effect=validate), patch( + "huggingface_hub.snapshot_download", return_value=str(exact_snapshot) + ): + result = huggingface.download_hub_model("unit/exact", revision="main") + + self.assertTrue(result["complete"]) + self.assertEqual(observed, [str(exact_snapshot)]) + + def test_repair_retries_transient_server_errors_without_forcing_download(self): + plan = {"files": [], "total_bytes": 0, "total_file_count": 0, "size_known": True} + validation = {"complete": True, "repair_required": False} + transient = RuntimeError("temporary CAS failure") + transient.response = SimpleNamespace(status_code=500) + progress_events = [] + with tempfile.TemporaryDirectory() as temp_dir, patch.dict( + huggingface.CONFIG.hf, {"cache_dir": temp_dir, "token": None} + ), patch.object(huggingface, "_repo_download_plan", return_value=plan), patch.object( + huggingface, "_repair_validation_summary", return_value=validation + ), patch("huggingface_hub.snapshot_download", side_effect=[transient, None]) as snapshot_download, patch.object( + huggingface.time, "sleep" + ) as sleep: + result = huggingface.download_hub_model("unit/retry", progress_events.append, repair=True) + + self.assertTrue(result["complete"]) + self.assertEqual(snapshot_download.call_count, 2) + self.assertTrue(all(not call.kwargs["force_download"] for call in snapshot_download.call_args_list)) + sleep.assert_called_once_with(1) + retry = next(event for event in progress_events if event["status"] == "retrying") + self.assertIsNone(retry["error"]) + self.assertIn("temporary CAS failure", retry["last_error"]) + + def test_repair_can_stage_a_byte_identical_file_from_a_verified_source_repo(self): + content = b"byte-identical model shard" + blob_hash = hashlib.sha256(content).hexdigest() + target_plan = { + "files": [{"name": "transformer/shard.bin", "size": len(content), "blob_hash": blob_hash}] + } + source_plan = { + "files": [{"name": "transformer/shard.bin", "size": len(content), "blob_hash": blob_hash}] + } + + with tempfile.TemporaryDirectory() as temp_dir: + snapshot = Path(temp_dir) / "models--unit--target" / "snapshots" / "revision" + snapshot.mkdir(parents=True) + + def fake_download(**kwargs): + staged = Path(kwargs["local_dir"]) / kwargs["filename"] + staged.parent.mkdir(parents=True, exist_ok=True) + staged.write_bytes(content) + return str(staged) + + with patch.object(huggingface, "_repo_download_plan", return_value=source_plan), patch( + "huggingface_hub.hf_hub_download", side_effect=fake_download + ) as download: + repaired = huggingface._repair_from_verified_source( + "unit/target", "unit/source", temp_dir, target_plan + ) + + target = snapshot / "transformer" / "shard.bin" + self.assertEqual(repaired, ["transformer/shard.bin"]) + self.assertEqual(target.read_bytes(), content) + self.assertEqual(target.resolve().name, blob_hash) + self.assertEqual(download.call_args.kwargs["repo_id"], "unit/source") + + def test_repair_source_must_publish_the_same_lfs_hash(self): + content = b"target" + target_hash = hashlib.sha256(content).hexdigest() + source_plan = { + "files": [{"name": "model.bin", "size": len(content), "blob_hash": "f" * 64}] + } + with tempfile.TemporaryDirectory() as temp_dir: + snapshot = Path(temp_dir) / "models--unit--target" / "snapshots" / "revision" + snapshot.mkdir(parents=True) + with patch.object(huggingface, "_repo_download_plan", return_value=source_plan), patch( + "huggingface_hub.hf_hub_download" + ) as download: + repaired = huggingface._repair_from_verified_source( + "unit/target", + "unit/source", + temp_dir, + {"files": [{"name": "model.bin", "size": len(content), "blob_hash": target_hash}]}, + ) + + self.assertEqual(repaired, []) + download.assert_not_called() + if __name__ == "__main__": unittest.main() diff --git a/tests/test_install_guidance.py b/tests/test_install_guidance.py index 7a9b340..1b53f8b 100644 --- a/tests/test_install_guidance.py +++ b/tests/test_install_guidance.py @@ -1,7 +1,11 @@ +import io import json +import re import subprocess import tempfile +import tomllib import unittest +from contextlib import redirect_stdout from pathlib import Path from unittest.mock import patch @@ -10,6 +14,139 @@ class GuidedInstallerTests(unittest.TestCase): + def test_plan_render_is_safe_for_legacy_windows_console_encodings(self): + host = { + "os": "windows", + "architecture": "x86_64", + "candidates": ["nvidia"], + "nvidia_usable": True, + "amd_candidate": False, + "intel_xpu_candidate": False, + "mps_candidate": False, + "wsl": False, + } + args = install.parser().parse_args(["--accelerator", "auto"]) + with patch.object(install, "detect_host", return_value=host): + plan = install.build_plan(args) + + stream = io.TextIOWrapper(io.BytesIO(), encoding="cp1252", errors="strict") + with redirect_stdout(stream): + install._render_plan(plan) + stream.flush() + + def test_readme_documents_the_managed_cross_platform_install_contract(self): + readme = (Path(__file__).parents[1] / "README.md").read_text(encoding="utf-8") + + required_commands = ( + "git clone https://github.com/sdevil7th/MoDiff-client.git MoDiff-client", + "./install.sh --accelerator auto", + r".\install.ps1 -Accelerator auto", + "./install.sh --accelerator auto --system-check", + r".\install.ps1 -Accelerator auto -SystemCheck", + "./install.sh --accelerator auto --resume", + r".\install.ps1 -Accelerator auto -Resume", + "./install.sh --accelerator auto --repair", + r".\install.ps1 -Accelerator auto -Repair", + "./install.sh --accelerator cpu --backend-only", + r".\install.ps1 -Accelerator cpu -BackendOnly", + "./run.sh", + r".\run.ps1", + "curl --fail http://127.0.0.1:8088/health", + ) + for command in required_commands: + with self.subTest(command=command): + self.assertIn(command, readme) + + command_blocks = re.findall(r"```(?:bash|powershell|sh)?\n(.*?)```", readme, flags=re.DOTALL) + unsupported = re.compile(r"(?m)^\s*uv\s+(?:sync|run)\b") + self.assertFalse( + any(unsupported.search(block) for block in command_blocks), + "README command blocks must use the managed installer instead of uv sync/uv run", + ) + + def test_archive_member_destination_rejects_escaping_paths(self): + unsafe_names = ( + "../escape", + "nested/../../escape", + "/absolute/path", + r"C:\absolute\path", + r"nested\..\escape", + ) + with tempfile.TemporaryDirectory() as temporary: + for member_name in unsafe_names: + with self.subTest(member_name=member_name): + with self.assertRaisesRegex(RuntimeError, "Unsafe|escapes"): + install._archive_member_destination(Path(temporary), member_name) + + def test_archive_member_destination_accepts_nested_relative_path(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self.assertEqual( + install._archive_member_destination(root, "tool/bin/executable"), + (root / "tool" / "bin" / "executable").resolve(), + ) + + def test_executable_project_dependency_pins_the_reviewed_diffusers_commit(self): + project = tomllib.loads((Path(__file__).parents[1] / "pyproject.toml").read_text(encoding="utf-8")) + diffusers = next(item for item in project["project"]["dependencies"] if item.startswith("diffusers")) + self.assertEqual( + diffusers, + "diffusers @ git+https://github.com/huggingface/diffusers.git@13a7bee4878d62fccc8d25f97e480e68de96fa03", + ) + self.assertNotIn("diffusers", project["tool"]["uv"].get("sources", {})) + + def test_direct_hugging_face_hub_import_has_compatible_declared_dependency(self): + project = tomllib.loads((Path(__file__).parents[1] / "pyproject.toml").read_text(encoding="utf-8")) + dependencies = project["project"]["dependencies"] + + self.assertIn("huggingface-hub>=1.23.0,<2.0", dependencies) + + def test_opencv_is_optional_at_runtime_but_available_to_media_tests(self): + root = Path(__file__).parents[1] + project = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8")) + + self.assertFalse(any(item.startswith("opencv-python") for item in project["project"]["dependencies"])) + self.assertEqual( + project["project"]["optional-dependencies"]["gallery-media"], + ["opencv-python-headless>=4.11.0"], + ) + self.assertIn("opencv-python-headless>=4.11.0", (root / "requirements/test.txt").read_text(encoding="utf-8")) + + def test_every_managed_profile_installs_project_dependencies(self): + root = Path(__file__).parents[1] + manifest = install.load_manifest() + + for profile, specification in manifest["profiles"].items(): + with self.subTest(profile=profile): + requirements = (root / specification["requirements"]).read_text(encoding="utf-8") + self.assertRegex(requirements, r"(?m)^-e \.(?:\[[^]]+\])?$") + + def test_uv_project_commands_cannot_replace_the_managed_runtime(self): + project = tomllib.loads((Path(__file__).parents[1] / "pyproject.toml").read_text(encoding="utf-8")) + self.assertIs(project["tool"]["uv"]["managed"], False) + + def test_all_launchers_validate_the_managed_profile_before_starting(self): + root = Path(__file__).parents[1] + linux_launcher = (root / "run.sh").read_text(encoding="utf-8") + runtime_wrapper = (root / "scripts/with-runtime-env.sh").read_text(encoding="utf-8") + windows_launcher = (root / "run.ps1").read_text(encoding="utf-8") + + self.assertIn("modiff.preflight --fail-on-error", linux_launcher) + self.assertIn("scripts/with-runtime-env.sh", linux_launcher) + self.assertNotIn("ROCM_LIBRARY_PATHS", linux_launcher) + self.assertIn('RUNTIME_PROFILE" == "amd-rocm-linux"', runtime_wrapper) + self.assertIn("/opt/rocm/core-*/lib", runtime_wrapper) + self.assertIn('LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH', runtime_wrapper) + subprocess.run( + ["bash", "-n", str(root / "run.sh"), str(root / "scripts/with-runtime-env.sh")], + check=True, + ) + self.assertIn("$preflightCode =", windows_launcher) + self.assertIn("['execution_ready']", windows_launcher) + self.assertNotIn("uv run", linux_launcher) + self.assertNotRegex(linux_launcher, r"exec python(?:3)? main\.py") + self.assertIn("Run ./install.sh before starting", linux_launcher) + def test_structured_issue_contains_help_and_safe_action_metadata(self): issue = enrich_issue( "gpu-groups-missing", "groups required", blocking=True, @@ -28,6 +165,65 @@ def test_system_action_allowlist_rejects_modified_commands(self): self.assertFalse(install._action_is_allowed({"id": "ubuntu-install-amdrocm-gfx1151", "argv": ["apt", "remove", "-y", "amdrocm-gfx1151"]})) self.assertFalse(install._action_is_allowed({"id": "unknown", "argv": ["sh", "-c", "anything"]})) + def test_amd_windows_plan_fails_before_installing_an_unreviewed_runtime(self): + host = { + "os": "windows", + "architecture": "x86_64", + "candidates": ["amd"], + "amd_candidate": True, + "amd_usable": False, + } + args = install.parser().parse_args(["--accelerator", "amd", "--non-interactive"]) + with patch.object(install, "detect_host", return_value=host): + plan = install.build_plan(args) + + self.assertEqual(plan["profile"], "amd-pytorch-windows") + self.assertEqual(plan["support_tier"], "conditional") + self.assertFalse(plan["execution_ready"]) + self.assertIn("amd-windows-install-review-required", [issue["code"] for issue in plan["issues"]]) + + def test_intel_integrated_graphics_selects_the_managed_xpu_preview_profile(self): + host = { + "os": "windows", + "architecture": "x86_64", + "candidates": ["intel"], + "nvidia_usable": False, + "amd_candidate": False, + "intel_xpu_candidate": True, + "mps_candidate": False, + "wsl": False, + } + args = install.parser().parse_args(["--accelerator", "auto", "--non-interactive"]) + with patch.object(install, "detect_host", return_value=host): + plan = install.build_plan(args) + + self.assertEqual(plan["profile"], "intel-xpu") + self.assertEqual(plan["support_tier"], "preview") + self.assertTrue(plan["requirements_exist"]) + self.assertTrue(plan["execution_ready"]) + + def test_windows_plan_reports_powershell_fallback_and_resume_commands(self): + host = { + "os": "windows", + "architecture": "x86_64", + "candidates": ["nvidia"], + "nvidia_usable": True, + "amd_candidate": False, + "intel_xpu_candidate": False, + "mps_candidate": False, + "wsl": False, + } + args = install.parser().parse_args(["--accelerator", "auto"]) + with patch.object(install, "detect_host", return_value=host): + plan = install.build_plan(args) + + self.assertEqual( + plan["cpu_fallback_command"], r".\install.ps1 -Accelerator cpu" + ) + self.assertEqual( + plan["resume_command"], r".\install.ps1 -Accelerator auto -Resume" + ) + def test_group_detection_is_safe_without_posix_grp(self): with patch.object(install, "grp", None): self.assertEqual(install._groups(), []) @@ -42,6 +238,24 @@ def test_malformed_journal_recovers_to_a_new_valid_state(self): self.assertEqual(written["schema_version"], 1) self.assertEqual(json.loads(journal.read_text(encoding="utf-8"))["status"], "running") + def test_successful_journal_update_clears_stale_failure_atomically(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + journal = root / "install-state.json" + journal.write_text( + json.dumps({"status": "failed", "failure": "old failure"}), + encoding="utf-8", + ) + with ( + patch.object(install, "JOURNAL_PATH", journal), + patch.object(install, "MANAGED_ROOT", root), + ): + written = install._write_journal(status="complete", current_phase="complete") + + self.assertNotIn("failure", written) + self.assertNotIn("failure", json.loads(journal.read_text(encoding="utf-8"))) + self.assertFalse(journal.with_suffix(".json.tmp").exists()) + def test_phase_record_updates_matching_setup_steps(self): with tempfile.TemporaryDirectory() as temporary: journal = Path(temporary) / "install-state.json" @@ -70,10 +284,10 @@ def test_backend_only_skips_node_provisioning(self): self.assertEqual(result, {"status": "skipped", "reason": "--backend-only"}) ensure_node.assert_not_called() - def test_missing_sibling_client_skips_node_provisioning(self): + def test_missing_sibling_client_explains_required_layout(self): with patch.object(install, "_client_path", return_value=None), patch.object(install, "_ensure_node") as ensure_node: - result = install._install_client(backend_only=False) - self.assertEqual(result, {"status": "skipped", "reason": "sibling client not found"}) + with self.assertRaisesRegex(RuntimeError, "Sibling MoDiff-client checkout not found"): + install._install_client(backend_only=False) ensure_node.assert_not_called() def test_client_build_provisions_node_on_demand(self): @@ -81,23 +295,201 @@ def test_client_build_provisions_node_on_demand(self): root = Path(temporary) client = root / "MoDiff-client" diagnostics = root / "diagnostics" - client.mkdir() + web = root / "web" + (client / "dist").mkdir(parents=True) + (client / "dist" / "index.html").write_text("new client", encoding="utf-8") + source = client / "src" / "studio" / "templateAssetSource.json" + source.parent.mkdir(parents=True) + source.write_text(json.dumps({"mode": "local"}), encoding="utf-8") + asset_script = client / "scripts" / "template-gallery-assets.py" + asset_script.parent.mkdir(parents=True) + asset_script.write_text("# asset installer\n", encoding="utf-8") + python = root / "python" + python.write_text("", encoding="utf-8") + (web / "user").mkdir(parents=True) + (web / "user" / "keep.txt").write_text("user asset", encoding="utf-8") + (web / "stale.js").write_text("stale", encoding="utf-8") toolchains = {"node": "/tools/node", "npm": "/tools/npm", "node_version": "24.12.0"} completed = subprocess.CompletedProcess([], 0, stdout="ok", stderr="") with ( patch.object(install, "_client_path", return_value=client), patch.object(install, "_ensure_node", return_value=toolchains) as ensure_node, patch.object(install, "DIAGNOSTICS_DIR", diagnostics), + patch.object(install, "WEB_ROOT", web), patch.object(install.subprocess, "run", return_value=completed) as run, ): - result = install._install_client(backend_only=False) - self.assertEqual(result, {"status": "complete", "path": str(client), "node": "24.12.0"}) - ensure_node.assert_called_once_with() - self.assertEqual([call.args[0] for call in run.call_args_list], [["/tools/npm", "ci"], ["/tools/npm", "run", "build"]]) + result = install._install_client(backend_only=False, python=python) + self.assertEqual( + result, + { + "status": "complete", + "path": str(client), + "node": "24.12.0", + "web": str(web.resolve()), + "template_gallery": {"source": "local", "asset_mode": "local"}, + }, + ) + self.assertEqual((web / "index.html").read_text(encoding="utf-8"), "new client") + self.assertEqual((web / "user" / "keep.txt").read_text(encoding="utf-8"), "user asset") + self.assertFalse((web / "stale.js").exists()) + ensure_node.assert_called_once_with() + self.assertEqual( + [call.args[0] for call in run.call_args_list], + [ + ["/tools/npm", "ci"], + [str(python), str(asset_script), "verify"], + ["/tools/npm", "run", "build"], + ], + ) + + def test_remote_gallery_is_downloaded_and_bundled_without_changing_checkout(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + client = root / "client" + web = root / "web" + diagnostics = root / "diagnostics" + managed = root / ".modiff" + gallery = client / "public" / "template-gallery" + gallery.mkdir(parents=True) + (gallery / "checkout-marker.txt").write_text("original", encoding="utf-8") + source = client / "src" / "studio" / "templateAssetSource.json" + source.parent.mkdir(parents=True) + source.write_text( + json.dumps( + { + "mode": "huggingface", + "repoId": "modiff-project/template-gallery", + "revision": "0" * 40, + "assetSetId": "sha256:canonical-json:" + "a" * 64, + } + ), + encoding="utf-8", + ) + asset_script = client / "scripts" / "template-gallery-assets.py" + asset_script.parent.mkdir(parents=True) + asset_script.write_text("# asset installer\n", encoding="utf-8") + python = root / "python" + python.write_text("", encoding="utf-8") + toolchains = { + "node": "/tools/node", + "npm": "/tools/npm", + "node_version": "24.12.0", + } + + def run_step(command, **kwargs): + if "download" in command: + destination = Path(command[command.index("--destination") + 1]) + downloaded = destination / "template-gallery" + downloaded.mkdir(parents=True) + (downloaded / "installed.webp").write_bytes(b"installed") + elif command[-2:] == ["run", "build"]: + self.assertEqual( + kwargs["env"]["VITE_MODIFF_TEMPLATE_ASSET_MODE"], "local" + ) + self.assertEqual( + (gallery / "installed.webp").read_bytes(), b"installed" + ) + self.assertEqual( + (client / ".template-gallery.install-backup" / "checkout-marker.txt").read_text( + encoding="utf-8" + ), + "original", + ) + self.assertFalse( + (client / "public" / ".template-gallery.install-backup").exists() + ) + dist_gallery = client / "dist" / "template-gallery" + dist_gallery.mkdir(parents=True) + (client / "dist" / "index.html").write_text( + "client", encoding="utf-8" + ) + (dist_gallery / "installed.webp").write_bytes(b"installed") + return subprocess.CompletedProcess(command, 0, stdout="ok", stderr="") + + with ( + patch.object(install, "_client_path", return_value=client), + patch.object(install, "_ensure_node", return_value=toolchains), + patch.object(install, "DIAGNOSTICS_DIR", diagnostics), + patch.object(install, "MANAGED_ROOT", managed), + patch.object(install, "WEB_ROOT", web), + patch.object(install.subprocess, "run", side_effect=run_step), + ): + result = install._install_client(backend_only=False, python=python) + + self.assertEqual( + result["template_gallery"]["source"], "huggingface" + ) + self.assertEqual(result["template_gallery"]["asset_mode"], "local") + self.assertEqual( + (web / "template-gallery" / "installed.webp").read_bytes(), + b"installed", + ) + self.assertEqual( + (gallery / "checkout-marker.txt").read_text(encoding="utf-8"), + "original", + ) + self.assertFalse((client / ".template-gallery.install-backup").exists()) + self.assertFalse((client / "dist" / ".template-gallery.install-backup").exists()) + + def test_windows_npm_batch_launcher_uses_node_without_a_shell(self): + with tempfile.TemporaryDirectory() as temporary: + node_root = Path(temporary) + npm = node_root / "npm.cmd" + npm.write_text("batch", encoding="utf-8") + npm_cli = node_root / "node_modules" / "npm" / "bin" / "npm-cli.js" + npm_cli.parent.mkdir(parents=True) + npm_cli.write_text("// npm", encoding="utf-8") + + command = install._npm_command( + {"node": str(node_root / "node.exe"), "npm": str(npm)}, + "run", + "build", + ) + + self.assertEqual(command, [str(node_root / "node.exe"), str(npm_cli), "run", "build"]) + + def test_client_mirror_preserves_an_explicit_offline_gallery_build(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + client = root / "client" + web = root / "web" + (client / "dist" / "template-gallery").mkdir(parents=True) + (client / "dist" / "index.html").write_text("client", encoding="utf-8") + (client / "dist" / "template-gallery" / "video.mp4").write_bytes(b"media") + + install._mirror_client_dist(client, web) + + self.assertEqual((web / "template-gallery" / "video.mp4").read_bytes(), b"media") + + def test_client_mirror_accepts_remote_build_without_gallery_directory(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + client = root / "client" + web = root / "web" + (client / "dist").mkdir(parents=True) + (client / "dist" / "index.html").write_text("remote client", encoding="utf-8") + (web / "template-gallery").mkdir(parents=True) + (web / "template-gallery" / "stale.mp4").write_bytes(b"stale") + + install._mirror_client_dist(client, web) + + self.assertEqual((web / "index.html").read_text(encoding="utf-8"), "remote client") + self.assertFalse((web / "template-gallery").exists()) def test_phase_contract_is_stable(self): self.assertEqual(PHASES, ["detect", "plan", "system-preparation", "toolchain", "backend", "client", "validation", "complete"]) + def test_profile_package_policy_checks_required_and_prohibited_packages(self): + with patch.object( + install, + "load_manifest", + return_value={"profiles": {"test": {"required": ["required-package"], "prohibited": ["bad_package"]}}}, + ): + script = install._profile_package_script("test") + self.assertIn("required-package", script) + self.assertIn("bad_package", script) + self.assertIn("prohibited_installed", script) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_lock_accelerator_wheels.py b/tests/test_lock_accelerator_wheels.py new file mode 100644 index 0000000..4858c45 --- /dev/null +++ b/tests/test_lock_accelerator_wheels.py @@ -0,0 +1,51 @@ +from pathlib import Path +from tempfile import TemporaryDirectory +import unittest + +from scripts.lock_accelerator_wheels import parse_direct_wheel_requirements + + +class DirectWheelRequirementTests(unittest.TestCase): + def _requirements(self, contents: str) -> Path: + temporary = TemporaryDirectory() + self.addCleanup(temporary.cleanup) + path = Path(temporary.name) / "profile.txt" + path.write_text(contents, encoding="utf-8") + return path + + def test_extracts_urls_without_existing_hashes(self): + path = self._requirements( + "# reviewed wheels\n" + "https://example.test/torch.whl --hash=sha256:old\n" + "https://example.test/vision.whl\n" + "-e .[accelerator]\n" + ) + + urls, editable = parse_direct_wheel_requirements(path) + + self.assertEqual( + urls, + [ + "https://example.test/torch.whl", + "https://example.test/vision.whl", + ], + ) + self.assertEqual(editable, "-e .[accelerator]") + + def test_rejects_index_based_profile_before_locking(self): + path = self._requirements( + "torch==2.8.0 --index-url https://download.pytorch.org/whl/cpu\n-e .\n" + ) + + with self.assertRaisesRegex(ValueError, "not direct-wheel-only"): + parse_direct_wheel_requirements(path) + + def test_rejects_profile_without_wheels(self): + path = self._requirements("# unsupported placeholder\n-e .\n") + + with self.assertRaisesRegex(ValueError, "no direct HTTPS wheel URLs"): + parse_direct_wheel_requirements(path) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_main_supervisor.py b/tests/test_main_supervisor.py new file mode 100644 index 0000000..3e6fcda --- /dev/null +++ b/tests/test_main_supervisor.py @@ -0,0 +1,88 @@ +import importlib.util +import os +import signal +import unittest +from pathlib import Path +from unittest.mock import Mock, patch + + +MAIN_PATH = Path(__file__).resolve().parents[1] / "main.py" + + +def load_main_module(): + spec = importlib.util.spec_from_file_location("modiff_main_supervisor_test", MAIN_PATH) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +class MainSupervisorTests(unittest.TestCase): + def test_supervisor_control_plane_ignores_non_loopback_bind_requests(self): + module = load_main_module() + worker = Mock(wait=Mock(return_value=0)) + worker.poll.return_value = None + control_server = Mock() + with ( + patch.object(module.subprocess, "Popen", return_value=worker), + patch.object(module.signal, "signal"), + patch("modiff.supervisor_control.SupervisorControlServer", return_value=control_server) as server_class, + patch.dict( + os.environ, + { + "MODIFF_SUPERVISOR_CONTROL_PORT": "0", + "MODIFF_SUPERVISOR_CONTROL_HOST": "0.0.0.0", + }, + ), + ): + self.assertEqual(module.run_supervisor(), 0) + + self.assertEqual(server_class.call_args.args[1], "127.0.0.1") + control_server.start.assert_called_once_with() + control_server.close.assert_called_once_with() + + def test_forced_cancel_exit_replaces_worker_and_normal_exit_stops(self): + module = load_main_module() + workers = [ + Mock(wait=Mock(return_value=module.SUPERVISED_RESTART_EXIT_CODE)), + Mock(wait=Mock(return_value=0)), + ] + for worker in workers: + worker.poll.return_value = None + + with ( + patch.object(module.subprocess, "Popen", side_effect=workers) as popen, + patch.object(module.signal, "signal"), + patch.dict(os.environ, {"MODIFF_SUPERVISOR_CONTROL_PORT": "0"}), + ): + self.assertEqual(module.run_supervisor(), 0) + + self.assertEqual(popen.call_count, 2) + for call in popen.call_args_list: + command = call.args[0] + worker_env = call.kwargs["env"] + self.assertEqual(command[-1], "--worker") + self.assertEqual(worker_env["MODIFF_WORKER_SUPERVISED"], "1") + + def test_shutdown_signal_is_forwarded_to_the_active_worker(self): + module = load_main_module() + worker = Mock() + worker.poll.return_value = None + captured_handlers = {} + + def remember_handler(sig, handler): + captured_handlers[sig] = handler + + def wait(): + captured_handlers[signal.SIGTERM](signal.SIGTERM, None) + return 0 + + worker.wait.side_effect = wait + with ( + patch.object(module.subprocess, "Popen", return_value=worker), + patch.object(module.signal, "signal", side_effect=remember_handler), + patch.dict(os.environ, {"MODIFF_SUPERVISOR_CONTROL_PORT": "0"}), + ): + self.assertEqual(module.run_supervisor(), 0) + + worker.send_signal.assert_called_once_with(signal.SIGTERM) + self.assertNotIn("MODIFF_WORKER_SUPERVISED", os.environ) diff --git a/tests/test_media_assets.py b/tests/test_media_assets.py new file mode 100644 index 0000000..ee61614 --- /dev/null +++ b/tests/test_media_assets.py @@ -0,0 +1,56 @@ +import tempfile +import unittest +from pathlib import Path + +from modiff.media_assets import cleanup_media_assets, coerce_video_asset, list_media_assets, register_video_asset + + +class MediaAssetTests(unittest.TestCase): + def test_registered_asset_keeps_operation_provenance_and_can_be_coerced(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + path = root / "derived.mp4" + path.write_bytes(b"video") + record = register_video_asset( + path, + asset_id="derived", + width=8, + height=6, + fps=2, + frame_count=4, + source_asset_ids=["source-a"], + operation="trim", + root=root, + ) + + coerced = coerce_video_asset(record) + + self.assertEqual(coerced["path"], str(path.resolve())) + self.assertEqual(coerced["source_asset_ids"], ["source-a"]) + self.assertEqual(coerced["operation"], "trim") + + def test_cleanup_is_scoped_and_never_removes_pinned_or_external_files(self): + with tempfile.TemporaryDirectory() as directory, tempfile.TemporaryDirectory() as external_directory: + root = Path(directory) + run_a = root / "run-a" + run_a.mkdir() + ordinary = run_a / "ordinary.mp4" + pinned = run_a / "pinned.mp4" + external = Path(external_directory) / "external.mp4" + for path in (ordinary, pinned, external): + path.write_bytes(b"video") + register_video_asset(ordinary, asset_id="ordinary", task_id="a", width=8, height=6, fps=2, frame_count=4, root=root) + register_video_asset(pinned, asset_id="pinned", task_id="a", width=8, height=6, fps=2, frame_count=4, pinned=True, root=root) + register_video_asset(external, asset_id="external", task_id="a", width=8, height=6, fps=2, frame_count=4, root=root) + + report = cleanup_media_assets(task_id="a", root=root) + + self.assertFalse(ordinary.exists()) + self.assertTrue(pinned.exists()) + self.assertTrue(external.exists()) + self.assertEqual([item["asset_id"] for item in report["removed"]], ["ordinary"]) + self.assertEqual({item["asset_id"] for item in list_media_assets(root=root)}, {"pinned", "external"}) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_media_import.py b/tests/test_media_import.py new file mode 100644 index 0000000..ccc1b12 --- /dev/null +++ b/tests/test_media_import.py @@ -0,0 +1,159 @@ +import unittest +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import MagicMock, patch + +from modiff.config import CONFIG +from modiff.media_import import ( + _PinnedHTTPConnection, + _public_http_url, + audio_as_wav, + import_root, + import_web_media, + import_youtube_media, +) +from modules.MediaSource.main import LocalMedia, YouTubeMedia + + +class MediaImportTests(unittest.TestCase): + def test_default_import_root_uses_configured_data_directory(self): + self.assertEqual(import_root(), (Path(CONFIG.paths["data"]) / "imports").resolve()) + + @patch("modiff.media_import.socket.getaddrinfo", return_value=[(None, None, None, None, ("127.0.0.1", 0))]) + def test_private_web_hosts_are_rejected(self, _resolve): + with self.assertRaisesRegex(ValueError, "private"): + _public_http_url("http://internal.example/image.png") + + @patch("modiff.media_import.socket.socket") + @patch( + "modiff.media_import.socket.getaddrinfo", + return_value=[(2, 1, 6, "", ("93.184.216.34", 80))], + ) + def test_http_connection_uses_the_validated_numeric_address(self, _resolve, socket_mock): + connection = _PinnedHTTPConnection("example.com", 80, timeout=3) + + connection.connect() + + socket_mock.assert_called_once_with(2, 1) + socket_mock.return_value.connect.assert_called_once_with(("93.184.216.34", 80)) + + @patch("modiff.media_import.socket.socket") + @patch( + "modiff.media_import.socket.getaddrinfo", + return_value=[(2, 1, 6, "", ("127.0.0.1", 80))], + ) + def test_connection_time_dns_rebinding_to_private_address_is_rejected(self, _resolve, socket_mock): + connection = _PinnedHTTPConnection("rebound.example", 80, timeout=3) + + with self.assertRaisesRegex(ValueError, "private"): + connection.connect() + + socket_mock.assert_not_called() + + @patch("modiff.media_import.socket.getaddrinfo", return_value=[(None, None, None, None, ("93.184.216.34", 0))]) + def test_credentials_are_rejected(self, _resolve): + with self.assertRaisesRegex(ValueError, "public HTTP"): + _public_http_url("https://user:pass@example.com/file.png") + + @patch("modiff.media_import.socket.getaddrinfo", return_value=[(None, None, None, None, ("142.250.0.1", 0))]) + def test_youtube_host_allowlist(self, _resolve): + self.assertIn("youtube.com", _public_http_url("https://www.youtube.com/watch?v=test", youtube_only=True)) + with self.assertRaisesRegex(ValueError, "YouTube"): + _public_http_url("https://example.com/watch?v=test", youtube_only=True) + + def test_local_media_returns_existing_absolute_path(self): + with TemporaryDirectory() as directory: + path = Path(directory) / "input.png" + path.write_bytes(b"test") + self.assertEqual(LocalMedia("local").execute(file=str(path))["path"], str(path.resolve())) + + def test_youtube_node_requires_rights_confirmation(self): + with self.assertRaisesRegex(ValueError, "permission"): + YouTubeMedia("youtube").execute(url="https://youtu.be/test", rights_confirmed=False) + + @patch("modiff.media_import.build_opener") + @patch("modiff.media_import.socket.getaddrinfo", return_value=[(None, None, None, None, ("93.184.216.34", 0))]) + def test_web_media_is_content_addressed(self, _resolve, build_opener_mock): + response = MagicMock() + response.headers.get_content_type.return_value = "image/png" + response.headers.get.return_value = str(len(b"png-data")) + response.geturl.return_value = "https://example.com/image.png" + response.read = MagicMock(side_effect=[b"png-data", b""]) + response.__enter__.return_value = response + build_opener_mock.return_value.open.return_value = response + with TemporaryDirectory() as directory: + first = import_web_media("https://example.com/image.png", root=directory) + response.read = MagicMock(side_effect=[b"png-data", b""]) + second = import_web_media("https://example.com/image.png", root=directory) + self.assertEqual(first, second) + self.assertEqual(first.read_bytes(), b"png-data") + + @patch("modiff.media_import._public_http_url", return_value="https://youtu.be/test") + def test_youtube_import_uses_single_item_and_duration_limit(self, _safe_url): + fake_module = MagicMock() + captured = {} + + class FakeDownloader: + def __init__(self, options): + captured.update(options) + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def extract_info(self, _url, download=True): + output = Path(captured["paths"]["home"]) / "test.mp4" + output.write_bytes(b"video") + return {"id": "test", "duration": 12} + + fake_module.YoutubeDL = FakeDownloader + with patch.dict("sys.modules", {"yt_dlp": fake_module}), TemporaryDirectory() as directory: + result = import_youtube_media("https://youtu.be/test", root=directory) + self.assertTrue(result.name.startswith("test-")) + self.assertTrue(captured["noplaylist"]) + self.assertEqual(captured["playlist_items"], "1") + + @patch("subprocess.run") + def test_audio_transcode_is_cached(self, run_mock): + def create_output(command, **_kwargs): + Path(command[-1]).write_bytes(b"wav") + return MagicMock(returncode=0, stderr="", stdout="") + + run_mock.side_effect = create_output + with TemporaryDirectory() as directory: + source = Path(directory) / "source.mp3" + source.write_bytes(b"mp3") + result = audio_as_wav(source, root=directory) + self.assertEqual(result.suffix, ".wav") + self.assertEqual(result.read_bytes(), b"wav") + + @patch("subprocess.run") + def test_concurrent_audio_transcodes_share_the_atomic_cached_result(self, run_mock): + temporary_paths = [] + + def create_output(command, **_kwargs): + temporary = Path(command[-1]) + temporary_paths.append(temporary) + temporary.write_bytes(b"wav") + return MagicMock(returncode=0, stderr="", stdout="") + + run_mock.side_effect = create_output + with TemporaryDirectory() as directory: + source = Path(directory) / "source.mp3" + source.write_bytes(b"mp3") + with ThreadPoolExecutor(max_workers=4) as executor: + results = list(executor.map(lambda _index: audio_as_wav(source, root=directory), range(4))) + leftovers = list((Path(directory) / "audio").glob(".*.wav")) + + self.assertEqual(len(set(results)), 1) + self.assertEqual(run_mock.call_count, 1) + self.assertEqual(len(temporary_paths), 1) + self.assertNotEqual(temporary_paths[0], results[0]) + self.assertEqual(leftovers, []) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_media_io.py b/tests/test_media_io.py new file mode 100644 index 0000000..d6d16a6 --- /dev/null +++ b/tests/test_media_io.py @@ -0,0 +1,222 @@ +from __future__ import annotations + +import subprocess +import wave +from pathlib import Path + +import numpy as np +import pytest +from PIL import Image +from scipy.io import wavfile + +from modiff.media_io import ( + export_media_file, + get_ffmpeg_exe, + media_capabilities, + probe_media_file, +) + + +def _write_tone(path: Path, sample_rate: int = 48000) -> None: + timeline = np.arange(sample_rate // 10, dtype=np.float32) / sample_rate + samples = (np.sin(timeline * 440 * 2 * np.pi) * 0.2 * 32767).astype(np.int16) + wavfile.write(path, sample_rate, samples) + + +def _write_video(path: Path, frame_root: Path) -> None: + frame_root.mkdir(parents=True, exist_ok=True) + for index in range(4): + Image.new("RGB", (160, 120), (20 + index * 40, 70, 160 - index * 20)).save( + frame_root / f"frame-{index:02d}.png" + ) + subprocess.run( + [ + get_ffmpeg_exe(), + "-y", + "-v", + "error", + "-nostdin", + "-framerate", + "4", + "-i", + str(frame_root / "frame-%02d.png"), + "-c:v", + "libx264", + "-pix_fmt", + "yuv420p", + str(path), + ], + check=True, + capture_output=True, + timeout=30, + ) + + +def test_capabilities_only_advertise_runtime_backed_popular_formats(): + capabilities = media_capabilities() + + assert capabilities["version"] == 1 + assert {item["value"] for item in capabilities["media"]["audio"]["exportFormats"]} >= { + "wav", + "flac", + "mp3", + "m4a", + "aac", + "opus", + } + assert {item["value"] for item in capabilities["media"]["image"]["exportFormats"]} >= { + "png", + "jpeg", + "webp", + } + assert {item["value"] for item in capabilities["media"]["video"]["exportFormats"]} >= { + "mp4", + "webm", + "mov", + "gif", + } + + +def test_probe_uses_content_instead_of_extension(tmp_path): + source = tmp_path / "misleading.bin" + Image.new("RGBA", (23, 17), (10, 20, 30, 128)).save(source, format="PNG") + + metadata = probe_media_file(source, "image") + + assert metadata["kind"] == "image" + assert metadata["mimeType"] == "image/png" + assert metadata["width"] == 23 + assert metadata["height"] == 17 + with pytest.raises(ValueError, match="not audio"): + probe_media_file(source, "audio") + + +@pytest.mark.parametrize("format_id", ["wav", "flac", "mp3", "m4a", "aac", "opus"]) +def test_audio_delivery_exports_are_real_decodable_files(tmp_path, format_id): + source = tmp_path / "source.wav" + _write_tone(source) + + exported, mime_type, filename = export_media_file( + source, + kind="audio", + format_id=format_id, + options={"sampleRate": 48000 if format_id == "opus" else 44100}, + cache_root=tmp_path / "exports", + ) + + assert exported.is_file() + assert exported.stat().st_size > 0 + assert filename.endswith(exported.suffix) + assert mime_type.startswith("audio/") + metadata = probe_media_file(exported, "audio") + assert metadata["kind"] == "audio" + expected_rate = 48000 if format_id == "opus" else 44100 + assert metadata["sampleRate"] == expected_rate + + +def test_wav_export_encodes_selected_rate_in_file_header(tmp_path): + source = tmp_path / "source.wav" + _write_tone(source) + + exported, _, _ = export_media_file( + source, + kind="audio", + format_id="wav", + options={"sampleRate": 88200, "bitDepth": 24}, + cache_root=tmp_path / "exports", + ) + + with wave.open(str(exported), "rb") as handle: + assert handle.getframerate() == 88200 + assert handle.getsampwidth() == 3 + + +@pytest.mark.parametrize("format_id", ["png", "jpeg", "webp", "avif", "tiff"]) +def test_image_delivery_exports_are_decodable(tmp_path, format_id): + available = {item["value"] for item in media_capabilities()["media"]["image"]["exportFormats"]} + if format_id not in available: + pytest.skip(f"{format_id} is not available in this Pillow runtime") + source = tmp_path / "source.png" + Image.new("RGBA", (31, 19), (10, 20, 30, 128)).save(source) + + exported, mime_type, _ = export_media_file( + source, + kind="image", + format_id=format_id, + options={"quality": 84}, + cache_root=tmp_path / "exports", + ) + + with Image.open(exported) as image: + assert image.size == (31, 19) + assert mime_type.startswith("image/") + + +@pytest.mark.parametrize( + ("format_id", "suffix", "mime_type"), + [ + ("mp4", ".mp4", "video/mp4"), + ("webm", ".webm", "video/webm"), + ("mov", ".mov", "video/quicktime"), + ("gif", ".gif", "image/gif"), + ], +) +def test_video_delivery_exports_are_real_decodable_files(tmp_path, format_id, suffix, mime_type): + available = {item["value"] for item in media_capabilities()["media"]["video"]["exportFormats"]} + if format_id not in available: + pytest.skip(f"{format_id} is not available in this FFmpeg runtime") + source = tmp_path / "source.mp4" + _write_video(source, tmp_path / "frames") + + exported, actual_mime_type, filename = export_media_file( + source, + kind="video", + format_id=format_id, + options={"quality": 24, "fps": 4, "width": 160}, + cache_root=tmp_path / "exports", + ) + + assert exported.is_file() + assert exported.stat().st_size > 0 + assert exported.suffix == suffix + assert filename.endswith(suffix) + assert actual_mime_type == mime_type + if format_id == "gif": + with Image.open(exported) as image: + assert image.is_animated + assert image.size == (160, 120) + else: + metadata = probe_media_file(exported, "video") + assert metadata["kind"] == "video" + assert metadata["width"] == 160 + assert metadata["height"] == 120 + + +def test_export_cache_key_includes_all_delivery_options(tmp_path): + source = tmp_path / "source.wav" + _write_tone(source) + + first, _, _ = export_media_file( + source, + kind="audio", + format_id="wav", + options={"sampleRate": 44100}, + cache_root=tmp_path / "exports", + ) + same, _, _ = export_media_file( + source, + kind="audio", + format_id="wav", + options={"sampleRate": 44100}, + cache_root=tmp_path / "exports", + ) + different, _, _ = export_media_file( + source, + kind="audio", + format_id="wav", + options={"sampleRate": 48000}, + cache_root=tmp_path / "exports", + ) + + assert same == first + assert different != first diff --git a/tests/test_media_preview_urls.py b/tests/test_media_preview_urls.py new file mode 100644 index 0000000..8188377 --- /dev/null +++ b/tests/test_media_preview_urls.py @@ -0,0 +1,274 @@ +import json +import tempfile +import unittest +from concurrent.futures import ThreadPoolExecutor +from io import BytesIO +from pathlib import Path +from types import SimpleNamespace + +import numpy as np +from PIL import Image +from scipy.io import wavfile +from unittest.mock import patch + +from modiff import media_io, server as server_module +from modiff.media_io import export_media_file +from modiff.server import ( + WebServer, + audio_download_filename, + byte_range_response, + file_backed_media_preview, + parse_audio_download_sample_rate, + resample_wav_bytes, +) + + +class MediaPreviewUrlTests(unittest.TestCase): + def test_file_backed_media_uses_file_route(self): + url = file_backed_media_preview("audio/source track.wav") + + self.assertTrue(url.startswith("/file?file=audio%2Fsource%20track.wav&t=")) + + def test_existing_browser_url_is_preserved(self): + self.assertEqual( + file_backed_media_preview("https://example.test/source.wav"), + "https://example.test/source.wav", + ) + + def test_non_path_media_uses_cache_fallback(self): + self.assertIsNone(file_backed_media_preview({"samples": []})) + + def test_cached_media_supports_browser_byte_ranges(self): + response = byte_range_response( + SimpleNamespace(headers={"Range": "bytes=2-5"}), + b"0123456789", + content_type="audio/wav", + filename="output.wav", + ) + + self.assertEqual(response.status, 206) + self.assertEqual(response.body, b"2345") + self.assertEqual(response.headers["Accept-Ranges"], "bytes") + self.assertEqual(response.headers["Content-Range"], "bytes 2-5/10") + self.assertEqual(response.headers["Content-Length"], "4") + + def test_cached_media_rejects_unsatisfiable_range(self): + response = byte_range_response( + SimpleNamespace(headers={"Range": "bytes=20-30"}), + b"0123456789", + content_type="audio/wav", + ) + + self.assertEqual(response.status, 416) + self.assertEqual(response.headers["Content-Range"], "bytes */10") + + def test_download_sample_rate_rewrites_the_wav_header_and_duration(self): + source = BytesIO() + wavfile.write(source, 48000, np.arange(48000, dtype=np.int16)) + + converted = resample_wav_bytes(source.getvalue(), 44100) + converted_rate, converted_samples = wavfile.read(BytesIO(converted)) + + self.assertEqual(converted_rate, 44100) + self.assertEqual(converted_samples.shape[0], 44100) + + def test_download_sample_rate_contract_rejects_unsupported_rates(self): + self.assertEqual(parse_audio_download_sample_rate("44100"), 44100) + self.assertIsNone(parse_audio_download_sample_rate(None)) + with self.assertRaisesRegex(ValueError, "must be one of"): + parse_audio_download_sample_rate("22050") + + def test_download_filename_records_the_actual_export_rate(self): + self.assertEqual(audio_download_filename("mix.wav", 44100), "mix-44.1kHz.wav") + + def test_concurrent_media_exports_create_one_atomic_cached_file(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + source = root / "source.png" + Image.new("RGB", (4, 3), "navy").save(source) + export_root = root / "exports" + + def fake_export(_source, destination, _format_id, _options): + destination.write_bytes(b"encoded") + + with patch.object(media_io, "_image_export", side_effect=fake_export) as encode: + with ThreadPoolExecutor(max_workers=4) as executor: + results = list( + executor.map( + lambda _index: export_media_file( + source, + kind="image", + format_id="png", + cache_root=export_root, + ), + range(4), + ) + ) + leftovers = list(export_root.glob(".*")) + + self.assertEqual(len({result[0] for result in results}), 1) + self.assertEqual(encode.call_count, 1) + self.assertEqual(leftovers, []) + + +class WorkspaceFileRouteTests(unittest.IsolatedAsyncioTestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + self.root = Path(self.temporary.name) + self.workspace = self.root / "work" + self.workspace.mkdir() + self.sibling = self.root / "work-secret" + self.sibling.mkdir() + self.secret = self.sibling / "secret.png" + Image.new("RGB", (2, 2), "red").save(self.secret) + self.server = WebServer(modules={}, work_dir=str(self.workspace), data_dir=str(self.workspace)) + + def tearDown(self): + self.temporary.cleanup() + + async def test_file_routes_reject_traversal_and_sibling_prefixes(self): + list_response = await self.server.listdir( + SimpleNamespace(query={"path": "../work-secret"}) + ) + preview_response = await self.server.preview( + SimpleNamespace(query={"file": str(self.secret)}) + ) + stream_response = await self.server.stream( + SimpleNamespace(query={"file": "../work-secret/secret.png"}) + ) + + self.assertEqual(list_response.status, 403) + self.assertEqual(preview_response.status, 403) + self.assertEqual(stream_response.status, 403) + self.assertIn("outside", json.loads(preview_response.text)["error"]) + + async def test_file_routes_reject_symlink_escapes(self): + link = self.workspace / "linked-secret" + try: + link.symlink_to(self.sibling, target_is_directory=True) + except OSError as exc: + self.skipTest(f"Symlinks are unavailable: {exc}") + + response = await self.server.stream( + SimpleNamespace(query={"file": "linked-secret/secret.png"}) + ) + + self.assertEqual(response.status, 403) + + async def test_valid_workspace_files_remain_available(self): + image_path = self.workspace / "image.png" + Image.new("RGB", (3, 2), "blue").save(image_path) + + listing = json.loads((await self.server.listdir(SimpleNamespace(query={"path": "."}))).text) + preview = await self.server.preview(SimpleNamespace(query={"file": "image.png"})) + stream = await self.server.stream(SimpleNamespace(query={"file": "image.png"})) + + self.assertEqual([item["name"] for item in listing["files"]], ["image.png"]) + self.assertEqual(preview.status, 200) + self.assertEqual(stream.status, 200) + + async def test_preview_decoding_uses_a_worker_without_disabling_pillow_limits(self): + image_path = self.workspace / "threaded.png" + Image.new("RGB", (3, 2), "blue").save(image_path) + original_limit = Image.MAX_IMAGE_PIXELS + calls = [] + + async def run_in_worker(function, *args, **kwargs): + calls.append(function) + return function(*args, **kwargs) + + with patch.object(server_module.asyncio, "to_thread", side_effect=run_in_worker): + response = await self.server.preview(SimpleNamespace(query={"file": "threaded.png"})) + + self.assertEqual(response.status, 200) + self.assertEqual(calls, [server_module.render_image_preview]) + self.assertEqual(Image.MAX_IMAGE_PIXELS, original_limit) + + async def test_preview_rejects_malformed_dimensions_and_oversized_sources(self): + image_path = self.workspace / "bounded.png" + Image.new("RGB", (3, 2), "blue").save(image_path) + + malformed = await self.server.preview( + SimpleNamespace(query={"file": "bounded.png", "width": "not-a-number"}) + ) + with patch.object(server_module, "MAX_PREVIEW_IMAGE_PIXELS", 4): + oversized = await self.server.preview(SimpleNamespace(query={"file": "bounded.png"})) + with patch("PIL.Image.open", side_effect=Image.DecompressionBombError("bomb")): + pillow_bomb = await self.server.preview(SimpleNamespace(query={"file": "bounded.png"})) + + self.assertEqual(malformed.status, 400) + self.assertEqual(oversized.status, 400) + self.assertEqual(pillow_bomb.status, 400) + + +class SeparateDataRootUploadTests(unittest.IsolatedAsyncioTestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + self.root = Path(self.temporary.name) + self.workspace = self.root / "work-volume" + self.data = self.root / "data-volume" + self.workspace.mkdir() + self.data.mkdir() + self.server = WebServer(modules={}, work_dir=str(self.workspace), data_dir=str(self.data)) + + def tearDown(self): + self.temporary.cleanup() + + async def test_upload_returns_data_root_identifier_and_every_file_route_resolves_it(self): + encoded = BytesIO() + Image.new("RGB", (3, 2), "green").save(encoded, format="PNG") + upload = SimpleNamespace(filename="source.png", file=BytesIO(encoded.getvalue())) + + class UploadRequest: + async def post(self): + return {"file": upload, "type": "images"} + + upload_response = await self.server.filePost(UploadRequest()) + payload = json.loads(upload_response.text) + identifier = payload["path"] + + self.assertEqual(upload_response.status, 200) + self.assertEqual(identifier, "@data/images/source.png") + self.assertNotIn(str(self.root), upload_response.text) + self.assertTrue((self.data / "images" / "source.png").is_file()) + + file_response = await self.server.fileGet(SimpleNamespace(query={"file": identifier})) + preview_response = await self.server.preview(SimpleNamespace(query={"file": identifier})) + stream_response = await self.server.stream(SimpleNamespace(query={"file": identifier})) + probe_response = await self.server.media_probe( + SimpleNamespace(query={"file": identifier, "media_kind": "image"}) + ) + + self.assertEqual(file_response.status, 200) + self.assertEqual(preview_response.status, 200) + self.assertEqual(stream_response.status, 200) + self.assertEqual(probe_response.status, 200) + + async def test_data_listing_uses_identifiers_and_traversal_is_rejected(self): + image_path = self.data / "images" / "listed.png" + image_path.parent.mkdir(parents=True) + Image.new("RGB", (2, 2), "purple").save(image_path) + + listing_response = await self.server.listdir( + SimpleNamespace(query={"path": "@data/images"}) + ) + listing = json.loads(listing_response.text) + + self.assertEqual(listing_response.status, 200) + self.assertEqual(listing["path"], "@data/images") + self.assertEqual(listing["abs_path"], "@data/images") + self.assertEqual(listing["files"][0]["path"], "@data/images/listed.png") + self.assertNotIn(str(self.root), listing_response.text) + + traversal = "@data/images/../../outside.png" + responses = [ + await self.server.fileGet(SimpleNamespace(query={"file": traversal})), + await self.server.preview(SimpleNamespace(query={"file": traversal})), + await self.server.stream(SimpleNamespace(query={"file": traversal})), + await self.server.listdir(SimpleNamespace(query={"path": traversal})), + ] + self.assertTrue(all(response.status == 403 for response in responses)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_memory_manager.py b/tests/test_memory_manager.py new file mode 100644 index 0000000..343a87c --- /dev/null +++ b/tests/test_memory_manager.py @@ -0,0 +1,28 @@ +import unittest +from unittest.mock import patch + +from utils.memory_menager import MemoryManager + + +class OffloadedPipelineStub: + def __init__(self): + self.to_calls = [] + + def to(self, device): + self.to_calls.append(device) + raise AssertionError("clear must not materialize a discarded offloaded pipeline on CPU") + + +class MemoryManagerCleanupTests(unittest.TestCase): + def test_clear_drops_offloaded_models_without_moving_them_to_cpu(self): + manager = MemoryManager() + pipeline = OffloadedPipelineStub() + manager.add(pipeline) + + with patch("utils.memory_menager.memory_flush") as flush: + cleared = manager.clear() + + self.assertEqual(cleared, 1) + self.assertEqual(manager.cache, {}) + self.assertEqual(pipeline.to_calls, []) + flush.assert_called_once_with() diff --git a/tests/test_model_artifact_catalog.py b/tests/test_model_artifact_catalog.py new file mode 100644 index 0000000..f9107f3 --- /dev/null +++ b/tests/test_model_artifact_catalog.py @@ -0,0 +1,100 @@ +import unittest + +from modiff.model_artifact_catalog import ( + catalog_artifact, + catalog_repository_pin, + catalog_revision, + community_artifact_is_discoverable, + public_model_artifact_catalog, + read_model_artifact_catalog, + resolve_model_revision, +) + + +STUDIO_MODEL_TYPES = { + "AceStepAudioPipeline", + "Flux2KleinPipeline", + "FluxCannyPipeline", + "FluxDepthPipeline", + "FluxDevPipeline", + "FluxFillPipeline", + "FluxKontextPipeline", + "FluxKreaPipeline", + "FluxReduxPipeline", + "FluxSchnellPipeline", + "LTXVideoPipeline", + "QwenImageEditModularPipeline", + "QwenImageEditPlusModularPipeline", + "QwenImageLayeredModularPipeline", + "QwenImageModularPipeline", + "WanImageToVideoPipeline", + "WanTI2VPipeline", + "WanVACEPipeline", + "WanVideoPipeline", + "ZImageModularPipeline", +} + + +class ModelArtifactCatalogTests(unittest.TestCase): + def test_catalog_covers_every_studio_profile(self): + catalog = read_model_artifact_catalog() + model_types = {model["modelType"] for model in catalog["models"]} + self.assertTrue(STUDIO_MODEL_TYPES.issubset(model_types)) + self.assertNotIn("DiffusionGemmaPipeline", model_types) + + def test_artifacts_expand_version_platform_evidence_and_popularity_fields(self): + artifact = catalog_artifact("FluxKontextPipeline", "black-forest-labs/FLUX.1-Kontext-dev-NVFP4") + self.assertIsNotNone(artifact) + self.assertIn("revision", artifact) + self.assertEqual(artifact["supportedPlatforms"], ["linux"]) + self.assertEqual(artifact["supportedBackends"], ["cuda"]) + self.assertEqual(artifact["qualificationEvidence"]["status"], "documented") + self.assertTrue(artifact["popularitySnapshot"]["checkedAt"].startswith("2026-07-18")) + + def test_catalog_auto_artifacts_and_base_models_are_revision_pinned(self): + catalog = read_model_artifact_catalog() + for model in catalog["models"]: + self.assertRegex(model["baseRevision"], r"^[0-9a-f]{40}$", model["baseRepo"]) + self.assertTrue(model.get("baseLicense"), model["baseRepo"]) + for artifact in model.get("artifacts") or []: + self.assertRegex(artifact["revision"], r"^[0-9a-f]{40}$", artifact["repo"]) + self.assertTrue(artifact.get("license"), artifact["repo"]) + + for pin in catalog.get("repositoryPins") or []: + self.assertRegex(pin["revision"], r"^[0-9a-f]{40}$", pin["repo"]) + self.assertTrue(pin.get("license"), pin["repo"]) + self.assertTrue(pin.get("purpose"), pin["repo"]) + + def test_repository_lookup_covers_base_artifact_and_auxiliary_pins(self): + self.assertEqual( + catalog_revision("black-forest-labs/FLUX.1-dev"), + "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21", + ) + artifact = catalog_repository_pin("QuantStack/Wan2.2-I2V-A14B-GGUF") + self.assertEqual(artifact["kind"], "artifact") + self.assertEqual(artifact["format"], "gguf") + self.assertEqual( + catalog_revision("lllyasviel/FramePackI2V_HY"), + "86cef4396041b6002c957852daac4c91aaa47c79", + ) + + def test_revision_resolution_preserves_explicit_and_unknown_user_selections(self): + self.assertEqual( + resolve_model_revision("black-forest-labs/FLUX.1-dev"), + "3de623fc3c33e44ffbe2bad470d0f45bccf2eb21", + ) + self.assertEqual(resolve_model_revision("black-forest-labs/FLUX.1-dev", "user-tag"), "user-tag") + self.assertIsNone(resolve_model_revision("user/private-model")) + self.assertIsNone(resolve_model_revision("black-forest-labs/FLUX.1-dev", source="local")) + + def test_popularity_is_discovery_only(self): + self.assertTrue(community_artifact_is_discoverable({"downloads": 1000, "likes": 0})) + self.assertTrue(community_artifact_is_discoverable({"downloads": 0, "likes": 10})) + self.assertFalse(community_artifact_is_discoverable({"downloads": 999, "likes": 9})) + public = public_model_artifact_catalog() + self.assertFalse(public["policy"]["popularityIsCompatibilityProof"]) + self.assertFalse(public["selectionPolicy"]["popularityMayChangeAutoSelection"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_model_artifact_options.py b/tests/test_model_artifact_options.py new file mode 100644 index 0000000..8bc03d2 --- /dev/null +++ b/tests/test_model_artifact_options.py @@ -0,0 +1,16 @@ +import unittest + +from modules.ModelArtifact.main import QuantizeDiffusersComponents + + +class ModelArtifactOptionTests(unittest.TestCase): + def test_quantization_node_does_not_select_an_optional_backend_that_is_not_installed(self): + self.assertEqual(QuantizeDiffusersComponents.params["quantization_mode"]["default"], "") + + node = QuantizeDiffusersComponents("missing-quantization-selection") + with self.assertRaisesRegex(ValueError, "Select an installed quantization backend"): + node.execute(model_id={"source": "hub", "value": "org/example"}) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_model_capabilities.py b/tests/test_model_capabilities.py new file mode 100644 index 0000000..ac25211 --- /dev/null +++ b/tests/test_model_capabilities.py @@ -0,0 +1,106 @@ +import json +import unittest + +import modules as module_registry +from modiff.server import WebServer + + +class FakeRequest: + query = {} + + +class ModelCapabilitiesTests(unittest.IsolatedAsyncioTestCase): + async def test_capabilities_publish_normalized_execution_contract(self): + response = await WebServer(module_registry.MODULE_MAP).model_capabilities(FakeRequest()) + payload = json.loads(response.text) + self.assertEqual(payload["schemaVersion"], 2) + self.assertEqual(len(payload["experimentalCapabilities"]), 5) + self.assertTrue(all(item["supportTier"] == "experimental" for item in payload["experimentalCapabilities"])) + experimental = {item["modelType"]: item for item in payload["experimentalCapabilities"]} + self.assertNotIn("DiffusionGemmaForBlockDiffusion", experimental) + self.assertNotIn("modules.TransformersMultimodal", module_registry.MODULE_MAP) + self.assertEqual( + experimental["Flux2KleinModularPipeline"]["runnableModes"], + ["text_to_image", "edit_image", "multi_image_reference_edit"], + ) + for capability in payload["experimentalCapabilities"]: + self.assertIn("executionProfiles", capability) + self.assertIn("inputContracts", capability) + self.assertIn("parameterAliases", capability) + self.assertIn("defaults", capability) + self.assertIn("artifactCandidates", capability) + self.assertIn("revisionCandidates", capability) + self.assertIn("quantizationSupport", capability) + by_model = {item["modelType"]: item for item in payload["capabilities"]} + + wan = by_model["WanVACEPipeline"] + self.assertEqual(wan["mediaKind"], "video") + self.assertEqual(wan["pipelineClasses"], ["WanVACEPipeline"]) + self.assertNotIn("video_to_video", wan["runnableModes"]) + self.assertEqual(wan["executionProfiles"][0]["backend_path"], "modules.DiffusersVideo.LoadPipeline") + self.assertEqual(wan["qualificationStatus"], "qualified") + self.assertEqual( + wan["qualifiedModes"], + ["text_to_video", "video_inpaint", "video_outpaint", "control_to_video"], + ) + self.assertEqual( + wan["runnableModes"], + ["control_to_video", "text_to_video", "video_inpaint", "video_outpaint"], + ) + self.assertIn("Wan-AI/Wan2.1-VACE-1.3B-diffusers", wan["artifactCandidates"]) + wan_video = by_model["WanVideoPipeline"] + self.assertEqual(wan_video["pipelineClasses"], ["WanPipeline", "WanVideoToVideoPipeline"]) + self.assertEqual(wan_video["artifactCandidates"], ["Wan-AI/Wan2.1-T2V-1.3B-Diffusers"]) + wan_v2v = next( + profile for profile in wan_video["executionProfiles"] if profile["id"] == "wan-video-to-video:direct" + ) + self.assertEqual(wan_v2v["modes"], ["video_to_video", "video_color_edit"]) + wan_t2v = next(profile for profile in wan_video["executionProfiles"] if profile["id"] == "wan-text-to-video:direct") + self.assertEqual(wan_t2v["modes"], ["text_to_video"]) + + ltx = by_model["LTXVideoPipeline"] + self.assertEqual(ltx["mediaKind"], "video") + self.assertEqual(ltx["supportTier"], "supported") + self.assertEqual(ltx["qualificationStatus"], "qualified") + self.assertEqual( + ltx["qualifiedModes"], + ["text_to_video", "image_to_video", "video_to_video", "reference_to_video"], + ) + self.assertEqual(ltx["pipelineClasses"], ["LTXConditionPipeline"]) + self.assertEqual( + ltx["runnableModes"], + ["image_to_video", "reference_to_video", "text_to_video", "video_to_video"], + ) + self.assertEqual(ltx["executionProfiles"][0]["backend_path"], "modules.DiffusersVideo.LoadPipeline") + self.assertEqual(ltx["maxPromptTokens"], 128) + self.assertEqual(ltx["defaultRepo"], "Lightricks/LTX-Video-0.9.8-13B-distilled") + self.assertIn("Lightricks/LTX-Video", ltx["artifactCandidates"]) + self.assertEqual(len(ltx["downloadFiles"]), 22) + self.assertNotIn("ltxv-13b-0.9.8-dev.safetensors", ltx["downloadFiles"]) + + canny = by_model["FluxCannyPipeline"] + self.assertEqual( + canny["artifactCandidates"][:2], + [ + "black-forest-labs/FLUX.1-Canny-dev", + "fuliucansheng/FLUX.1-Canny-dev-diffusers", + ], + ) + self.assertEqual( + canny["verifiedRepairSources"][0]["repo"], + "fuliucansheng/FLUX.1-Canny-dev-diffusers", + ) + self.assertIn("guidance_scale", wan["parameterAliases"]["guidanceScale"]) + self.assertIn("num_inference_steps", wan["parameterAliases"]["steps"]) + self.assertEqual(wan["defaults"]["dtype"], "bfloat16") + + qwen_inpaint = by_model["QwenImageEditModularPipeline"] + self.assertEqual(qwen_inpaint["inpaintContract"]["source"], "modules.DiffusersImage.Inpaint") + self.assertIn("QwenImageEditInpaintPipeline", qwen_inpaint["pipelineClasses"]) + + blocked = by_model["QwenImageEditPlusModularPipeline"] + self.assertNotIn("inpaint", blocked["runnableModes"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_modular_diffusers_upstream_contract.py b/tests/test_modular_diffusers_upstream_contract.py new file mode 100644 index 0000000..ffe3c94 --- /dev/null +++ b/tests/test_modular_diffusers_upstream_contract.py @@ -0,0 +1,310 @@ +import inspect +import unittest +from unittest.mock import MagicMock, patch + +import diffusers +import torch +from diffusers import ComponentSpec, ComponentsManager, ModularPipeline +from diffusers.modular_pipelines import InputParam, LoopSequentialPipelineBlocks, ModularPipelineBlocks, OutputParam + +from modules.ModularDiffusers.modular_utils import get_all_model_types +from modules.ModularDiffusers import FLUX_BLOCKS, QWEN_IMAGE_BLOCKS, SDXL_BLOCKS +from modules.ModularDiffusers.denoise import Denoise +from modules.ModularDiffusers.dynamic_node import DynamicBlockNode +from modules.ModularDiffusers.guiders import Guider, Layers +from modules.ModularDiffusers.guiders import GUIDER_OPTIONS +from modules.ModularDiffusers.loaders import AutoModelLoader, ModelsLoader, QuantizationConfigNode +from modules.ModularDiffusers.pipeline_schema import ( + MoDiffPipelineConfig, + input_param_to_modiff_param, + output_param_to_modiff_param, +) + + +_NO_EXPLICIT_GUIDER = object() + + +class ModularDiffusersUpstreamContractTests(unittest.TestCase): + """Hardware-free checks for the experimental upstream API MoDiff consumes.""" + + def _run_denoise_guider_contract(self, *, guider=_NO_EXPLICIT_GUIDER, pipeline_components=("guider",)): + pipeline = MagicMock() + pipeline.component_names = list(pipeline_components) + pipeline._execution_device = "cpu" + pipeline.transformer = None + pipeline.return_value = {} + + blocks = MagicMock() + blocks.component_names = ["guider"] + blocks.input_names = [] + blocks.init_pipeline.return_value = pipeline + node_config = { + "params": {"guidance_scale": {"type": "float"}}, + "input_names": ["guidance_scale"], + "model_input_names": ["unet", "guider"], + "output_names": [], + } + kwargs = { + "unet": {"repo_id": None}, + "guidance_scale": 4.5, + } + if guider is not _NO_EXPLICIT_GUIDER: + kwargs["guider"] = guider + + node = Denoise("guider-install-contract") + node._pipeline_class = object() + with ( + patch( + "modules.ModularDiffusers.denoise.pipeline_class_to_modiff_node_config", + return_value=(blocks, node_config), + ), + patch("modules.ModularDiffusers.denoise.deepcopy", return_value=blocks), + patch("modules.ModularDiffusers.denoise.insert_preview_block"), + ): + result = node.execute(**kwargs) + + return pipeline, result + + def test_core_symbols_and_loader_signature_are_present(self): + self.assertTrue(inspect.isclass(ModularPipeline)) + self.assertTrue(inspect.isclass(ComponentsManager)) + self.assertTrue(inspect.isclass(ComponentSpec)) + self.assertTrue(inspect.isclass(ModularPipelineBlocks)) + self.assertTrue(inspect.isclass(LoopSequentialPipelineBlocks)) + + loader_params = inspect.signature(ModularPipeline.from_pretrained).parameters + self.assertIn("pretrained_model_name_or_path", loader_params) + self.assertIn("trust_remote_code", loader_params) + self.assertIn("components_manager", loader_params) + self.assertIn("collection", loader_params) + + def test_every_remote_code_loader_exposes_an_immutable_revision_field(self): + for loader in (DynamicBlockNode, AutoModelLoader, ModelsLoader): + with self.subTest(loader=loader.__name__): + self.assertIn("trust_remote_code", loader.params) + self.assertIn("revision", loader.params) + + def test_public_guider_registry_exposes_resolved_options_mapping(self): + from modules import MODULE_MAP + + options = MODULE_MAP["modules.ModularDiffusers"]["Guider"]["params"]["guider"]["options"] + self.assertIsInstance(options, dict) + self.assertEqual(options, GUIDER_OPTIONS) + + def test_reviewed_dynamic_block_resolves_its_catalog_revision(self): + node = DynamicBlockNode("dynamic-revision-probe") + with patch( + "modules.ModularDiffusers.dynamic_node.PipelineConfig.load", + return_value=object(), + ) as load_config: + node._get_custom_config("diffusers/FLUX.2-klein-4B-modular") + + load_config.assert_called_once_with( + "diffusers/FLUX.2-klein-4B-modular", + revision="62ac375aa5308588f111fcd12115f5c54a8b1f4f", + ) + + def test_models_loader_resolves_known_base_revision(self): + node = ModelsLoader("modular-revision-probe") + with ( + patch("modules.ModularDiffusers.loaders.configure_components_manager_offload"), + patch( + "modules.ModularDiffusers.loaders.ModularPipeline.from_pretrained", + side_effect=RuntimeError("stop after loader call"), + ) as loader, + ): + with self.assertRaisesRegex(RuntimeError, "stop after loader call"): + node.execute( + model_type="ZImageModularPipeline", + repo_id={"source": "hub", "value": "Tongyi-MAI/Z-Image-Turbo"}, + device="cpu", + dtype="float16", + trust_remote_code=False, + auto_offload=False, + offload_mode="none", + ) + + self.assertEqual( + loader.call_args.kwargs["revision"], + "f332072aa78be7aecdf3ee76d5c247082da564a6", + ) + + def test_quantization_layer_probe_uses_the_public_auto_model_boundary(self): + fake_model = unittest.mock.Mock() + fake_model.named_modules.return_value = [ + ("transformer_blocks.0.attn.to_q", torch.nn.Linear(2, 2)), + ("transformer_blocks.0.norm", torch.nn.LayerNorm(2)), + ] + node = QuantizationConfigNode("quant-layer-contract") + + with ( + patch("diffusers.AutoModel.load_config", return_value={"_class_name": "FixtureModel"}), + patch("diffusers.AutoModel.from_config", return_value=fake_model) as from_config, + ): + layers = node._get_model_layers("unit/model", "transformer") + + from_config.assert_called_once_with({"_class_name": "FixtureModel"}) + self.assertEqual(layers, {"transformer_blocks.0": ["transformer_blocks.0.attn.to_q"]}) + self.assertNotIn("pipeline_loading_utils", inspect.getsource(QuantizationConfigNode._get_model_layers)) + + def test_input_and_output_metadata_converts_to_graph_fields(self): + prompt = input_param_to_modiff_param( + InputParam(name="prompt", default="", required=True, metadata={"modiff": "textbox"}) + ) + image = output_param_to_modiff_param(OutputParam(name="images", metadata={"modiff": "image"})) + + self.assertEqual(prompt.name, "prompt") + self.assertEqual(prompt.display, "textarea") + self.assertEqual(image.name, "images") + self.assertEqual(image.type, "image") + self.assertEqual(image.display, "output") + + def test_modiff_schema_round_trip_does_not_require_model_weights(self): + config = MoDiffPipelineConfig( + node_specs={ + "encode": { + "inputs": [ + input_param_to_modiff_param( + InputParam(name="prompt", default="", metadata={"modiff": "textbox"}) + ) + ], + "outputs": [ + output_param_to_modiff_param(OutputParam(name="images", metadata={"modiff": "image"})) + ], + "required_inputs": ["prompt"], + "block_name": "text_encoder", + } + }, + label="Contract fixture", + default_repo="local/fixture", + default_dtype="bfloat16", + ) + + restored = MoDiffPipelineConfig.from_dict(config.to_dict()) + self.assertEqual(restored.to_dict(), config.to_dict()) + self.assertEqual(restored.node_params["encode"]["block_name"], "text_encoder") + self.assertIn("prompt", restored.node_params["encode"]["params"]) + + def test_required_pipeline_registry_matches_installed_diffusers(self): + required = { + "StableDiffusionXLModularPipeline", + "QwenImageModularPipeline", + "QwenImageEditModularPipeline", + "QwenImageEditPlusModularPipeline", + "QwenImageLayeredModularPipeline", + "FluxModularPipeline", + "FluxKontextModularPipeline", + "Flux2KleinModularPipeline", + "ZImageModularPipeline", + "WanModularPipeline", + "WanImage2VideoModularPipeline", + } + + missing_exports = sorted(name for name in required if not hasattr(diffusers, name)) + self.assertEqual(missing_exports, [], f"Installed Diffusers removed exports: {missing_exports}") + registered = set(get_all_model_types()) + self.assertTrue(required.issubset(registered), f"MoDiff registry is missing: {sorted(required - registered)}") + + def test_layer_options_identify_module_list_stacks(self): + self.assertEqual(QWEN_IMAGE_BLOCKS, ["transformer_blocks"]) + self.assertEqual(FLUX_BLOCKS, ["transformer_blocks", "single_transformer_blocks"]) + self.assertTrue(SDXL_BLOCKS) + self.assertTrue(all(value.endswith(".transformer_blocks") for value in SDXL_BLOCKS)) + self.assertTrue(all(value == value.strip() for value in [*SDXL_BLOCKS, *QWEN_IMAGE_BLOCKS, *FLUX_BLOCKS])) + + def test_layers_preserve_exact_stack_fqn_and_validate_indices(self): + node = object.__new__(Layers) + output = node.execute( + blocks_select=["transformer_blocks"], + transformer_blocks={"indices": "0, 18", "dropout": 0.5}, + ) + + self.assertEqual( + output["layers_config"], + [ + { + "indices": [0, 18], + "fqn": "transformer_blocks", + "dropout": 0.5, + "skip_attention": False, + "skip_attention_scores": False, + "skip_ff": False, + } + ], + ) + with self.assertRaisesRegex(ValueError, "comma-separated integers"): + node.execute(transformer_blocks={"indices": "zero"}) + + def test_layer_dependent_guiders_require_an_explicit_nonempty_selection(self): + node = object.__new__(Guider) + node.node_id = "guider-contract" + + for guider in ("SkipLayerGuidance", "AutoGuidance", "SmoothedEnergyGuidance"): + with self.subTest(guider=guider), self.assertRaisesRegex(ValueError, "non-empty Layers connection"): + node.execute(guider, layers_config=[]) + + def test_guider_converts_validated_layer_mapping_to_upstream_config(self): + node = object.__new__(Guider) + node.node_id = "guider-contract" + with patch.object(diffusers, "SkipLayerGuidance", return_value="configured") as constructor: + result = node.execute( + "SkipLayerGuidance", + layers_config=[ + { + "indices": [1, 3], + "fqn": "transformer_blocks", + "dropout": 1.0, + "skip_attention": False, + "skip_attention_scores": True, + "skip_ff": False, + } + ], + ) + + self.assertEqual(result, {"guider_out": "configured"}) + config = constructor.call_args.kwargs["skip_layer_config"][0] + self.assertEqual(config.indices, [1, 3]) + self.assertEqual(config.fqn, "transformer_blocks") + + def test_frequency_decoupled_guider_uses_upstream_plural_scale_argument(self): + node = object.__new__(Guider) + node.node_id = "frequency-guider-contract" + with patch.object(diffusers, "FrequencyDecoupledGuidance", return_value="configured") as constructor: + result = node.execute("FrequencyDecoupledGuidance", guidance_scale=4.5) + + self.assertEqual(result, {"guider_out": "configured"}) + self.assertEqual(constructor.call_args.kwargs["guidance_scales"], [4.5]) + self.assertNotIn("guidance_scale", constructor.call_args.kwargs) + + def test_denoise_installs_connected_diffusers_guider_without_legacy_overwrite(self): + guider = diffusers.ClassifierFreeGuidance(guidance_scale=7.0) + + pipeline, result = self._run_denoise_guider_contract(guider=guider) + + self.assertEqual(result, {}) + pipeline.update_components.assert_called_once_with(guider=guider) + pipeline.get_component_spec.assert_not_called() + + def test_denoise_keeps_guidance_scale_fallback_without_model_ids_or_explicit_guider(self): + pipeline, result = self._run_denoise_guider_contract() + created_guider = pipeline.get_component_spec.return_value.create.return_value + + # No model IDs exercise the formerly uninitialized component-update path. + self.assertEqual(result, {}) + pipeline.get_component_spec.assert_called_once_with("guider") + pipeline.get_component_spec.return_value.create.assert_called_once_with(guidance_scale=4.5) + pipeline.update_components.assert_called_once_with(guider=created_guider) + + def test_denoise_rejects_non_diffusers_guider_with_actionable_error(self): + with self.assertRaisesRegex(TypeError, "Diffusers BaseGuidance instance"): + self._run_denoise_guider_contract(guider=object()) + + def test_denoise_rejects_guider_for_pipeline_without_guider_component(self): + guider = diffusers.ClassifierFreeGuidance(guidance_scale=7.0) + + with self.assertRaisesRegex(ValueError, "does not expose a 'guider' component"): + self._run_denoise_guider_contract(guider=guider, pipeline_components=()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_modular_image_outputs.py b/tests/test_modular_image_outputs.py new file mode 100644 index 0000000..ed67cde --- /dev/null +++ b/tests/test_modular_image_outputs.py @@ -0,0 +1,111 @@ +import sys +import json +import unittest +from pathlib import Path +from unittest.mock import patch + +from PIL import Image + + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from modules.Image.main import Preview # noqa: E402 +from modules.ModularDiffusers.latents import ( # noqa: E402 + ImageEncode, + flatten_pil_images, + prepare_image_for_vae_pipeline, +) + + +class ModularImageOutputTests(unittest.TestCase): + def test_qwen_layered_vae_input_adds_an_opaque_alpha_channel(self): + class QwenImageLayeredModularPipeline: + pass + + source = Image.new("RGB", (8, 8), "red") + prepared = prepare_image_for_vae_pipeline(source, QwenImageLayeredModularPipeline) + + self.assertEqual(prepared.mode, "RGBA") + self.assertEqual(prepared.getpixel((0, 0)), (255, 0, 0, 255)) + self.assertEqual(source.mode, "RGB") + + def test_qwen_layered_vae_input_normalizes_nested_pil_batches(self): + class QwenImageLayeredModularPipeline: + pass + + source = [Image.new("RGB", (8, 8), "red"), (Image.new("RGBA", (8, 8), "blue"),)] + prepared = prepare_image_for_vae_pipeline(source, QwenImageLayeredModularPipeline) + + self.assertEqual(prepared[0].mode, "RGBA") + self.assertEqual(prepared[1][0].mode, "RGBA") + + def test_other_vae_pipelines_keep_their_source_unchanged(self): + class QwenImageEditModularPipeline: + pass + + source = Image.new("RGB", (8, 8), "red") + + self.assertIs(prepare_image_for_vae_pipeline(source, QwenImageEditModularPipeline), source) + + def test_image_encode_applies_layered_rgba_contract_before_running_pipeline(self): + class QwenImageLayeredModularPipeline: + pass + + observed = {} + + class FakePipeline: + def __call__(self, **kwargs): + observed.update(kwargs) + return {"latents": "encoded"} + + class FakeBlocks: + component_names = [] + input_names = ["image"] + + @staticmethod + def init_pipeline(repo_id, components_manager): + self.assertEqual(repo_id, "fixture/layered") + return FakePipeline() + + node_config = { + "params": {}, + "model_input_names": [], + "input_names": ["image"], + "output_names": ["latents"], + } + source = Image.new("RGB", (8, 8), "red") + node = ImageEncode() + node._pipeline_class = QwenImageLayeredModularPipeline + + with patch( + "modules.ModularDiffusers.latents.pipeline_class_to_modiff_node_config", + return_value=(FakeBlocks(), node_config), + ): + result = node.execute(vae={"repo_id": "fixture/layered"}, image=source) + + self.assertEqual(result["latents"], "encoded") + summary = json.loads(result["encode_summary_data"]) + self.assertEqual(summary["schemaVersion"], 1) + self.assertEqual(summary["status"], "encoded") + self.assertGreaterEqual(summary["elapsedSeconds"], 0) + self.assertEqual(observed["image"].mode, "RGBA") + self.assertEqual(observed["image"].getpixel((0, 0)), (255, 0, 0, 255)) + self.assertEqual(source.mode, "RGB") + + def test_flattens_nested_layered_diffusers_pil_batches(self): + first = Image.new("RGB", (8, 8), "red") + second = Image.new("RGBA", (8, 8), "blue") + + self.assertEqual(flatten_pil_images([[first], (second,)]), [first, second]) + + def test_does_not_relabel_non_image_values_as_decoded_images(self): + self.assertIsNone(flatten_pil_images([[Image.new("RGB", (8, 8))], object()])) + + def test_preview_missing_vae_respects_declared_output_contract(self): + result = Preview.execute(object(), image=object(), export="", vae=None, device="cpu") + + self.assertEqual(result, {"output": None, "filtered": None}) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_modular_pipeline_recovery.py b/tests/test_modular_pipeline_recovery.py new file mode 100644 index 0000000..19075d8 --- /dev/null +++ b/tests/test_modular_pipeline_recovery.py @@ -0,0 +1,186 @@ +import unittest +from types import SimpleNamespace +from unittest.mock import Mock, patch + +from diffusers import QwenImageEditPlusModularPipeline + +from modules.ModularDiffusers.modular_utils import ( + DummyCustomPipeline, + pin_modular_component_revisions, + pipeline_class_from_runtime_inputs, + require_immutable_hub_revision, +) +from modules.ModularDiffusers.denoise import Denoise + + +class ModularPipelineRecoveryTests(unittest.TestCase): + def test_modular_component_specs_require_reviewed_auxiliary_pins(self): + primary = SimpleNamespace( + pretrained_model_name_or_path="Tongyi-MAI/Z-Image-Turbo", + revision=None, + ) + known_auxiliary = SimpleNamespace( + pretrained_model_name_or_path="lllyasviel/flux_redux_bfl", + revision=None, + ) + unknown_auxiliary = SimpleNamespace( + pretrained_model_name_or_path="user/custom-component", + revision=None, + ) + pipeline = SimpleNamespace( + _component_specs={ + "transformer": primary, + "image_encoder": known_auxiliary, + "custom": unknown_auxiliary, + } + ) + + with self.assertRaisesRegex(ValueError, "40-character"): + pin_modular_component_revisions( + pipeline, + "Tongyi-MAI/Z-Image-Turbo", + "f332072aa78be7aecdf3ee76d5c247082da564a6", + ) + + self.assertIsNone(primary.revision) + self.assertIsNone(known_auxiliary.revision) + self.assertIsNone(unknown_auxiliary.revision) + + def test_modular_component_specs_accept_explicit_auxiliary_commit(self): + explicit_revision = "c" * 40 + primary = SimpleNamespace(pretrained_model_name_or_path="owner/base", revision=None) + auxiliary = SimpleNamespace(pretrained_model_name_or_path="owner/component", revision=explicit_revision) + pipeline = SimpleNamespace(_component_specs={"transformer": primary, "auxiliary": auxiliary}) + + applied = pin_modular_component_revisions(pipeline, "owner/base", "d" * 40) + + self.assertEqual(primary.revision, "d" * 40) + self.assertEqual(auxiliary.revision, explicit_revision) + self.assertEqual(applied, {"transformer": "d" * 40, "auxiliary": explicit_revision}) + + def tearDown(self): + DummyCustomPipeline.repo_id = None + DummyCustomPipeline.revision = None + DummyCustomPipeline.trust_remote_code = False + + def test_remote_code_requires_an_immutable_commit_revision(self): + with self.assertRaisesRegex(ValueError, "40-character"): + require_immutable_hub_revision("owner/custom-pipeline", "main", required=True) + revision = "a" * 40 + self.assertEqual( + require_immutable_hub_revision("owner/custom-pipeline", revision, required=True), + revision, + ) + + def test_dummy_custom_pipeline_never_silently_enables_remote_code(self): + DummyCustomPipeline.repo_id = "owner/custom-pipeline" + with patch("diffusers.ModularPipeline.from_pretrained", return_value="pipeline") as loader: + self.assertEqual(DummyCustomPipeline(), "pipeline") + + loader.assert_called_once_with( + "owner/custom-pipeline", + trust_remote_code=False, + local_files_only=True, + ) + + def test_dummy_custom_pipeline_propagates_explicit_trust_and_revision(self): + DummyCustomPipeline.repo_id = "owner/custom-pipeline" + DummyCustomPipeline.trust_remote_code = True + with patch("diffusers.ModularPipeline.from_pretrained") as loader: + with self.assertRaisesRegex(ValueError, "40-character"): + DummyCustomPipeline() + loader.assert_not_called() + + DummyCustomPipeline.revision = "b" * 40 + loader.return_value = "pipeline" + self.assertEqual(DummyCustomPipeline(), "pipeline") + + loader.assert_called_once_with( + "owner/custom-pipeline", + trust_remote_code=True, + local_files_only=True, + revision="b" * 40, + ) + + def test_dynamic_denoise_declares_its_stable_model_input_as_required(self): + self.assertTrue(Denoise.params["unet"]["required"]) + + def test_modular_denoise_honors_interrupt_at_step_boundary(self): + node = Denoise("interrupt-probe") + node._interrupt = True + + with self.assertRaisesRegex(InterruptedError, "interrupted by the user"): + node._raise_if_interrupted() + + def test_modular_denoise_publishes_measurable_progress_before_the_first_step(self): + node = Denoise("progress-probe") + node.progress = Mock() + + node._publish_initial_denoise_progress(50) + + node.progress.assert_called_once_with( + 0, + phase="denoising", + message="Denoising 0/50", + current_step=0, + total_steps=50, + elapsed_seconds=0.0, + average_step_seconds=None, + eta_seconds=None, + ) + + def test_preserves_pipeline_class_set_by_dynamic_signal(self): + self.assertIs( + pipeline_class_from_runtime_inputs(QwenImageEditPlusModularPipeline, {}), + QwenImageEditPlusModularPipeline, + ) + + def test_recovers_pipeline_class_from_nested_loader_output(self): + runtime_inputs = { + "text_encoders": { + "text_encoder": {"component": object()}, + "repo_id": "Qwen/Qwen-Image-Edit-2511", + "model_type": "QwenImageEditPlusModularPipeline", + } + } + self.assertIs( + pipeline_class_from_runtime_inputs(None, runtime_inputs), + QwenImageEditPlusModularPipeline, + ) + + def test_recovers_custom_pipeline_marker(self): + self.assertIs( + pipeline_class_from_runtime_inputs( + None, + { + "model_type": "DummyCustomPipeline", + "repo_id": "owner/custom-pipeline", + "revision": "c" * 40, + "trust_remote_code": True, + }, + ), + DummyCustomPipeline, + ) + self.assertEqual(DummyCustomPipeline.repo_id, "owner/custom-pipeline") + self.assertEqual(DummyCustomPipeline.revision, "c" * 40) + self.assertTrue(DummyCustomPipeline.trust_remote_code) + + def test_custom_pipeline_recovery_rejects_missing_trust_metadata(self): + with self.assertRaisesRegex(ValueError, "trust metadata"): + pipeline_class_from_runtime_inputs(None, {"model_type": "DummyCustomPipeline"}) + + def test_rejects_mixed_model_inputs_before_loading(self): + with self.assertRaisesRegex(ValueError, "incompatible pipeline classes"): + pipeline_class_from_runtime_inputs( + None, + {"model_type": "QwenImageEditPlusModularPipeline"}, + {"model_type": "FluxModularPipeline"}, + ) + + def test_unknown_pipeline_has_actionable_error(self): + with self.assertRaisesRegex(ValueError, "Install a Diffusers version"): + pipeline_class_from_runtime_inputs(None, {"model_type": "FuturePipeline"}) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_node_base.py b/tests/test_node_base.py index 33254c5..bd7ad12 100644 --- a/tests/test_node_base.py +++ b/tests/test_node_base.py @@ -1,8 +1,12 @@ import json import subprocess import sys +import threading +import time import unittest from pathlib import Path +from types import SimpleNamespace +from unittest.mock import patch import numpy as np @@ -10,10 +14,367 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from modules.DiffusersAudio.main import Generate as _Generate # noqa: E402,F401 -from modiff.NodeBase import deep_equal # noqa: E402 +from modiff.NodeBase import NodeBase, deep_equal, node_message_context # noqa: E402 class NodeBaseDeepEqualTests(unittest.TestCase): + def test_dynamic_node_messages_carry_workflow_ownership_and_target_the_originating_session(self): + class DynamicNode(NodeBase): + pass + + module_name = ".".join(DynamicNode.__module__.split(".")[:-1]) + definition = {module_name: {"DynamicNode": {"params": {}}}} + with patch("modiff.NodeBase._module_map", return_value=definition): + node = DynamicNode("dynamic-node") + node._sid = "browser-session" + messages = [] + current_server = SimpleNamespace( + _current_dynamic_message_identity_payload=lambda: { + "task_id": "graph-task", + "workflow_tab_id": "graph-workflow", + "workflow_canvas_epoch": 4, + }, + queue_message=lambda message, sid=None: messages.append((message, sid)), + ) + + with patch("modiff.NodeBase._server", return_value=current_server): + node.set_field_value({"dtype": "float16"}) + with node_message_context( + { + "workflow_tab_id": "field-workflow", + "workflow_canvas_epoch": 9, + } + ): + node.set_field_visibility({"dtype": True}) + + graph_message, graph_sid = messages[0] + self.assertEqual(graph_message["task_id"], "graph-task") + self.assertEqual(graph_message["workflow_tab_id"], "graph-workflow") + self.assertEqual(graph_message["workflow_canvas_epoch"], 4) + self.assertEqual(graph_message["sid"], "browser-session") + self.assertEqual(graph_sid, "browser-session") + + field_message, field_sid = messages[1] + self.assertNotIn("task_id", field_message) + self.assertEqual(field_message["workflow_tab_id"], "field-workflow") + self.assertEqual(field_message["workflow_canvas_epoch"], 9) + self.assertEqual(field_sid, "browser-session") + + def test_memory_manager_execution_without_node_identity_accepts_default_arguments(self): + class BareNode(NodeBase): + pass + + module_name = ".".join(BareNode.__module__.split(".")[:-1]) + definition = {module_name: {"BareNode": {"params": {}}}} + with patch("modiff.NodeBase._module_map", return_value=definition): + node = BareNode() + + self.assertEqual(node.mm_exec(lambda: "ok", "cpu"), "ok") + self.assertEqual( + node.mm_exec( + lambda value, *, suffix: f"{value}{suffix}", + "cpu", + args=["run"], + kwargs={"suffix": "-ok"}, + ), + "run-ok", + ) + + def test_unchanged_deterministic_node_reuses_its_cached_output(self): + class CachedNode(NodeBase): + def __init__(self): + self.execution_count = 0 + super().__init__("cached-node") + + def execute(self, value): + self.execution_count += 1 + return {"result": value * 2} + + module_name = ".".join(CachedNode.__module__.split(".")[:-1]) + definition = { + module_name: { + "CachedNode": { + "params": { + "value": {"type": "int", "default": 0}, + "result": {"type": "int", "display": "output"}, + } + } + } + } + with patch("modiff.NodeBase._module_map", return_value=definition): + node = CachedNode() + self.assertEqual(node(value=4), {"result": 8}) + self.assertTrue(node._has_changed) + self.assertEqual(node(value=4), {"result": 8}) + self.assertFalse(node._has_changed) + self.assertEqual(node.execution_count, 1) + self.assertEqual(node(value=5), {"result": 10}) + self.assertTrue(node._has_changed) + self.assertEqual(node.execution_count, 2) + + def test_changed_upstream_node_invalidates_consumer_of_same_mutable_object(self): + from modiff.server import WebServer + + class ConsumerNode(NodeBase): + def __init__(self, node_id): + self.execution_count = 0 + super().__init__(node_id) + + def execute(self, pipeline): + self.execution_count += 1 + return {"result": pipeline["adapter_scale"]} + + module_name = ".".join(ConsumerNode.__module__.split(".")[:-1]) + definition = { + module_name: { + "ConsumerNode": { + "params": { + "pipeline": {"type": "pipeline", "required": True}, + "result": {"type": "float", "display": "output"}, + } + } + } + } + pipeline = {"adapter_scale": 0.5} + source = SimpleNamespace( + _has_changed=True, + module_name="modules.DiffusersImage", + class_name="LoadAdapter", + output={"pipeline": pipeline}, + ) + graph_node = { + "module": module_name, + "action": "ConsumerNode", + "params": { + "pipeline": { + "sourceId": "adapter", + "sourceKey": "pipeline", + } + }, + } + + with patch("modiff.NodeBase._module_map", return_value=definition): + consumer = ConsumerNode("generate") + server = object.__new__(WebServer) + server.modules = definition + server.node_cache = {"adapter": source, "generate": consumer} + + server.execute_node("generate", graph_node, "test", quiet=True) + self.assertEqual(consumer.execution_count, 1) + + pipeline["adapter_scale"] = 0.8 + source._has_changed = True + server.execute_node("generate", graph_node, "test", quiet=True) + self.assertEqual(consumer.output, {"result": 0.8}) + self.assertEqual(consumer.execution_count, 2) + + source._has_changed = False + server.execute_node("generate", graph_node, "test", quiet=True) + self.assertEqual(consumer.execution_count, 2) + + def test_typed_numeric_value_matches_string_keyed_option_contract(self): + class NumericOptionNode(NodeBase): + def execute(self, sample_rate): + return {"result": sample_rate} + + module_name = ".".join(NumericOptionNode.__module__.split(".")[:-1]) + definition = { + module_name: { + "NumericOptionNode": { + "params": { + "sample_rate": { + "type": "int", + "default": 48000, + "options": { + "44100": "44.1 kHz", + "48000": "48 kHz", + }, + }, + "result": {"type": "int", "display": "output"}, + } + } + } + } + + with patch("modiff.NodeBase._module_map", return_value=definition): + node = NumericOptionNode("numeric-option-node") + self.assertEqual(node(sample_rate=44100), {"result": 44100}) + self.assertEqual(node.params["sample_rate"], 44100) + + def test_pipeline_callback_without_node_identity_preserves_diffusers_kwargs(self): + node = _Generate() + callback_kwargs = {"latents": object()} + self.assertIs(node.pipe_callback(object(), 0, None, callback_kwargs), callback_kwargs) + + def test_pipeline_callback_interrupts_at_the_completed_step_boundary(self): + node = _Generate("interrupt-test") + node._interrupt = True + pipe = type("Pipeline", (), {"_interrupt": False, "_num_timesteps": 30})() + + with self.assertRaisesRegex(InterruptedError, "after the current model step"): + node.pipe_callback(pipe, 2, None, {}) + + self.assertTrue(pipe._interrupt) + + def test_pipeline_callback_stops_at_the_configured_runtime_limit(self): + node = _Generate("runtime-limit-test") + pipe = type("Pipeline", (), {"_interrupt": False, "_num_timesteps": 30})() + task = { + "started_at": time.time() - 120, + "runtimeHints": {"maxRuntimeSeconds": 60}, + } + + with ( + patch("modiff.NodeBase._server", return_value=SimpleNamespace(current_task=task)), + self.assertRaisesRegex(TimeoutError, "configured 60 second runtime limit"), + ): + node.pipe_callback(pipe, 2, None, {}) + + self.assertTrue(pipe._interrupt) + + def test_pipeline_callback_eta_uses_measured_step_intervals(self): + node = _Generate("eta-test") + pipe = type("Pipeline", (), {"_interrupt": False, "_num_timesteps": 40})() + task = {"started_at": 1, "runtimeHints": {}} + + with ( + patch("modiff.NodeBase._server", return_value=SimpleNamespace(current_task=task)), + patch("modiff.NodeBase.time.time", side_effect=[100.0, 700.0]), + patch.object(node, "progress") as progress, + ): + node.pipe_callback(pipe, 0, None, {}) + node.pipe_callback(pipe, 1, None, {}) + + first = progress.call_args_list[0] + self.assertIsNone(first.kwargs["average_step_seconds"]) + self.assertIsNone(first.kwargs["eta_seconds"]) + second = progress.call_args_list[1] + self.assertEqual(second.kwargs["average_step_seconds"], 600.0) + self.assertEqual(second.kwargs["eta_seconds"], 22_800.0) + + def test_diffusers_loader_progress_reports_components_and_nested_shards(self): + from transformers import core_model_loading + from diffusers.utils import logging as diffusers_logging + from transformers.utils import logging as transformers_logging + + class LoaderNode(NodeBase): + pass + + module_name = ".".join(LoaderNode.__module__.split(".")[:-1]) + definition = { + module_name: { + "LoaderNode": { + "params": {}, + } + } + } + original_tqdm = diffusers_logging.tqdm + original_transformers_tqdm = transformers_logging.tqdm + original_core_loading_tqdm = core_model_loading.tqdm + with patch("modiff.NodeBase._module_map", return_value=definition): + node = LoaderNode("loader-progress") + + with patch.object(node, "progress") as progress: + with node.diffusers_loading_progress(): + background = threading.Thread( + target=lambda: list( + diffusers_logging.tqdm( + [object()], + desc="Background model download", + disable=True, + ) + ) + ) + background.start() + background.join() + for component in diffusers_logging.tqdm( + [("transformer", object()), ("vae", object())], + desc="Loading pipeline components...", + disable=True, + ): + if component[0] == "transformer": + with diffusers_logging.tqdm( + total=2, + desc="Loading checkpoint shards", + disable=True, + ) as shard_progress: + shard_progress.update(1) + shard_progress.update(1) + with core_model_loading.tqdm( + total=2, + desc="Loading weights", + disable=True, + ) as weight_progress: + weight_progress.update(1) + weight_progress.update(1) + + self.assertIs(diffusers_logging.tqdm, original_tqdm) + self.assertIs(transformers_logging.tqdm, original_transformers_tqdm) + self.assertIs(core_model_loading.tqdm, original_core_loading_tqdm) + messages = [call.kwargs["message"] for call in progress.call_args_list] + self.assertIn("Loading pipeline component 1/2: transformer", messages) + self.assertIn("Loading checkpoint shards 1/2", messages) + self.assertIn("Loading checkpoint shards 2/2", messages) + self.assertIn("Loading weights 1/2", messages) + self.assertIn("Loading weights 2/2", messages) + self.assertIn("Loading pipeline component 2/2: vae", messages) + self.assertFalse(any("Background model download" in message for message in messages)) + values = [call.args[0] for call in progress.call_args_list] + self.assertEqual(values, sorted(values)) + self.assertLessEqual(max(values), 99) + shard_call = next( + call + for call in progress.call_args_list + if call.kwargs["message"] == "Loading weights 2/2" + ) + self.assertEqual(shard_call.kwargs["current_step"], 2) + self.assertEqual(shard_call.kwargs["total_steps"], 2) + self.assertEqual(shard_call.kwargs["phase"], "shard_loading") + component_call = next( + call + for call in progress.call_args_list + if call.kwargs["message"] == "Loading pipeline component 2/2: vae" + ) + self.assertEqual(component_call.kwargs["phase"], "component_loading") + + def test_structured_loader_progress_publishes_count_finalized_on_close(self): + from modiff.NodeBase import _StructuredLoadingProgress + + class CloseFinalizingBar: + desc = "Loading checkpoint shards" + n = 4 + total = 5 + + def close(self): + self.n = self.total + return None + + reports = [] + progress = _StructuredLoadingProgress( + CloseFinalizingBar(), + lambda value, message, current, total: reports.append((value, message, current, total)), + ) + + progress.close() + + self.assertEqual(reports[-1], (99, "Loading checkpoint shards 5/5", 5, 5)) + + def test_structured_loader_progress_names_outer_model_component(self): + from diffusers.utils import logging as diffusers_logging + from modiff.NodeBase import _StructuredLoadingProgress + + reports = [] + progress = _StructuredLoadingProgress( + diffusers_logging.tqdm(["transformer"], disable=True), + lambda value, message, current, total: reports.append((value, message, current, total)), + description="Loading model components", + total=1, + ) + + list(progress) + + self.assertIn((0, "Loading model component 1/1: transformer", 1, 1), reports) + def test_nested_audio_arrays_compare_without_image_attributes(self): left = {"audio": {"samples": np.zeros((2, 480), dtype=np.float32), "sample_rate": 48000}} right = {"audio": {"samples": np.zeros((2, 480), dtype=np.float32), "sample_rate": 48000}} @@ -31,6 +392,8 @@ def test_direct_node_base_imports_preserve_complete_module_registry(self): print(json.dumps({ "module_count": len(modules.MODULE_MAP), "node_count": modules.total_nodes, + "recomputed_node_count": sum(len(nodes) for nodes in modules.MODULE_MAP.values()), + "module_names": sorted(modules.MODULE_MAP), })) """ result = subprocess.run( @@ -42,7 +405,12 @@ def test_direct_node_base_imports_preserve_complete_module_registry(self): ) self.assertEqual(result.returncode, 0, result.stderr) payload = json.loads(result.stdout.strip().splitlines()[-1]) - self.assertEqual(payload, {"module_count": 19, "node_count": 83}) + self.assertEqual(payload["module_count"], len(payload["module_names"])) + self.assertEqual(payload["node_count"], payload["recomputed_node_count"]) + self.assertGreater(payload["node_count"], 0) + self.assertTrue( + {"modules.DiffusersImage", "modules.ModularDiffusers"}.issubset(payload["module_names"]) + ) if __name__ == "__main__": diff --git a/tests/test_optimization_packages.py b/tests/test_optimization_packages.py new file mode 100644 index 0000000..090e089 --- /dev/null +++ b/tests/test_optimization_packages.py @@ -0,0 +1,193 @@ +import json +import tempfile +import unittest +from pathlib import Path +from unittest import mock + +from modiff import optimization_packages as optimizations + + +class OptimizationPackageTests(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + root = Path(self.temporary.name) + self.path_patchers = [ + mock.patch.object(optimizations, "OPTIMIZATION_ROOT", root), + mock.patch.object(optimizations, "ENVIRONMENTS_DIR", root / "environments"), + mock.patch.object(optimizations, "STAGING_DIR", root / "staging"), + mock.patch.object(optimizations, "STATE_PATH", root / "state.json"), + mock.patch.object(optimizations, "RECEIPTS_PATH", root / "receipts.json"), + ] + for patcher in self.path_patchers: + patcher.start() + + def tearDown(self): + for patcher in reversed(self.path_patchers): + patcher.stop() + self.temporary.cleanup() + + def create_environment(self, environment_id): + root = optimizations.ENVIRONMENTS_DIR / environment_id + site_packages = root / "site-packages" + site_packages.mkdir(parents=True) + (root / "manifest.json").write_text( + json.dumps({"id": environment_id, "capabilities": ["torchao"]}), + encoding="utf-8", + ) + (root / "validation.json").write_text( + json.dumps({"status": "passed"}), + encoding="utf-8", + ) + return root + + def test_catalog_is_profile_gated_and_disabled_by_default(self): + catalog = optimizations.public_catalog( + runtime_profile={"installed": "amd-rocm-linux"}, + hardware={"torch": {"version": "2.9.1+rocm7.2"}, "amd_architectures": ["gfx1151"]}, + ) + by_id = {item["id"]: item for item in catalog["capabilities"]} + self.assertTrue(by_id["torchao"]["compatible"]) + self.assertFalse(by_id["hub_attention_kernels"]["compatible"]) + self.assertFalse(by_id["torchao"]["enabled"]) + + def test_activation_and_rollback_only_use_validated_environments(self): + self.create_environment("first") + self.create_environment("second") + first = optimizations.activate_environment("first") + self.assertTrue(first["restartRequired"]) + second = optimizations.activate_environment("second") + self.assertEqual(second["state"]["previousEnvironmentId"], "first") + rolled_back = optimizations.rollback_environment() + self.assertEqual(rolled_back["state"]["activeEnvironmentId"], "first") + with self.assertRaises(ValueError): + optimizations.activate_environment("missing") + + def test_failed_stage_never_changes_active_environment(self): + self.create_environment("active") + optimizations.activate_environment("active") + failed_process = mock.Mock(returncode=1, stdout="", stderr="compiler failed") + with ( + mock.patch.object(optimizations, "_uv_executable", return_value="/managed/uv"), + mock.patch.object(optimizations.subprocess, "run", return_value=failed_process), + self.assertRaisesRegex(RuntimeError, "compiler failed"), + ): + optimizations.install_capability( + "torchao", + runtime_profile={"installed": "amd-rocm-linux"}, + hardware={"torch": {"version": "2.9.1+rocm7.2"}}, + ) + self.assertEqual(optimizations.read_state()["activeEnvironmentId"], "active") + + def test_source_package_bootstraps_toolchain_before_abi_install(self): + self.create_environment("active") + optimizations.activate_environment("active") + succeeded = mock.Mock(returncode=0, stdout="installed", stderr="") + validation = {"status": "passed", "detail": {"torch": "2.9.1+rocm7.2"}} + with ( + mock.patch.object(optimizations, "_uv_executable", return_value="/managed/uv"), + mock.patch.object(optimizations.subprocess, "run", return_value=succeeded) as run, + mock.patch.object(optimizations, "_run_validation", return_value=validation), + ): + result = optimizations.install_capability( + "flash_attention_2", + runtime_profile={"installed": "amd-rocm-linux"}, + hardware={ + "torch": {"version": "2.9.1+rocm7.2"}, + "amd_architectures": ["gfx1151"], + }, + ) + + self.assertEqual(run.call_count, 2) + build_command, package_command = (call.args[0] for call in run.call_args_list) + self.assertIn("ninja==1.13.0", build_command) + self.assertIn("flash-attn==2.8.3.post1", package_command) + self.assertIn("--no-build-isolation", package_command) + self.assertIn("--no-deps", package_command) + self.assertTrue(result["requiresActivation"]) + self.assertFalse(result["activeRuntimeChanged"]) + self.assertEqual(optimizations.read_state()["activeEnvironmentId"], "active") + self.assertTrue((optimizations.ENVIRONMENTS_DIR / result["environmentId"] / "validation.json").is_file()) + + def test_auto_requires_opt_in_baseline_review_and_exact_runtime(self): + self.create_environment("active") + optimizations.activate_environment("active") + optimizations.set_capability_enabled("regional_compile", True) + runtime = "runtime-a" + common = { + "runtime_fingerprint": runtime, + "model_type": "Qwen-Image-2512", + "mode": "text_to_image", + "artifact": "Qwen/Qwen-Image-2512", + "workload_key": "workload-a", + } + optimizations.record_workload_baseline( + **common, + measurement={"elapsedSeconds": 100.0, "peakAllocatedBytes": 1000}, + ) + observed = optimizations.record_workload_observation( + capability_id="regional_compile", + **common, + selection={"regionalCompile": True}, + measurement={"elapsedSeconds": 80.0, "peakAllocatedBytes": 1000}, + ) + self.assertEqual( + optimizations.qualified_auto_overrides(**common), + {}, + ) + optimizations.qualify_receipt(observed["id"], output_reviewed=True) + self.assertEqual( + optimizations.qualified_auto_overrides(**common), + {"regionalCompile": True}, + ) + self.assertEqual( + optimizations.qualified_auto_overrides(**{**common, "runtime_fingerprint": "runtime-b"}), + {}, + ) + + def test_import_probe_receipt_never_authorizes_auto(self): + receipt = optimizations.record_probe_receipt( + capability_id="regional_compile", + runtime_fingerprint="runtime", + result={"status": "passed"}, + ) + self.assertEqual(receipt["status"], "probe_passed") + self.assertFalse(receipt["autoEligible"]) + + def test_auto_combines_independently_qualified_capabilities(self): + self.create_environment("active") + optimizations.activate_environment("active") + for capability in ("regional_compile", "channels_last"): + optimizations.set_capability_enabled(capability, True) + common = { + "runtime_fingerprint": "runtime-a", + "model_type": "Qwen-Image-2512", + "mode": "text_to_image", + "artifact": "Qwen/Qwen-Image-2512", + "workload_key": "workload-a", + } + optimizations.record_workload_baseline( + **common, + measurement={"elapsedSeconds": 100.0, "peakAllocatedBytes": 1000}, + ) + compile_receipt = optimizations.record_workload_observation( + capability_id="regional_compile", + **common, + selection={"regionalCompile": True}, + measurement={"elapsedSeconds": 80.0, "peakAllocatedBytes": 1000}, + ) + layout_receipt = optimizations.record_workload_observation( + capability_id="channels_last", + **common, + selection={"channelsLast": True}, + measurement={"elapsedSeconds": 95.0, "peakAllocatedBytes": 900}, + ) + optimizations.qualify_receipt(compile_receipt["id"], output_reviewed=True) + optimizations.qualify_receipt(layout_receipt["id"], output_reviewed=True) + self.assertEqual( + optimizations.qualified_auto_overrides(**common), + {"channelsLast": True, "regionalCompile": True}, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_path_identifiers.py b/tests/test_path_identifiers.py new file mode 100644 index 0000000..36b9f6b --- /dev/null +++ b/tests/test_path_identifiers.py @@ -0,0 +1,140 @@ +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from PIL import Image + +from modiff.config import CONFIG +from modiff.media_assets import coerce_video_asset +from modiff.path_identifiers import ( + data_path_identifier, + resolve_data_path_identifier, + resolve_managed_path_identifier, + resolve_runtime_input_path, +) + + +class PathIdentifierTests(unittest.TestCase): + def test_separate_data_root_uses_portable_identifier_without_relating_to_work_root(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + work = root / "work-volume" + data = root / "data-volume" + media = data / "images" / "source.png" + work.mkdir() + media.parent.mkdir(parents=True) + media.write_bytes(b"image") + + identifier = data_path_identifier(media, data) + + self.assertEqual(identifier, "@data/images/source.png") + self.assertNotIn(str(root), identifier) + self.assertEqual(resolve_data_path_identifier(identifier, data), media.resolve()) + self.assertEqual( + resolve_managed_path_identifier(identifier, work_root=work, data_root=data), + media.resolve(), + ) + self.assertEqual( + resolve_runtime_input_path(identifier, work_root=work, data_root=data), + media.resolve(), + ) + + def test_legacy_work_relative_data_identifier_remains_supported(self): + with tempfile.TemporaryDirectory() as temporary: + work = Path(temporary) + data = work / "data" + media = data / "images" / "legacy.png" + media.parent.mkdir(parents=True) + media.write_bytes(b"image") + + resolved = resolve_managed_path_identifier( + "data/images/legacy.png", + work_root=work, + data_root=data, + ) + + self.assertEqual(resolved, media.resolve()) + + def test_data_identifier_rejects_traversal_and_malformed_namespace_values(self): + with tempfile.TemporaryDirectory() as temporary: + data = Path(temporary) / "data" + work = Path(temporary) / "work" + data.mkdir() + work.mkdir() + + for identifier in ( + "@data/../secret.png", + "@data/images/../../secret.png", + "@data//secret.png", + "@data\\..\\secret.png", + "@data/C:/secret.png", + "@database/secret.png", + ): + with self.subTest(identifier=identifier): + self.assertIsNone( + resolve_managed_path_identifier(identifier, work_root=work, data_root=data) + ) + + def test_data_identifier_rejects_symlink_escape(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + data = root / "data" + external = root / "external" + data.mkdir() + external.mkdir() + (external / "secret.png").write_bytes(b"secret") + try: + (data / "linked").symlink_to(external, target_is_directory=True) + except OSError as exc: + self.skipTest(f"Symlinks are unavailable: {exc}") + + with self.assertRaisesRegex(ValueError, "escapes"): + resolve_data_path_identifier("@data/linked/secret.png", data) + + def test_media_loader_consumers_resolve_server_issued_identifier(self): + from modules.Audio.main import _resolve_file + from modules.Image.main import Load as LoadImage + from modules.MediaSource.main import LocalMedia + + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + work = root / "work" + data = root / "data" + image_path = data / "images" / "source.png" + audio_path = data / "audio" / "source.wav" + video_path = data / "videos" / "source.mp4" + work.mkdir() + image_path.parent.mkdir(parents=True) + audio_path.parent.mkdir(parents=True) + video_path.parent.mkdir(parents=True) + Image.new("RGB", (2, 3), "orange").save(image_path) + audio_path.write_bytes(b"wav") + video_path.write_bytes(b"video") + + with patch.dict( + CONFIG.paths, + {"work_dir": str(work), "data": str(data)}, + ): + image = LoadImage().execute(file="@data/images/source.png") + local = LocalMedia().execute(file="@data/images/source.png") + audio = _resolve_file("@data/audio/source.wav") + video = coerce_video_asset( + { + "path": "@data/videos/source.mp4", + "width": 2, + "height": 2, + "fps": 1, + "frame_count": 1, + "duration_seconds": 1, + } + ) + + self.assertEqual(image["image"].size, (2, 3)) + self.assertEqual(local["path"], str(image_path.resolve())) + self.assertEqual(audio, audio_path.resolve()) + self.assertEqual(video["path"], str(video_path.resolve())) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_qwen_inpaint_contract.py b/tests/test_qwen_inpaint_contract.py new file mode 100644 index 0000000..434466e --- /dev/null +++ b/tests/test_qwen_inpaint_contract.py @@ -0,0 +1,69 @@ +import unittest + +from PIL import Image + +from modules.DiffusersImage.main import ( + IMAGE_PIPELINE_ADAPTERS, + Inpaint, + LoadPipeline, + OutpaintCanvas, + composite_masked_pil_outputs, +) + + +class QwenInpaintContractTests(unittest.TestCase): + def test_qwen_inpaint_is_a_generic_adapter_contract(self): + adapter = IMAGE_PIPELINE_ADAPTERS["QwenImageEditInpaintPipeline"] + self.assertEqual(adapter.guidance_parameter, "true_cfg_scale") + self.assertEqual(adapter.modes, frozenset({"inpaint", "outpaint"})) + self.assertEqual(Inpaint.params["output_type"]["options"], ["pil"]) + + def test_loader_defaults_to_memory_bounded_vae_decode(self): + self.assertTrue(LoadPipeline.params["enable_vae_slicing"]["default"]) + self.assertTrue(LoadPipeline.params["enable_vae_tiling"]["default"]) + + def test_generated_pixels_are_exposed_only_inside_the_white_mask(self): + source = Image.new("RGB", (4, 2), (10, 20, 30)) + generated = Image.new("RGB", (4, 2), (200, 210, 220)) + mask = Image.new("L", (4, 2), 0) + for x in (2, 3): + for y in (0, 1): + mask.putpixel((x, y), 255) + + output = composite_masked_pil_outputs([generated], source, mask)[0] + + self.assertEqual(output.getpixel((0, 0)), (10, 20, 30)) + self.assertEqual(output.getpixel((1, 1)), (10, 20, 30)) + self.assertEqual(output.getpixel((2, 0)), (200, 210, 220)) + self.assertEqual(output.getpixel((3, 1)), (200, 210, 220)) + + def test_mask_and_source_are_normalized_to_generated_output_size(self): + source = Image.new("RGB", (2, 2), (10, 20, 30)) + generated = Image.new("RGB", (4, 4), (200, 210, 220)) + mask = Image.new("L", (2, 2), 255) + + output = composite_masked_pil_outputs(generated, source, mask)[0] + + self.assertEqual(output.size, (4, 4)) + self.assertEqual(output.getpixel((0, 0)), (200, 210, 220)) + + def test_outpaint_canvas_is_model_neutral(self): + result = OutpaintCanvas("canvas-test").execute( + image=Image.new("RGB", (32, 32), "white"), + width=64, + height=64, + left=16, + right=16, + top=16, + bottom=16, + overlap=0, + feather=0, + ) + + self.assertEqual(result["canvas"].size, (64, 64)) + self.assertEqual(result["mask_image"].getpixel((0, 0)), 255) + self.assertEqual(result["mask_image"].getpixel((32, 16)), 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_runtime_option_catalog.py b/tests/test_runtime_option_catalog.py new file mode 100644 index 0000000..3cf79b2 --- /dev/null +++ b/tests/test_runtime_option_catalog.py @@ -0,0 +1,51 @@ +import unittest + +import modules +from modiff.server import WebServer + + +class RuntimeOptionCatalogTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.server = WebServer(modules=modules.MODULE_MAP, work_dir=".", data_dir="data") + + def test_every_registered_option_has_a_safe_descriptor_and_default(self): + malformed = [] + invalid_defaults = [] + for module, actions in self.server.modules.items(): + for action, values in actions.items(): + params = self.server.describe_node_params(values.get("params", {})) + for field_name, field in params.items(): + options = field.get("options") if isinstance(field, dict) else None + if not isinstance(options, (list, dict)): + continue + entries = list(options.values()) if isinstance(options, dict) else options + descriptors = [ + entry + for entry in entries + if isinstance(entry, dict) and entry.get("schemaVersion") == 1 + ] + for descriptor in descriptors: + if descriptor.get("value") == "[object Object]" or str( + descriptor.get("label", "") + ).startswith("['"): + malformed.append(f"{module}.{action}.{field_name}") + + default = field.get("default") + defaults = default if isinstance(default, list) else [default] + for item in defaults: + if item in (None, "") or not descriptors: + continue + selected = next( + (descriptor for descriptor in descriptors if descriptor.get("value") == str(item)), + None, + ) + if selected is None or selected.get("availability") != "installed": + invalid_defaults.append(f"{module}.{action}.{field_name}={item}") + + self.assertEqual(malformed, []) + self.assertEqual(invalid_defaults, []) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_runtime_profile.py b/tests/test_runtime_profile.py new file mode 100644 index 0000000..114397c --- /dev/null +++ b/tests/test_runtime_profile.py @@ -0,0 +1,261 @@ +import json +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from modiff.runtime_profile import ( + MANIFEST_PATH, + RUNTIME_CONTRACT_SCHEMA, + load_manifest, + lock_digest, + runtime_contract_paths, + runtime_profile, +) + + +def hardware(*, version, cuda_version=None, hip_version=None, cuda_available=False, xpu_available=False): + return { + "torch": { + "available": True, + "version": version, + "cuda_version": cuda_version, + "hip_version": hip_version, + "cuda_available": cuda_available, + "xpu_available": xpu_available, + "mps_built": False, + "mps_available": False, + }, + "devices": [{"type": "cpu", "device": "cpu:0"}], + "default_device": "cpu:0", + } + + +class RuntimeProfileTests(unittest.TestCase): + def test_runtime_contract_digest_changes_with_every_contract_input(self): + with tempfile.TemporaryDirectory() as temporary: + paths = tuple(Path(temporary) / name for name in ("profile.txt", "pyproject.toml", "manifest.json")) + for index, path in enumerate(paths): + path.write_text(f"contract-{index}", encoding="utf-8") + baseline = lock_digest(paths[0], contract_paths=paths) + for index, path in enumerate(paths): + original = path.read_text(encoding="utf-8") + path.write_text(f"{original}-changed", encoding="utf-8") + self.assertNotEqual(lock_digest(paths[0], contract_paths=paths), baseline) + path.write_text(original, encoding="utf-8") + + def test_runtime_contract_digest_ignores_unrelated_accelerator_profiles(self): + with tempfile.TemporaryDirectory() as temporary: + requirement = Path(temporary) / "profile.txt" + requirement.write_text("torch==unit", encoding="utf-8") + manifest = { + "schema_version": 1, + "python": "3.12.*", + "profiles": { + "cpu": {"requirements": "requirements/profiles/cpu.txt", "tier": "supported"}, + "intel-xpu": {"requirements": "requirements/profiles/intel-xpu.txt", "tier": "preview"}, + }, + } + with patch("modiff.runtime_profile.load_manifest", return_value=manifest): + baseline = lock_digest(requirement, contract_paths=(requirement,), profile="cpu") + + manifest["profiles"]["intel-xpu"]["tier"] = "supported" + with patch("modiff.runtime_profile.load_manifest", return_value=manifest): + unrelated_change = lock_digest(requirement, contract_paths=(requirement,), profile="cpu") + + manifest["profiles"]["cpu"]["tier"] = "conditional" + with patch("modiff.runtime_profile.load_manifest", return_value=manifest): + selected_change = lock_digest(requirement, contract_paths=(requirement,), profile="cpu") + + self.assertEqual(unrelated_change, baseline) + self.assertNotEqual(selected_change, baseline) + + def write_profile(self, directory: str, profile: str): + root = Path(directory) + manifest = load_manifest() + requirement = MANIFEST_PATH.parents[2] / manifest["profiles"][profile]["requirements"] + (root / "modiff-profile.json").write_text( + json.dumps( + { + "schema_version": 1, + "profile": profile, + "support_tier": manifest["profiles"][profile]["tier"], + "manifest_revision": manifest["revision"], + "requirements": requirement.name, + "runtime_contract_schema": RUNTIME_CONTRACT_SCHEMA, + "lock_digest": lock_digest( + requirement, + contract_paths=runtime_contract_paths(requirement), + profile=profile, + ), + } + ), + encoding="utf-8", + ) + return root + + def test_managed_rocm_profile_rejects_a_cuda_torch_replacement(self): + with tempfile.TemporaryDirectory() as temporary: + venv = self.write_profile(temporary, "amd-rocm-linux") + profile = runtime_profile( + hardware(version="2.11.0+cu128", cuda_version="12.8"), + venv=venv, + ) + + self.assertFalse(profile["execution_ready"]) + self.assertEqual(profile["status"], "mismatch") + self.assertEqual(profile["installed"], "nvidia-cuda") + self.assertIn("profile-mismatch", [issue["code"] for issue in profile["issues"]]) + + def test_rocm_profile_requires_a_successful_device_tensor(self): + with tempfile.TemporaryDirectory() as temporary: + venv = self.write_profile(temporary, "amd-rocm-linux") + with patch( + "modiff.runtime_profile._device_tensor_probe", + return_value={"ready": False, "device": "cuda:0", "message": "tensor failed"}, + ): + profile = runtime_profile( + hardware( + version="2.9.1+rocm7.2.0", + hip_version="7.2.0", + cuda_available=True, + ), + venv=venv, + ) + + self.assertFalse(profile["execution_ready"]) + self.assertIn("device-tensor-failed", [issue["code"] for issue in profile["issues"]]) + + def test_matching_rocm_profile_is_ready_after_device_tensor(self): + with tempfile.TemporaryDirectory() as temporary: + venv = self.write_profile(temporary, "amd-rocm-linux") + with patch( + "modiff.runtime_profile._device_tensor_probe", + return_value={"ready": True, "device": "cuda:0", "message": None}, + ): + profile = runtime_profile( + hardware( + version="2.9.1+rocm7.2.0", + hip_version="7.2.0", + cuda_available=True, + ), + venv=venv, + ) + + self.assertTrue(profile["execution_ready"]) + self.assertEqual(profile["status"], "ready") + self.assertEqual(profile["device_validation"]["device"], "cuda:0") + self.assertEqual(profile["runtime_contract"]["status"], "verified") + + def test_matching_intel_xpu_profile_is_ready_after_device_tensor(self): + with tempfile.TemporaryDirectory() as temporary: + venv = self.write_profile(temporary, "intel-xpu") + with patch( + "modiff.runtime_profile._device_tensor_probe", + return_value={"ready": True, "device": "xpu:0", "message": None}, + ): + profile = runtime_profile( + hardware(version="2.12.1+xpu", xpu_available=True), + venv=venv, + ) + + self.assertTrue(profile["execution_ready"]) + self.assertEqual(profile["installed"], "intel-xpu") + self.assertEqual(profile["device_validation"]["device"], "xpu:0") + + def test_changed_runtime_contract_requires_repair_before_preflight_is_ready(self): + with tempfile.TemporaryDirectory() as temporary: + venv = self.write_profile(temporary, "cpu") + with ( + patch("modiff.runtime_profile.lock_digest", return_value="f" * 64), + patch( + "modiff.runtime_profile._device_tensor_probe", + return_value={"ready": True, "device": "cpu", "message": None}, + ), + ): + profile = runtime_profile(hardware(version="2.8.0"), venv=venv) + + self.assertFalse(profile["execution_ready"]) + self.assertEqual(profile["status"], "repair-required") + self.assertTrue(profile["repair_required"]) + self.assertEqual(profile["runtime_contract"]["status"], "drifted") + self.assertIn("runtime-contract-drift", [issue["code"] for issue in profile["issues"]]) + self.assertEqual(profile["repair_command"], "./install.sh --accelerator cpu --repair") + + def test_legacy_contract_record_is_non_blocking_when_runtime_checks_pass(self): + with tempfile.TemporaryDirectory() as temporary: + venv = Path(temporary) + (venv / "modiff-profile.json").write_text( + json.dumps( + { + "schema_version": 1, + "profile": "cpu", + "support_tier": "supported", + "requirements": "cpu.txt", + "lock_digest": "a" * 64, + "runtime_contract_files": [ + "requirements/profiles/cpu.txt", + "pyproject.toml", + "modiff/compatibility/accelerators.v1.json", + ], + } + ), + encoding="utf-8", + ) + with patch( + "modiff.runtime_profile._device_tensor_probe", + return_value={"ready": True, "device": "cpu", "message": None}, + ): + profile = runtime_profile(hardware(version="2.8.0"), venv=venv) + + self.assertTrue(profile["execution_ready"]) + self.assertFalse(profile["repair_required"]) + self.assertEqual(profile["runtime_contract"]["status"], "legacy") + self.assertIn("runtime-contract-legacy", [issue["code"] for issue in profile["issues"]]) + + def test_experimental_profile_repair_command_includes_required_opt_in(self): + with tempfile.TemporaryDirectory() as temporary: + venv = Path(temporary) + (venv / "modiff-profile.json").write_text( + json.dumps( + { + "schema_version": 1, + "profile": "cpu", + "support_tier": "experimental", + "lock_digest": "a" * 64, + "runtime_contract_files": ["requirements/profiles/cpu.txt"], + } + ), + encoding="utf-8", + ) + with patch( + "modiff.runtime_profile._device_tensor_probe", + return_value={"ready": True, "device": "cpu", "message": None}, + ): + profile = runtime_profile(hardware(version="2.8.0"), venv=venv) + + self.assertEqual( + profile["repair_command"], + "./install.sh --accelerator cpu --repair --allow-experimental", + ) + + def test_saved_profile_without_contract_digest_requires_repair(self): + with tempfile.TemporaryDirectory() as temporary: + venv = Path(temporary) + (venv / "modiff-profile.json").write_text( + json.dumps({"schema_version": 1, "profile": "cpu"}), + encoding="utf-8", + ) + with patch( + "modiff.runtime_profile._device_tensor_probe", + return_value={"ready": True, "device": "cpu", "message": None}, + ): + profile = runtime_profile(hardware(version="2.8.0"), venv=venv) + + self.assertEqual(profile["status"], "repair-required") + self.assertEqual(profile["runtime_contract"]["status"], "unverified") + self.assertIn("runtime-contract-unverified", [issue["code"] for issue in profile["issues"]]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_runtime_status.py b/tests/test_runtime_status.py index 4f2e24c..ab1bc5a 100644 --- a/tests/test_runtime_status.py +++ b/tests/test_runtime_status.py @@ -2,6 +2,7 @@ import io import json import mimetypes +import os import sys import tempfile import types @@ -9,7 +10,7 @@ from contextlib import redirect_stdout from pathlib import Path from types import SimpleNamespace -from unittest.mock import patch +from unittest.mock import AsyncMock, Mock, patch sys.path.insert(0, str(Path(__file__).resolve().parents[1])) @@ -109,6 +110,14 @@ def available_package(module_name, distribution_name=None): } +class JsonRequest: + def __init__(self, payload): + self.payload = payload + + async def json(self): + return self.payload + + class RuntimeStatusTests(unittest.IsolatedAsyncioTestCase): def test_webp_assets_use_browser_image_content_type(self): self.assertEqual(mimetypes.guess_type("gallery-image.webp")[0], "image/webp") @@ -121,17 +130,782 @@ def setUp(self): def tearDown(self): self.temp_dir.cleanup() + async def test_runtime_resources_uses_a_separate_versioned_snapshot_without_mutating_auto_state(self): + snapshot = { + "schemaVersion": 1, + "sampledAt": 123.0, + "system": { + "cpuPercent": 12.5, + "ramTotalBytes": 32 * GIB, + "ramAvailableBytes": 20 * GIB, + "ramUsedBytes": 12 * GIB, + "ramPercent": 37.5, + }, + "process": {"cpuPercent": 4.0, "rssBytes": 2 * GIB}, + "storage": { + "path": self.temp_dir.name, + "totalBytes": 128 * GIB, + "freeBytes": 80 * GIB, + "usedBytes": 48 * GIB, + "percent": 37.5, + "activePercent": 8.0, + "activitySource": "windows-physical-disk", + "kind": "ssd", + "detectionSource": "linux-sysfs", + }, + "activeDevice": "cuda:0", + "accelerators": [], + "currentRun": None, + "errors": [], + } + self.server._last_auto_resource_signature = ("auto-state",) + + with patch.object(self.server, "_runtime_resource_snapshot", return_value=snapshot) as collect: + response = await self.server.runtime_resources(None) + + self.assertEqual(json.loads(response.text), snapshot) + self.assertEqual(self.server._last_auto_resource_signature, ("auto-state",)) + collect.assert_called_once_with() + + def test_runtime_storage_snapshot_reports_backing_volume_usage_and_verified_kind(self): + usage = SimpleNamespace(total=128 * GIB, used=48 * GIB, free=80 * GIB) + with ( + patch("modiff.server.shutil.disk_usage", return_value=usage), + patch.object(self.server, "_storage_kind_for_path", return_value=("ssd", "linux-sysfs")), + patch.object( + self.server._runtime_disk_activity_sampler, + "sample", + return_value=(8.0, "windows-physical-disk"), + ), + ): + snapshot = self.server._runtime_storage_snapshot() + + self.assertEqual(snapshot["totalBytes"], 128 * GIB) + self.assertEqual(snapshot["usedBytes"], 48 * GIB) + self.assertEqual(snapshot["freeBytes"], 80 * GIB) + self.assertEqual(snapshot["percent"], 37.5) + self.assertEqual(snapshot["activePercent"], 8.0) + self.assertEqual(snapshot["activitySource"], "windows-physical-disk") + self.assertEqual(snapshot["kind"], "ssd") + self.assertEqual(snapshot["detectionSource"], "linux-sysfs") + + async def test_runtime_options_describe_compatibility_availability_and_dependencies(self): + self.server.modules = { + "unit": { + "Choice": { + "params": { + "device": { + "options": ["cpu", "cuda:0", "cuda:1"], + "optionDependencies": {"model_type": "image"}, + }, + "mode": { + "options": {"fast": "Fast", "quality": "Quality"}, + }, + } + } + } + } + + with patch.object(self.server, "_available_runtime_devices", return_value=["cpu", "cuda:0"]): + response = await self.server.runtime_options(None) + + payload = json.loads(response.text) + self.assertEqual(payload["schemaVersion"], 1) + device_options = payload["nodes"]["unit.Choice"]["device"] + self.assertEqual(device_options[1]["value"], "cuda:0") + self.assertEqual(device_options[1]["compatibility"], "compatible") + self.assertEqual(device_options[1]["installationState"], "installed") + self.assertEqual(device_options[1]["dependencies"], {"model_type": "image"}) + self.assertEqual(device_options[2]["compatibility"], "incompatible") + self.assertEqual(device_options[2]["availability"], "unavailable") + self.assertIn("not available", device_options[2]["disabledReason"]) + self.assertEqual(payload["nodes"]["unit.Choice"]["mode"]["fast"]["label"], "Fast") + + with patch.object(self.server, "_available_runtime_devices", return_value=["cpu", "cuda:0"]): + described = self.server.describe_node_params( + { + "device": {"options": ["cpu", "cuda:1"], "postProcess": str}, + "mode": {"options": {"fast": "Fast"}}, + "layout": {"display": "ui_group", "options": ["prompt", "negative_prompt"]}, + } + ) + self.assertNotIn("postProcess", described["device"]) + self.assertEqual(described["device"]["options"][1]["compatibility"], "incompatible") + self.assertEqual(described["device"]["options"][2]["value"], "cuda:0") + self.assertEqual(described["device"]["options"][2]["availability"], "installed") + self.assertEqual(described["mode"]["options"]["fast"]["value"], "fast") + self.assertEqual(described["layout"]["options"], ["prompt", "negative_prompt"]) + + def test_runtime_device_options_keep_cpu_aliases_and_plain_labels(self): + with ( + patch("torch.cuda.is_available", return_value=False), + patch("torch.backends.mps.is_available", return_value=False), + ): + self.assertEqual(self.server._available_runtime_devices(), ["cpu", "cpu:0"]) + + described = self.server._option_descriptors( + "device", + { + "options": { + "cuda:0": {"label": ["cuda:0"], "name": "GPU 0"}, + "cpu:0": {"label": ["cpu:0"], "name": "CPU 0"}, + } + }, + ) + self.assertEqual(described["cuda:0"]["label"], "cuda:0") + self.assertEqual(described["cpu:0"]["label"], "cpu:0") + self.assertEqual(described["cpu:0"]["availability"], "installed") + + def test_runtime_options_disable_unavailable_attention_and_quantization_backends(self): + self.server._runtime_choice_capabilities_cache = { + "attention_backends": { + "native": {"available": True, "reason": "PyTorch SDPA fallback"}, + "flash": {"available": False, "reason": "Requires NVIDIA CUDA"}, + }, + "quantization_backends": { + "none": {"available": True}, + "bnb_4bit": {"available": False, "reason": "Not qualified on AMD ROCm"}, + }, + "dtypes": {"float32": True, "bfloat16": True}, + } + + attention = self.server._option_descriptors( + "attention_backend", + {"options": ["native", "flash"]}, + ) + quantization = self.server._option_descriptors( + "quantization_mode", + {"options": ["none", "bnb_4bit"]}, + ) + + self.assertEqual(attention[0]["availability"], "installed") + self.assertEqual(attention[1]["availability"], "unavailable") + self.assertEqual(attention[1]["disabledReason"], "Requires NVIDIA CUDA") + self.assertEqual(quantization[0]["availability"], "installed") + self.assertEqual(quantization[1]["availability"], "unavailable") + self.assertEqual(quantization[1]["disabledReason"], "Not qualified on AMD ROCm") + + async def test_broadcast_prunes_a_transport_closed_during_send(self): + class ClosingWebsocket: + closed = False + + async def send_json(self, _message): + self.closed = True + raise RuntimeError("Cannot write to closing transport") + + websocket = ClosingWebsocket() + self.server.ws_sessions = {"refreshing-client": websocket} + + with ( + patch("modiff.server.logger.debug") as debug, + patch("modiff.server.logger.warning") as warning, + ): + await self.server.broadcast({"type": "task_progress"}) + + self.assertNotIn("refreshing-client", self.server.ws_sessions) + warning.assert_not_called() + self.assertIn("Dropped closing session", debug.call_args.args[0]) + + async def test_stop_cancels_queued_runs_and_interrupts_the_active_pipeline(self): + pipeline = SimpleNamespace(_interrupt=False) + node = SimpleNamespace(_interrupt=False, _active_pipeline=pipeline) + self.server.node_cache = {"active-node": node} + self.server.current_task = { + "task_id": "active-task", + "sid": "session", + "runtimeHints": {"workflowTabId": "workflow-active"}, + } + self.server.queued_tasks = { + "queued-one": { + "name": "Graph execution", + "sid": "session", + "runtimeHints": {"workflowTabId": "workflow-one"}, + }, + "queued-two": { + "name": "Graph execution", + "sid": "session", + "runtimeHints": {"workflowTabId": "workflow-two"}, + }, + } + self.server.task_graphs = { + "queued-one": {"nodes": {}}, + "queued-two": {"nodes": {}}, + } + + with ( + patch.object(self.server, "queue_message") as queue_message, + patch.object( + self.server, + "_schedule_forced_restart_if_still_running", + return_value=2000, + ) as schedule_restart, + ): + response = await self.server.stop_execution(None) + + payload = json.loads(response.text) + self.assertFalse(payload["error"]) + self.assertTrue(payload["cleanup_pending"]) + self.assertTrue(payload["hard_restart_scheduled"]) + self.assertEqual(payload["hard_restart_after_ms"], 2000) + self.assertEqual(payload["task_id"], "active-task") + self.assertCountEqual(payload["cancelled_queued_task_ids"], ["queued-one", "queued-two"]) + self.assertEqual(self.server.queued_tasks, {}) + self.assertEqual(self.server.task_graphs, {}) + self.assertTrue(self.server.interrupt_flag) + self.assertTrue(self.server.current_task["interrupt_requested"]) + self.assertEqual(self.server.current_task["phase"], "stopping") + self.assertTrue(node._interrupt) + self.assertTrue(pipeline._interrupt) + cancelled_messages = [ + call.args[0] + for call in queue_message.call_args_list + if call.args and call.args[0].get("type") == "task_cancelled" + ] + self.assertCountEqual( + [message["task_id"] for message in cancelled_messages], + ["queued-one", "queued-two"], + ) + progress_messages = [ + call.args[0] + for call in queue_message.call_args_list + if call.args and call.args[0].get("type") == "task_progress" + ] + self.assertEqual(progress_messages[-1]["task_id"], "active-task") + self.assertEqual(progress_messages[-1]["phase"], "stopping") + schedule_restart.assert_called_once_with("active-task") + + def test_forced_restart_is_scheduled_only_for_a_supervised_worker(self): + with patch.dict(os.environ, {}, clear=False): + os.environ.pop("MODIFF_WORKER_SUPERVISED", None) + self.assertIsNone(self.server._schedule_forced_restart_if_still_running("task")) + + timer = SimpleNamespace(daemon=False, start=Mock(), cancel=Mock()) + with ( + patch.dict( + os.environ, + { + "MODIFF_WORKER_SUPERVISED": "1", + "MODIFF_HARD_CANCEL_GRACE_SECONDS": "1.25", + }, + ), + patch("modiff.server.threading.Timer", return_value=timer) as timer_factory, + ): + self.assertEqual(self.server._schedule_forced_restart_if_still_running("task"), 1250) + + timer_factory.assert_called_once_with( + 1.25, + self.server._force_restart_if_task_is_active, + args=("task",), + ) + self.assertTrue(timer.daemon) + timer.start.assert_called_once_with() + + def test_queue_snapshots_include_workflow_navigation_without_repeating_it_in_progress_identity(self): + workflow_snapshot = {"nodes": [{"id": "loader"}], "edges": []} + runtime_hints = self.server._coerce_runtime_hints( + { + "clientRunId": "client-run", + "workflowTabId": "workflow-one", + "workflowTitle": "Workflow One", + "workflowSnapshot": workflow_snapshot, + } + ) + self.assertEqual(runtime_hints["workflowTitle"], "Workflow One") + self.assertEqual(runtime_hints["workflowSnapshot"], workflow_snapshot) + self.server.current_task = { + "task_id": "active-task", + "name": "Graph execution", + "sid": "session", + "started_at": 1, + "runtimeHints": runtime_hints, + } + self.server.queued_tasks = { + "queued-task": { + "name": "Graph execution", + "sid": "session", + "queued_at": 2, + "runtimeHints": runtime_hints, + }, + } + + queued, current = self.server._get_queue() + + self.assertEqual(current["workflow_title"], "Workflow One") + self.assertEqual(current["workflow_snapshot"], workflow_snapshot) + self.assertEqual(queued["queued-task"]["workflow_snapshot"], workflow_snapshot) + progress_identity = self.server._current_run_identity_payload() + self.assertNotIn("workflow_snapshot", progress_identity) + self.assertNotIn("workflow_title", progress_identity) + + def test_graph_start_snapshot_identifies_the_first_node_before_model_loading(self): + graph = { + "nodes": { + "loader": { + "module": "modules.DiffusersImage", + "action": "LoadPipeline", + "params": {}, + }, + "generate": { + "module": "modules.DiffusersImage", + "action": "Generate", + "params": {}, + }, + }, + "paths": [["loader", "generate"]], + } + + state = self.server._initial_graph_execution_state((graph,)) + + self.assertEqual(state["current_node"], "loader") + self.assertEqual(state["current_node_name"], "modules.DiffusersImage.LoadPipeline") + self.assertEqual(state["node_progress"], -1) + self.assertEqual(state["phase"], "loading") + self.assertIn("Loading", state["message"]) + + def test_first_measurable_node_progress_bypasses_supervisor_snapshot_throttle(self): + self.server.current_task = { + "task_id": "active-task", + "node_progress": -1, + "current_step": None, + "completed_progress": 25, + "current_node_weight": 50, + } + + with patch.object(self.server, "_persist_supervisor_queue_state") as persist: + payload = self.server.record_node_progress({ + "task_id": "active-task", + "node": "generate", + "progress": 0, + "phase": "denoising", + "message": "Denoising 0/50", + "current_step": 0, + "total_steps": 50, + }) + + self.assertEqual(self.server.current_task["node_progress"], 0) + self.assertEqual(self.server.current_task["total_steps"], 50) + self.assertEqual(payload["overall_progress"], 25) + persist.assert_called_once_with(force=True) + + def test_later_node_progress_uses_the_normal_supervisor_snapshot_throttle(self): + self.server.current_task = { + "task_id": "active-task", + "node_progress": 10, + "current_step": 5, + "total_steps": 50, + "completed_progress": 25, + "current_node_weight": 50, + } + + with patch.object(self.server, "_persist_supervisor_queue_state") as persist: + self.server.record_node_progress({ + "task_id": "active-task", + "node": "generate", + "progress": 12, + "phase": "denoising", + "current_step": 6, + "total_steps": 50, + }) + + persist.assert_called_once_with(force=False) + + def test_loader_cache_moves_across_workflow_node_ids_but_not_within_the_active_graph(self): + reuse_preparations = [] + reusable = SimpleNamespace( + module_name="modules.DiffusersImage", + class_name="LoadPipeline", + node_id="old-loader", + prepare_for_workflow_reuse=lambda: reuse_preparations.append("old-loader"), + ) + active = SimpleNamespace( + module_name="modules.DiffusersImage", + class_name="LoadPipeline", + node_id="active-loader", + ) + self.server.node_cache = { + "old-loader": reusable, + "active-loader": active, + } + self.server._active_graph_node_ids = {"active-loader", "new-loader"} + + previous_id = self.server._adopt_reusable_loader_node( + "new-loader", + "modules.DiffusersImage", + "LoadPipeline", + ) + + self.assertEqual(previous_id, "old-loader") + self.assertNotIn("old-loader", self.server.node_cache) + self.assertIs(self.server.node_cache["new-loader"], reusable) + self.assertEqual(reusable.node_id, "new-loader") + self.assertEqual(reuse_preparations, ["old-loader"]) + self.assertIs(self.server.node_cache["active-loader"], active) + self.assertIsNone( + self.server._adopt_reusable_loader_node( + "other-loader", + "modules.DiffusersImage", + "Generate", + ) + ) + + async def test_cancelled_run_releases_runtime_before_the_queue_advances(self): + events = [] + + async def run_callback(_callback, *, serialize_model_io=False): + if "first" not in events: + events.append("first") + self.server.current_task["interrupt_requested"] = True + return None + events.append("second") + self.server._shutdown_event.set() + return None + + def release_runtime(): + events.append("cleanup") + return {"released": {}, "errors": []} + + task = lambda: None + self.server.loop = __import__("asyncio").get_running_loop() + self.server.queued_tasks = { + "first-task": { + "name": "Graph execution", + "sid": "session", + "queued_at": 1, + "runtimeHints": {"resourceMode": "auto", "modelType": "FirstFamily"}, + }, + "second-task": { + "name": "Graph execution", + "sid": "session", + "queued_at": 2, + "runtimeHints": {"resourceMode": "auto", "modelType": "SecondFamily"}, + }, + } + await self.server.main_queue.put((task, (), None, "first-task")) + await self.server.main_queue.put((task, (), None, "second-task")) + + with ( + patch.object(self.server, "_run_executor_callback", side_effect=run_callback), + patch.object(self.server, "_release_runtime_caches_for_retry", side_effect=release_runtime), + patch.object(self.server, "_record_terminal_task", return_value={"completed_at": 1}), + patch.object(self.server, "queue_message"), + ): + await self.server._main_worker() + + self.assertEqual(events, ["first", "cleanup", "second"]) + self.assertIsNone(self.server.current_task) + self.assertFalse(self.server.interrupt_flag) + + def test_auto_pre_run_cleanup_releases_unknown_and_cross_family_caches(self): + self.server.node_cache = {"cached-node": object()} + snapshot = hardware_snapshot() + cleanup = {"released": {"nodes": 1}, "errors": []} + + with ( + patch("modiff.server.get_hardware_snapshot", return_value=snapshot), + patch.object(self.server, "_release_runtime_caches_for_retry", return_value=cleanup) as release, + patch.object(self.server, "queue_message"), + ): + first = self.server._prepare_auto_runtime_for_graph({ + "resourceMode": "auto", + "modelType": "QwenImage", + }) + second = self.server._prepare_auto_runtime_for_graph({ + "resourceMode": "auto", + "modelType": "AceStep", + }) + + self.assertTrue(first["performed"]) + self.assertIn("cached model family is unknown", first["reasons"]) + self.assertTrue(second["performed"]) + self.assertIn("model family changed from QwenImage to AceStep", second["reasons"]) + self.assertEqual(release.call_count, 2) + self.assertEqual(self.server._last_auto_model_family, "AceStep") + + def test_auto_pre_run_cleanup_preserves_same_family_cache_with_headroom(self): + self.server.node_cache = {"cached-node": object()} + self.server._last_auto_model_family = "QwenImage" + + with ( + patch("modiff.server.get_hardware_snapshot", return_value=hardware_snapshot()), + patch.object(self.server, "_release_runtime_caches_for_retry") as release, + patch.object(self.server, "queue_message"), + ): + result = self.server._prepare_auto_runtime_for_graph({ + "resourceMode": "auto", + "modelType": "QwenImage", + "autoResourcePlan": { + "requirements": { + "minimum": { + "systemRamBytes": 8 * GIB, + "vramBytes": 8 * GIB, + }, + }, + }, + }) + + self.assertFalse(result["performed"]) + self.assertEqual(result["reasons"], []) + release.assert_not_called() + + def test_auto_pre_run_cleanup_does_not_count_resident_recipe_minimum_twice(self): + self.server.node_cache = {"cached-node": object()} + self.server._last_auto_model_family = "QwenImageEdit" + runtime_hints = { + "resourceMode": "auto", + "modelType": "QwenImageEdit", + "loaderContract": ["modules.ModularDiffusers.ModelsLoader"], + "autoResourcePlan": { + "modelType": "QwenImageEdit", + "artifact": "Qwen/Qwen-Image-Edit-2511", + "pipelineClass": "QwenImageEditPlusModularPipeline", + "dtype": "bfloat16", + "quantizationMode": "none", + "offloadMode": "none", + "deviceMap": "cuda", + "requirements": { + "minimum": { + "systemRamBytes": 64 * GIB, + "vramBytes": 64 * GIB, + }, + }, + }, + } + self.server._last_auto_resource_signature = self.server._auto_candidate_cache_signature( + runtime_hints + ) + snapshot = hardware_snapshot() + snapshot["system"]["ram_available"] = 16 * GIB + snapshot["devices"][0]["torch_vram_free"] = 16 * GIB + snapshot["devices"][0]["vram_free"] = 16 * GIB + + with ( + patch("modiff.server.get_hardware_snapshot", return_value=snapshot), + patch.object(self.server, "_release_runtime_caches_for_retry") as release, + patch.object(self.server, "queue_message"), + ): + result = self.server._prepare_auto_runtime_for_graph(runtime_hints) + + self.assertTrue(result["residentRecipeReusable"]) + self.assertFalse(result["performed"]) + self.assertEqual(result["reasons"], []) + release.assert_not_called() + + def test_auto_pre_run_cleanup_releases_incompatible_same_family_recipe(self): + self.server.node_cache = {"cached-node": object()} + self.server._last_auto_model_family = "QwenImage" + resident = { + "resourceMode": "auto", + "modelType": "QwenImage", + "autoResourcePlan": { + "id": "qwen-text-native", + "modelType": "QwenImage", + "artifact": "Qwen/Qwen-Image-2512", + "dtype": "bfloat16", + "quantizationMode": "none", + "offloadMode": "none", + "deviceMap": "cuda", + "requirements": {"minimum": {"systemRamBytes": 8 * GIB, "vramBytes": 8 * GIB}}, + }, + } + offloaded = copy.deepcopy(resident) + offloaded["autoResourcePlan"].update({ + "id": "qwen-control-model-cpu", + "offloadMode": "model_cpu", + "deviceMap": None, + }) + + with ( + patch("modiff.server.get_hardware_snapshot", return_value=hardware_snapshot()), + patch.object( + self.server, + "_release_runtime_caches_for_retry", + return_value={"released": {}, "errors": []}, + ) as release, + patch.object(self.server, "queue_message"), + ): + first = self.server._prepare_auto_runtime_for_graph(resident) + second = self.server._prepare_auto_runtime_for_graph(offloaded) + + self.assertFalse(first["performed"]) + self.assertTrue(second["performed"]) + self.assertTrue(second["resourceRecipeChanged"]) + self.assertIn("Auto resource recipe changed within QwenImage", second["reasons"]) + release.assert_called_once() + + def test_auto_pre_run_cleanup_ignores_candidate_id_when_recipe_is_identical(self): + self.server.node_cache = {"cached-node": object()} + self.server._last_auto_model_family = "QwenImageEdit" + first_plan = { + "resourceMode": "auto", + "modelType": "QwenImageEdit", + "autoResourcePlan": { + "id": "qwen-edit", + "modelType": "QwenImageEdit", + "artifact": "Qwen/Qwen-Image-Edit", + "dtype": "bfloat16", + "quantizationMode": "none", + "offloadMode": "none", + "deviceMap": "cuda", + }, + } + second_plan = copy.deepcopy(first_plan) + second_plan["autoResourcePlan"]["id"] = "qwen-inpaint" + + with ( + patch("modiff.server.get_hardware_snapshot", return_value=hardware_snapshot()), + patch.object(self.server, "_release_runtime_caches_for_retry") as release, + patch.object(self.server, "queue_message"), + ): + self.server._prepare_auto_runtime_for_graph(first_plan) + result = self.server._prepare_auto_runtime_for_graph(second_plan) + + self.assertFalse(result["performed"]) + self.assertFalse(result["resourceRecipeChanged"]) + release.assert_not_called() + + def test_auto_pre_run_cleanup_distinguishes_diffusers_loader_topology(self): + self.server.node_cache = {"cached-node": object()} + self.server._last_auto_model_family = "QwenImageEdit" + common = { + "resourceMode": "auto", + "modelType": "QwenImageEdit", + "autoResourcePlan": { + "modelType": "QwenImageEdit", + "artifact": "Qwen/Qwen-Image-Edit", + "pipelineClass": "QwenImageEditModularPipeline", + "dtype": "bfloat16", + "quantizationMode": "none", + "offloadMode": "none", + "deviceMap": "cuda", + }, + } + component_graph = { + **copy.deepcopy(common), + "loaderContract": ["modules.ModularDiffusers.ModelsLoader"], + } + assembled_graph = { + **copy.deepcopy(common), + "loaderContract": ["modules.DiffusersImage.LoadPipeline"], + } + + with ( + patch("modiff.server.get_hardware_snapshot", return_value=hardware_snapshot()), + patch.object( + self.server, + "_release_runtime_caches_for_retry", + return_value={"released": {}, "errors": []}, + ) as release, + patch.object(self.server, "queue_message"), + ): + first = self.server._prepare_auto_runtime_for_graph(component_graph) + second = self.server._prepare_auto_runtime_for_graph(assembled_graph) + + self.assertFalse(first["performed"]) + self.assertTrue(second["performed"]) + self.assertIn("Auto resource recipe changed within QwenImageEdit", second["reasons"]) + release.assert_called_once() + + def test_auto_pre_run_cleanup_releases_same_family_cache_under_live_pressure(self): + self.server.node_cache = {"cached-node": object()} + self.server._last_auto_model_family = "QwenImage" + snapshot = hardware_snapshot() + snapshot["system"]["ram_available"] = 3 * GIB + snapshot["devices"][0]["torch_vram_free"] = 1 * GIB + snapshot["devices"][0]["vram_free"] = 1 * GIB + + with ( + patch("modiff.server.get_hardware_snapshot", return_value=snapshot), + patch.object( + self.server, + "_release_runtime_caches_for_retry", + return_value={"released": {}, "errors": []}, + ) as release, + patch.object(self.server, "queue_message"), + ): + result = self.server._prepare_auto_runtime_for_graph({ + "resourceMode": "auto", + "modelType": "QwenImage", + }) + + self.assertTrue(result["performed"]) + self.assertIn("available system memory is below the safety floor", result["reasons"]) + self.assertIn("available accelerator memory is below the safety floor", result["reasons"]) + release.assert_called_once() + + def test_failed_or_cancelled_run_cleanup_trims_device_and_process_allocators(self): + self.server.node_cache = {"cached-node": object()} + with ( + patch("modiff.server.memory_manager.clear", return_value=1), + patch.object(self.server, "_release_modular_diffusers_components", return_value=(0, [])), + patch.object(self.server, "_release_diffusers_offload_cache", return_value=(0, [])), + patch.object(self.server, "_best_effort_device_cache_clear", return_value=[]) as device_clear, + patch.object(self.server, "_best_effort_allocator_trim", return_value=(True, [])) as allocator_trim, + patch("modiff.server.gc.collect", return_value=0), + ): + result = self.server._release_runtime_caches_for_retry() + + self.assertEqual(result["released"]["nodes"], 1) + self.assertEqual(result["released"]["models"], 1) + self.assertTrue(result["allocatorTrimmed"]) + self.assertEqual(result["errors"], []) + device_clear.assert_called_once_with() + allocator_trim.assert_called_once_with() + + def test_node_input_validation_does_not_poison_auto_resource_history(self): + try: + raise ValueError("LTX prompt exceeds the artifact token limit") + except ValueError as cause: + wrapped = RuntimeError("Error executing modules.DiffusersVideo.Generate") + wrapped.__cause__ = cause + + classification = self.server._classify_exception(wrapped) + self.assertEqual(classification["category"], "input_validation") + self.server.current_task = {"runtimeHints": {"resourceMode": "auto"}} + with patch("modiff.server.record_auto_resource_failure") as record_failure: + self.server._record_auto_resource_failure(wrapped, classification) + record_failure.assert_not_called() + + def test_video_runtime_limit_is_numeric_and_capped_at_six_hours(self): + hints = self.server._coerce_runtime_hints({"maxRuntimeSeconds": 999999}) + minimum = self.server._coerce_runtime_hints({"maxRuntimeSeconds": 1}) + + self.assertEqual(hints["maxRuntimeSeconds"], 43200) + self.assertEqual(minimum["maxRuntimeSeconds"], 60) + + def test_oom_still_records_auto_resource_failure(self): + self.server.current_task = {"runtimeHints": {"resourceMode": "auto"}} + classification = {"category": "oom", "error_code": "cuda_oom"} + with patch("modiff.server.record_auto_resource_failure") as record_failure: + self.server._record_auto_resource_failure(RuntimeError("CUDA out of memory"), classification) + record_failure.assert_called_once() + async def test_runtime_status_preserves_existing_fields_and_adds_hardware(self): snapshot = hardware_snapshot() self.server._package_status = available_package + profile = { + "requested": "nvidia-cuda", + "installed": "nvidia-cuda", + "detected": "nvidia-cuda", + "status": "ready", + "execution_ready": True, + "issues": [], + } - with patch("modiff.server.get_hardware_snapshot", return_value=copy.deepcopy(snapshot)) as get_snapshot: + with ( + patch.object( + self.server, + "_runtime_fingerprint", + return_value={"fingerprint": "sha256:runtime-ready", "hardware": copy.deepcopy(snapshot)}, + ), + patch("modiff.server.runtime_profile", return_value=profile), + ): response = await self.server.runtime_status(None) payload = json.loads(response.text) self.assertTrue({ "error", "ready", + "runtime_fingerprint", "instance", "server", "python", @@ -142,10 +916,270 @@ async def test_runtime_status_preserves_existing_fields_and_adds_hardware(self): "queue", }.issubset(payload)) self.assertEqual(payload["hardware"], snapshot) + self.assertEqual(payload["runtime_fingerprint"], "sha256:runtime-ready") + self.assertEqual(payload["runtime_profile"], profile) self.assertTrue(payload["ready"]) self.assertEqual(payload["packages"]["torch"]["cuda_device_name"], "Mock CUDA") self.assertEqual(payload["packages"]["torch"]["cuda_memory_free_bytes"], 12 * GIB) - get_snapshot.assert_called_once_with(self.server.data_dir) + + async def test_runtime_status_uses_cached_hardware_while_a_graph_is_running(self): + snapshot = hardware_snapshot() + self.server._package_status = available_package + self.server.current_task = {"task_id": "active-run", "name": "Graph execution"} + self.server._last_runtime_fingerprint = { + "fingerprint": "sha256:cached-execution", + "resourceFingerprint": "sha256:cached-resource", + "hardware": copy.deepcopy(snapshot), + } + profile = { + "requested": "nvidia-cuda", + "installed": "nvidia-cuda", + "detected": "nvidia-cuda", + "status": "ready", + "execution_ready": True, + "issues": [], + } + + with ( + patch.object( + self.server, + "_runtime_fingerprint", + side_effect=AssertionError("active status must not enter accelerator APIs"), + ), + patch("modiff.server.runtime_profile", return_value=profile), + ): + response = await self.server.runtime_status(None) + + payload = json.loads(response.text) + self.assertTrue(payload["ready"]) + self.assertEqual(payload["runtime_fingerprint"], "sha256:cached-resource") + self.assertEqual(payload["hardware"], snapshot) + self.assertEqual(payload["queue"]["current"]["task_id"], "active-run") + + async def test_system_stats_uses_cached_hardware_while_a_graph_is_running(self): + snapshot = hardware_snapshot() + self.server.current_task = {"task_id": "active-run"} + self.server._last_runtime_fingerprint = { + "fingerprint": "sha256:cached", + "hardware": copy.deepcopy(snapshot), + } + + with patch( + "modiff.server.get_hardware_snapshot", + side_effect=AssertionError("active stats must not enter accelerator APIs"), + ): + response = await self.server.system_stats(None) + + self.assertEqual(json.loads(response.text), snapshot) + + async def test_runtime_status_is_not_ready_when_managed_profile_is_broken(self): + snapshot = hardware_snapshot(cuda=False) + self.server._package_status = available_package + profile = { + "requested": "amd-rocm-linux", + "installed": "nvidia-cuda", + "detected": "nvidia-cuda", + "status": "mismatch", + "execution_ready": False, + "issues": [ + { + "code": "profile-mismatch", + "severity": "error", + "message": "Requested amd-rocm-linux, but installed Torch resolves to nvidia-cuda.", + } + ], + "repair_command": "python -m modiff.install --accelerator amd --repair", + } + + with ( + patch.object( + self.server, + "_runtime_fingerprint", + return_value={"fingerprint": "sha256:runtime-broken", "hardware": copy.deepcopy(snapshot)}, + ), + patch("modiff.server.runtime_profile", return_value=profile), + ): + response = await self.server.runtime_status(None) + + payload = json.loads(response.text) + self.assertFalse(payload["ready"]) + self.assertEqual(payload["runtime_profile"]["status"], "mismatch") + + async def test_auto_plan_reports_environment_repair_before_model_planning(self): + profile = { + "requested": "amd-rocm-linux", + "installed": "nvidia-cuda", + "status": "mismatch", + "execution_ready": False, + "issues": [ + { + "code": "profile-mismatch", + "severity": "error", + "message": "Requested amd-rocm-linux, but installed Torch resolves to nvidia-cuda.", + } + ], + "repair_command": "python -m modiff.install --accelerator amd --repair", + } + request = JsonRequest( + {"form": {"modelType": "QwenImageModularPipeline", "mode": "text_to_image"}} + ) + + with ( + patch("modiff.server.get_hardware_snapshot", return_value=hardware_snapshot(cuda=False)), + patch("modiff.server.runtime_profile", return_value=profile), + patch("modiff.server.build_auto_resource_plan") as build_plan, + ): + response = await self.server.auto_resource_plan(request) + + payload = json.loads(response.text) + self.assertEqual(payload["issue"]["category"], "environment") + self.assertEqual(payload["issue"]["code"], "runtime_profile_mismatch") + self.assertEqual(payload["schemaVersion"], 2) + self.assertEqual(payload["compatibility"]["code"], "runtime_profile_mismatch") + self.assertEqual(payload["compatibility"]["source"], "backend_auto_planner") + self.assertEqual(payload["repairAction"]["type"], "open_setup") + self.assertEqual(payload["candidates"], []) + build_plan.assert_not_called() + + def test_auto_planning_counts_only_modiff_reserved_vram_as_reclaimable(self): + snapshot = hardware_snapshot() + snapshot["devices"][0]["vram_free"] = 4 * GIB + snapshot["devices"][0]["torch_vram_free"] = 5 * GIB + snapshot["torch"]["cuda_memory_free_bytes"] = 5 * GIB + fingerprint = { + "fingerprint": "sha256:runtime", + "resourceFingerprint": "sha256:resource", + "hardware": snapshot, + } + fake_cuda = SimpleNamespace( + is_available=lambda: True, + device_count=lambda: 1, + memory_reserved=lambda _index: 6 * GIB, + ) + self.server.node_cache = {"resident-loader": object()} + + with ( + patch.object(self.server, "_runtime_fingerprint", return_value=fingerprint), + patch("modiff.server.import_module", return_value=SimpleNamespace(cuda=fake_cuda)), + ): + adjusted = self.server._auto_planning_runtime_fingerprint() + + adjusted_device = adjusted["hardware"]["devices"][0] + self.assertEqual(adjusted_device["vram_free"], 10 * GIB) + self.assertEqual(adjusted_device["torch_vram_free"], 11 * GIB) + self.assertEqual(adjusted_device["modiff_reclaimable_vram"], 6 * GIB) + self.assertEqual( + adjusted["hardware"]["torch"]["cuda_memory_free_bytes"], + 11 * GIB, + ) + # The live snapshot remains authoritative and unmodified; the uplift is + # scoped only to this Auto planning request. + self.assertEqual(fingerprint["hardware"]["devices"][0]["vram_free"], 4 * GIB) + + def test_auto_planning_reclaimable_vram_is_capped_at_physical_capacity(self): + snapshot = hardware_snapshot() + fingerprint = {"fingerprint": "sha256:runtime", "hardware": snapshot} + fake_cuda = SimpleNamespace( + is_available=lambda: True, + device_count=lambda: 1, + memory_reserved=lambda _index: 8 * GIB, + ) + self.server.node_cache = {"resident-loader": object()} + + with ( + patch.object(self.server, "_runtime_fingerprint", return_value=fingerprint), + patch("modiff.server.import_module", return_value=SimpleNamespace(cuda=fake_cuda)), + ): + adjusted = self.server._auto_planning_runtime_fingerprint() + + adjusted_device = adjusted["hardware"]["devices"][0] + self.assertEqual(adjusted_device["vram_free"], 16 * GIB) + self.assertEqual(adjusted_device["torch_vram_free"], 16 * GIB) + + def test_auto_planning_does_not_enter_accelerator_apis_during_active_run(self): + snapshot = hardware_snapshot() + fingerprint = { + "fingerprint": "sha256:cached-execution", + "resourceFingerprint": "sha256:cached-resource", + "hardware": copy.deepcopy(snapshot), + } + self.server.current_task = {"task_id": "active-run"} + self.server._last_runtime_fingerprint = copy.deepcopy(fingerprint) + self.server.node_cache = {"loading-pipeline": object()} + + with ( + patch.object( + self.server, + "_runtime_fingerprint", + side_effect=AssertionError("active planning must use the cached fingerprint"), + ), + patch( + "modiff.server.import_module", + side_effect=AssertionError("active planning must not enter torch.cuda"), + ), + ): + adjusted = self.server._auto_planning_runtime_fingerprint() + + self.assertEqual(adjusted, fingerprint) + + async def test_auto_plan_uses_capacity_after_releasing_its_resident_cache(self): + snapshot = hardware_snapshot() + snapshot["devices"][0]["vram_free"] = 4 * GIB + snapshot["devices"][0]["torch_vram_free"] = 5 * GIB + fingerprint = {"fingerprint": "sha256:runtime", "hardware": snapshot} + fake_cuda = SimpleNamespace( + is_available=lambda: True, + device_count=lambda: 1, + memory_reserved=lambda _index: 6 * GIB, + ) + self.server.node_cache = {"resident-loader": object()} + request = JsonRequest( + {"form": {"modelType": "QwenImageModularPipeline", "mode": "text_to_image"}} + ) + + with ( + patch.object(self.server, "_auto_resource_runtime_block", return_value=None), + patch.object(self.server, "_runtime_fingerprint", return_value=fingerprint), + patch("modiff.server.import_module", return_value=SimpleNamespace(cuda=fake_cuda)), + patch("modiff.server.get_local_models", return_value=[]), + patch("modiff.server.read_auto_resource_history", return_value={}), + patch( + "modiff.server.build_auto_resource_plan", + return_value={"error": False, "status": "ready"}, + ) as build_plan, + ): + response = await self.server.auto_resource_plan(request) + + self.assertEqual(json.loads(response.text)["status"], "ready") + planning_fingerprint = build_plan.call_args.kwargs["runtime_fingerprint"] + planning_device = planning_fingerprint["hardware"]["devices"][0] + self.assertEqual(planning_device["vram_free"], 10 * GIB) + self.assertEqual(planning_device["torch_vram_free"], 11 * GIB) + + async def test_graph_queue_rejects_a_broken_managed_runtime(self): + runtime_block = { + "issue": { + "code": "runtime_profile_mismatch", + "category": "environment", + "message": "Managed runtime mismatch.", + }, + "repairAction": { + "type": "open_setup", + "label": "Open Setup", + "command": "python -m modiff.install --accelerator amd --repair", + }, + "runtimeProfile": {"execution_ready": False, "status": "mismatch"}, + } + self.server.queue_task = AsyncMock() + + with patch.object(self.server, "_auto_resource_runtime_block", return_value=runtime_block): + response = await self.server.graph(JsonRequest({"sid": "unit"})) + + payload = json.loads(response.text) + self.assertEqual(response.status, 409) + self.assertEqual(payload["error_code"], "runtime_profile_mismatch") + self.assertEqual(payload["repair_action"]["type"], "open_setup") + self.server.queue_task.assert_not_awaited() async def test_system_stats_route_returns_normalized_snapshot(self): snapshot = hardware_snapshot() @@ -160,6 +1194,48 @@ async def test_system_stats_route_returns_normalized_snapshot(self): self.assertEqual(payload["devices"], snapshot["devices"]) self.assertEqual(payload["default_device"], "cuda:0") + async def test_gpu_cleanup_refuses_to_clear_nodes_while_task_is_active(self): + cached_node = object() + self.server.node_cache = {"active-node": cached_node} + self.server.current_task = {"task_id": "active-task", "name": "Graph execution"} + + response = await self.server.runtime_gpu_cleanup(None) + + payload = json.loads(response.text) + self.assertEqual(response.status, 409) + self.assertTrue(payload["error"]) + self.assertEqual(payload["error_code"], "runtime_cleanup_busy") + self.assertEqual(payload["task_id"], "active-task") + self.assertIs(self.server.node_cache["active-node"], cached_node) + + async def test_gpu_cleanup_detaches_managed_models_before_destroying_cached_nodes(self): + cleanup_order = [] + + class OrderedNodeCache(dict): + def clear(self): + cleanup_order.append("nodes") + super().clear() + + self.server.node_cache = OrderedNodeCache({"loader": object()}) + with ( + patch("modiff.server.memory_manager.clear", side_effect=lambda: cleanup_order.append("models") or 1), + patch.object(self.server, "_cuda_memory_snapshot", return_value={"available": False}), + patch.object(self.server, "_release_modular_diffusers_components", return_value=(0, [])), + patch.object(self.server, "_release_diffusers_offload_cache", return_value=(0, [])), + patch.object(self.server, "_best_effort_device_cache_clear", return_value=[]), + patch.object(self.server, "_best_effort_allocator_trim", return_value=(True, [])) as allocator_trim, + patch("modiff.server.gc.collect", return_value=0), + ): + response = await self.server.runtime_gpu_cleanup(None) + + payload = json.loads(response.text) + self.assertFalse(payload["error"]) + self.assertEqual(cleanup_order, ["models", "nodes"]) + self.assertEqual(payload["released_model_count"], 1) + self.assertEqual(payload["released_node_count"], 1) + self.assertTrue(payload["allocator_trimmed"]) + allocator_trim.assert_called_once_with() + def test_runtime_fingerprint_exposes_hardware_without_hashing_ram_or_disk(self): first_snapshot = hardware_snapshot(ram_total=32 * GIB, cuda=False) second_snapshot = hardware_snapshot(ram_total=64 * GIB, cuda=False) @@ -168,7 +1244,7 @@ def test_runtime_fingerprint_exposes_hardware_without_hashing_ram_or_disk(self): with patch( "modiff.server.get_hardware_snapshot", side_effect=[copy.deepcopy(first_snapshot), copy.deepcopy(second_snapshot)], - ): + ) as get_snapshot: first = self.server._runtime_fingerprint() second = self.server._runtime_fingerprint() @@ -177,9 +1253,60 @@ def test_runtime_fingerprint_exposes_hardware_without_hashing_ram_or_disk(self): self.assertEqual(second["hardware"]["system"]["ram_total"], 64 * GIB) self.assertTrue({"packages", "torch", "work_dir", "data_dir", "hardware"}.issubset(first)) self.assertFalse(first["torch"]["cuda_available"]) + self.assertEqual( + get_snapshot.call_args_list, + [ + unittest.mock.call(self.server.data_dir, refresh=True), + unittest.mock.call(self.server.data_dir, refresh=True), + ], + ) + + def test_resource_fingerprint_ignores_free_memory_and_deterministic_run_state(self): + first_snapshot = hardware_snapshot() + second_snapshot = copy.deepcopy(first_snapshot) + second_snapshot["devices"][0]["vram_free"] = 4 * GIB + second_snapshot["devices"][0]["torch_vram_free"] = 5 * GIB + third_snapshot = copy.deepcopy(second_snapshot) + third_snapshot["torch"]["deterministic_algorithms"] = True + third_snapshot["torch"]["cudnn_deterministic"] = True + + with patch( + "modiff.server.get_hardware_snapshot", + side_effect=[first_snapshot, second_snapshot, third_snapshot], + ): + first = self.server._runtime_fingerprint() + second = self.server._runtime_fingerprint() + third = self.server._runtime_fingerprint() + + self.assertEqual(first["fingerprint"], second["fingerprint"]) + self.assertNotEqual(second["fingerprint"], third["fingerprint"]) + self.assertEqual(first["resourceFingerprint"], second["resourceFingerprint"]) + self.assertEqual(second["resourceFingerprint"], third["resourceFingerprint"]) class PreflightHardwareTests(unittest.TestCase): + def test_diffusers_package_status_rejects_an_older_api_contract(self): + old_diffusers = SimpleNamespace( + __version__="0.39.0", + AceStepPipeline=type("AceStepPipeline", (), {}), + ) + with ( + patch("modiff.preflight.metadata.version", return_value="0.39.0"), + patch("modiff.preflight.importlib.import_module", return_value=old_diffusers), + ): + status = preflight.package_status("diffusers", "diffusers") + + self.assertFalse(status["available"]) + self.assertEqual( + status["contractMissing"], + [ + "AceStepPipeline.load_lora_weights", + "AceStepPipeline.set_adapters", + "AceStepPipeline.unload_lora_weights", + ], + ) + self.assertIn("Repair the managed environment", status["error"]) + def test_report_adds_hardware_and_preserves_torch_human_summary(self): snapshot = hardware_snapshot() @@ -197,6 +1324,10 @@ def package_status(module_name, distribution_name, import_check=True): with ( patch("modiff.preflight.package_status", side_effect=package_status), patch("modiff.preflight.get_hardware_snapshot", return_value=copy.deepcopy(snapshot)), + patch( + "modiff.preflight.runtime_profile", + return_value={"execution_ready": True, "issues": [], "status": "ready"}, + ), patch("modiff.preflight.port_in_use", return_value=False), ): report = preflight.build_report(args) @@ -217,9 +1348,57 @@ def package_status(module_name, distribution_name, import_check=True): output = io.StringIO() with redirect_stdout(output): preflight.print_human(report) - self.assertIn("Torch: unit-test CUDA available (Mock CUDA); MPS not available", output.getvalue()) + self.assertIn( + "Torch: unit-test CUDA available (Mock CUDA); XPU not available; MPS not available", + output.getvalue(), + ) self.assertIn("Namespace: use python -m modiff.preflight", output.getvalue()) + def test_report_fails_when_managed_runtime_profile_is_broken(self): + snapshot = hardware_snapshot(cuda=False) + + def package_status(module_name, distribution_name, import_check=True): + return { + "module": module_name, + "distribution": distribution_name, + "available": True, + "importChecked": import_check, + "version": "unit-test", + } + + profile = { + "execution_ready": False, + "status": "repair-required", + "repair_required": True, + "repair_command": "python -m modiff.install --accelerator cpu --repair", + "issues": [ + { + "code": "runtime-contract-drift", + "severity": "error", + "message": "The reviewed CPU runtime inputs changed after installation.", + } + ], + } + args = SimpleNamespace(check_port=65534, full=False) + with ( + patch("modiff.preflight.package_status", side_effect=package_status), + patch("modiff.preflight.get_hardware_snapshot", return_value=copy.deepcopy(snapshot)), + patch("modiff.preflight.runtime_profile", return_value=profile), + patch("modiff.preflight.port_in_use", return_value=False), + ): + report = preflight.build_report(args) + + self.assertFalse(report["ready"]) + self.assertTrue(report["error"]) + self.assertEqual(report["runtimeProfile"], profile) + self.assertIn("changed after installation", report["issues"][0]) + + output = io.StringIO() + with redirect_stdout(output): + preflight.print_human(report) + self.assertIn("Runtime profile: repair-required", output.getvalue()) + self.assertIn(profile["repair_command"], output.getvalue()) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_server_security.py b/tests/test_server_security.py new file mode 100644 index 0000000..cbbda2b --- /dev/null +++ b/tests/test_server_security.py @@ -0,0 +1,330 @@ +import base64 +import json +import tempfile +import unittest +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import patch + +from aiohttp import web +from aiohttp.test_utils import TestClient, TestServer + +from modiff.server import WebServer, is_hidden_path + + +class JsonRequest: + def __init__(self, payload, *, origin=None, host="127.0.0.1:8088"): + self._payload = payload + self.headers = {"Origin": origin} if origin else {} + self.host = host + self.query = {} + self.match_info = {} + self.can_read_body = True + + async def json(self): + return self._payload + + +class WebSocketRequest: + def __init__(self, *, origin=None, host="127.0.0.1:8088", remote="127.0.0.1", sid="test-session"): + self.headers = {"Origin": origin} if origin else {} + self.host = host + self.remote = remote + self.query = {"sid": sid} + + +class EmptyWebSocket: + def __init__(self): + self.closed = False + self.prepared = False + self.messages = [] + + async def prepare(self, _request): + self.prepared = True + + async def send_json(self, message): + self.messages.append(message) + + def __aiter__(self): + return self + + async def __anext__(self): + raise StopAsyncIteration + + +class ServerSecurityTests(unittest.IsolatedAsyncioTestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + self.server = WebServer( + modules={}, + work_dir=self.temporary.name, + data_dir=self.temporary.name, + ) + + def tearDown(self): + self.temporary.cleanup() + + def test_state_changing_control_routes_are_post_only(self): + methods = {(route.method, route.resource.canonical) for route in self.server.app.router.routes()} + self.assertIn(("POST", "/stop"), methods) + self.assertIn(("POST", "/hf_download"), methods) + self.assertIn(("GET", "/hf_hub"), methods) + self.assertIn(("GET", "/hf_cache"), methods) + self.assertIn(("POST", "/hf_token"), methods) + self.assertIn(("GET", "/model_artifact_catalog"), methods) + self.assertNotIn(("GET", "/stop"), methods) + self.assertNotIn(("GET", "/hf_download"), methods) + self.assertFalse(any(path.startswith("/inference/") for _method, path in methods)) + + def test_hidden_path_supports_dotfiles_and_windows_attributes(self): + dotfile = SimpleNamespace(name=".token", stat=lambda: (_ for _ in ()).throw(AssertionError("unused"))) + windows_hidden = SimpleNamespace(name="token", stat=lambda: SimpleNamespace(st_file_attributes=0x2)) + visible = SimpleNamespace(name="token", stat=lambda: SimpleNamespace(st_file_attributes=0)) + + self.assertTrue(is_hidden_path(dotfile)) + with patch("modiff.server.stat.FILE_ATTRIBUTE_HIDDEN", 0x2, create=True): + self.assertTrue(is_hidden_path(windows_hidden)) + self.assertFalse(is_hidden_path(visible)) + + def test_mutation_origin_guard_is_registered_centrally(self): + self.assertIn(self.server._mutation_origin_middleware, self.server.app.middlewares) + + def test_template_gallery_route_is_optional_for_remote_asset_builds(self): + missing_gallery = Path(self.temporary.name) / "no-local-gallery" + with patch("modiff.server.TEMPLATE_GALLERY_ROOT", missing_gallery): + remote_server = WebServer( + modules={}, + work_dir=self.temporary.name, + data_dir=self.temporary.name, + ) + + routes = {route.resource.canonical for route in remote_server.app.router.routes()} + self.assertNotIn("/template-gallery", routes) + self.assertIn("/assets", routes) + + def test_template_gallery_route_remains_available_for_offline_builds(self): + local_gallery = Path(self.temporary.name) / "template-gallery" + local_gallery.mkdir() + with patch("modiff.server.TEMPLATE_GALLERY_ROOT", local_gallery): + offline_server = WebServer( + modules={}, + work_dir=self.temporary.name, + data_dir=self.temporary.name, + ) + + routes = {route.resource.canonical for route in offline_server.app.router.routes()} + self.assertIn("/template-gallery", routes) + + async def test_all_methods_reject_hostile_dns_host_and_origin(self): + called = False + + async def handler(_request): + nonlocal called + called = True + return web.json_response({"ok": True}) + + for method in ("GET", "POST", "PUT", "PATCH", "DELETE"): + with self.subTest(method=method): + response = await self.server._mutation_origin_middleware( + SimpleNamespace( + method=method, + headers={"Origin": "https://attacker.example"}, + host="attacker.example", + remote="127.0.0.1", + ), + handler, + ) + self.assertEqual(response.status, 403) + self.assertEqual(json.loads(response.text)["code"], "untrusted_request_boundary") + self.assertFalse(called) + + async def test_read_rejects_hostile_origin_even_with_loopback_destination(self): + called = False + + async def handler(_request): + nonlocal called + called = True + return web.json_response({"ok": True}) + + response = await self.server._mutation_origin_middleware( + SimpleNamespace( + method="GET", + headers={"Origin": "https://attacker.example"}, + host="127.0.0.1:8088", + remote="127.0.0.1", + ), + handler, + ) + + self.assertEqual(response.status, 403) + self.assertEqual(json.loads(response.text)["code"], "untrusted_request_boundary") + self.assertFalse(called) + + async def test_loopback_vite_origin_can_mutate_loopback_backend(self): + async def handler(_request): + return web.json_response({"ok": True}) + + response = await self.server._mutation_origin_middleware( + SimpleNamespace( + method="POST", + headers={"Origin": "http://localhost:5173"}, + host="127.0.0.1:8088", + remote="127.0.0.1", + ), + handler, + ) + + self.assertEqual(response.status, 200) + self.assertTrue(json.loads(response.text)["ok"]) + + async def test_originless_native_mutation_requires_loopback_host_and_peer(self): + async def handler(_request): + return web.json_response({"ok": True}) + + local = await self.server._mutation_origin_middleware( + SimpleNamespace( + method="DELETE", + headers={}, + host="localhost:8088", + remote="::1", + ), + handler, + ) + remote = await self.server._mutation_origin_middleware( + SimpleNamespace( + method="DELETE", + headers={}, + host="127.0.0.1:8088", + remote="192.0.2.10", + ), + handler, + ) + + self.assertEqual(local.status, 200) + self.assertEqual(remote.status, 403) + + async def test_hostile_origin_is_rejected_even_with_loopback_request_host(self): + async def handler(_request): + return web.json_response({"ok": True}) + + response = await self.server._mutation_origin_middleware( + SimpleNamespace( + method="POST", + headers={"Origin": "https://attacker.example"}, + host="127.0.0.1:8088", + remote="127.0.0.1", + ), + handler, + ) + + self.assertEqual(response.status, 403) + + async def test_registered_middleware_guards_real_read_and_mutation_routes(self): + client = TestClient(TestServer(self.server.app)) + await client.start_server() + try: + hostile = await client.post( + "/stop", + headers={ + "Host": "attacker.example", + "Origin": "https://attacker.example", + }, + ) + hostile_payload = await hostile.json() + hostile_read = await client.get( + "/queue", + headers={"Host": "attacker.example"}, + ) + hostile_read_payload = await hostile_read.json() + local = await client.post( + "/stop", + headers={"Origin": "http://localhost:5173"}, + ) + finally: + await client.close() + + self.assertEqual(hostile.status, 403) + self.assertEqual(hostile_payload["code"], "untrusted_request_boundary") + self.assertEqual(hostile_read.status, 403) + self.assertEqual(hostile_read_payload["code"], "untrusted_request_boundary") + self.assertEqual(local.status, 200) + + async def test_websocket_rejects_hostile_browser_origin_before_upgrade(self): + with patch("modiff.server.web.WebSocketResponse") as websocket_factory: + response = await self.server.websocket(WebSocketRequest(origin="https://attacker.example")) + + self.assertEqual(response.status, 403) + websocket_factory.assert_not_called() + + async def test_websocket_allows_loopback_origin_and_compacts_welcome_history(self): + self.server.recent_tasks = [ + { + "task_id": "completed-task", + "status": "completed", + "workflow_snapshot": {"nodes": [{"prompt": "private prompt"}]}, + } + ] + websocket = EmptyWebSocket() + + with patch("modiff.server.web.WebSocketResponse", return_value=websocket): + response = await self.server.websocket(WebSocketRequest(origin="http://localhost:5173")) + + self.assertIs(response, websocket) + self.assertTrue(websocket.prepared) + self.assertEqual(websocket.messages[0]["type"], "welcome") + self.assertNotIn("workflow_snapshot", websocket.messages[0]["recent"][0]) + self.assertTrue(websocket.messages[0]["recent"][0]["has_workflow_snapshot"]) + + async def test_websocket_without_origin_requires_a_loopback_native_client(self): + local_websocket = EmptyWebSocket() + with patch("modiff.server.web.WebSocketResponse", return_value=local_websocket): + local_response = await self.server.websocket(WebSocketRequest(origin=None)) + + self.assertIs(local_response, local_websocket) + self.assertTrue(local_websocket.prepared) + + with patch("modiff.server.web.WebSocketResponse") as websocket_factory: + remote_response = await self.server.websocket(WebSocketRequest(origin=None, remote="192.0.2.10")) + + self.assertEqual(remote_response.status, 403) + websocket_factory.assert_not_called() + + async def test_public_workflow_share_does_not_expose_backend_paths(self): + encoded = base64.b64encode(b"small-preview").decode("ascii") + response = await self.server.workflow_share_post( + JsonRequest( + { + "share_id": "safe-share", + "metadata": {"preview": f"data:image/png;base64,{encoded}"}, + "manifest": {"media": {}}, + "latestOutput": {}, + } + ) + ) + payload = json.loads(response.text) + serialized = json.dumps(payload) + + self.assertEqual(response.status, 200) + self.assertNotIn(self.temporary.name, serialized) + self.assertNotIn("backendShareMediaPath", serialized) + self.assertNotIn('"path"', serialized) + self.assertEqual(payload["persistedMedia"]["filename"], "preview.png") + + share_path = Path(self.temporary.name) / "studio" / "shares" / "safe-share.json" + self.assertNotIn(self.temporary.name, share_path.read_text(encoding="utf-8")) + + def test_legacy_workflow_share_paths_are_sanitized_on_read(self): + public = self.server._public_workflow_share( + { + "persistedMedia": {"path": "/private/share.png", "url": "/media/share.png"}, + "package": { + "manifest": {"media": {"backendShareMediaPath": "/private/share.png"}}, + "latestOutput": {"backendShareMediaPath": "/private/share.png"}, + }, + } + ) + self.assertNotIn("/private", json.dumps(public)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_studio_blocks.py b/tests/test_studio_blocks.py index a145eed..bbcc463 100644 --- a/tests/test_studio_blocks.py +++ b/tests/test_studio_blocks.py @@ -89,6 +89,33 @@ async def test_invalid_block_is_rejected(self): self.assertEqual(response.status, 400) self.assertTrue(response_json(response)["error"]) + async def test_nested_block_definition_is_rejected(self): + server = await self.make_server() + response = await server.studio_blocks_post( + FakeRequest( + { + "id": "nested", + "name": "Nested", + "version": 1, + "nodes": [ + { + "id": "child-block", + "type": "block", + "data": {"type": "block", "params": {}, "userBlockId": "existing"}, + "position": {"x": 0, "y": 0}, + } + ], + "edges": [], + "inputs": [], + "outputs": [], + "exposedParams": [], + } + ) + ) + + self.assertEqual(response.status, 400) + self.assertIn("Nested user blocks", response_json(response)["message"]) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_supervisor_control.py b/tests/test_supervisor_control.py new file mode 100644 index 0000000..3467a32 --- /dev/null +++ b/tests/test_supervisor_control.py @@ -0,0 +1,189 @@ +import json +import tempfile +import unittest +from http.client import HTTPConnection +from pathlib import Path +from unittest.mock import Mock + +from modiff.supervisor_control import SupervisorControlServer, SupervisorController, _allowed_browser_origin + + +class SupervisorControlTests(unittest.TestCase): + def test_browser_origin_policy_allows_only_local_origins(self): + self.assertTrue(_allowed_browser_origin("http://localhost:5173")) + self.assertTrue(_allowed_browser_origin("http://127.0.0.1:8088")) + self.assertFalse(_allowed_browser_origin("https://attacker.example")) + self.assertFalse(_allowed_browser_origin("null")) + + def test_control_server_rejects_get_stop_and_cross_site_post(self): + with tempfile.TemporaryDirectory() as directory: + controller = SupervisorController(Path(directory) / "supervisor-queue.json") + worker = Mock(pid=4242) + worker.poll.return_value = None + controller.set_worker(worker) + server = SupervisorControlServer(controller, "127.0.0.1", 0) + server.start() + port = server.server.server_address[1] + try: + connection = HTTPConnection("127.0.0.1", port, timeout=2) + connection.request("GET", "/stop") + self.assertEqual(connection.getresponse().status, 404) + connection.close() + + connection = HTTPConnection("127.0.0.1", port, timeout=2) + connection.request("POST", "/stop", headers={"Origin": "https://attacker.example"}) + response = connection.getresponse() + self.assertEqual(response.status, 403) + self.assertIsNone(response.getheader("Access-Control-Allow-Origin")) + connection.close() + + connection = HTTPConnection("127.0.0.1", port, timeout=2) + connection.request("GET", "/queue", headers={"Host": "attacker.example"}) + response = connection.getresponse() + self.assertEqual(response.status, 403) + self.assertIsNone(response.getheader("Access-Control-Allow-Origin")) + connection.close() + finally: + server.close() + worker.kill.assert_not_called() + + def test_stop_kills_worker_and_cancels_current_and_queued_runs(self): + with tempfile.TemporaryDirectory() as directory: + state_path = Path(directory) / "supervisor-queue.json" + state_path.write_text( + json.dumps( + { + "workerPid": 4242, + "current": {"task_id": "active", "name": "Graph execution"}, + "queued": {"next": {"task_id": "next", "name": "Graph execution"}}, + "recent": [ + { + "task_id": "prior", + "status": "completed", + "workflow_snapshot": {"nodes": [{"id": "recoverable"}], "edges": []}, + } + ], + } + ), + encoding="utf-8", + ) + worker = Mock(pid=4242) + worker.poll.return_value = None + controller = SupervisorController(state_path) + controller.set_worker(worker) + + status, payload = controller.stop() + + self.assertEqual(status, 200) + self.assertFalse(payload["error"]) + self.assertEqual(payload["task_id"], "active") + self.assertEqual(payload["cancelled_queued_task_ids"], ["next"]) + self.assertTrue(payload["backend_restart"]) + worker.kill.assert_called_once_with() + persisted = json.loads(state_path.read_text(encoding="utf-8")) + self.assertIsNone(persisted["current"]) + self.assertEqual(persisted["queued"], {}) + self.assertEqual( + [task["task_id"] for task in persisted["recent"][:2]], + ["active", "next"], + ) + self.assertTrue(all(task["status"] == "cancelled" for task in persisted["recent"][:2])) + self.assertEqual( + persisted["recent"][2]["workflow_snapshot"]["nodes"][0]["id"], + "recoverable", + ) + + def test_queue_rejects_state_from_a_replaced_worker(self): + with tempfile.TemporaryDirectory() as directory: + state_path = Path(directory) / "supervisor-queue.json" + state_path.write_text( + json.dumps( + { + "workerPid": 1111, + "current": {"task_id": "stale"}, + "queued": {}, + "recent": [{"task_id": "done", "status": "completed"}], + } + ), + encoding="utf-8", + ) + worker = Mock(pid=2222) + worker.poll.return_value = None + controller = SupervisorController(state_path) + controller.set_worker(worker) + + queue = controller.queue() + + self.assertIsNone(queue["current"]) + self.assertEqual(queue["queued"], {}) + self.assertEqual(queue["recent"][0]["task_id"], "done") + + def test_queue_compacts_completed_workflow_snapshots_for_polling(self): + with tempfile.TemporaryDirectory() as directory: + state_path = Path(directory) / "supervisor-queue.json" + state_path.write_text( + json.dumps( + { + "workerPid": 4242, + "current": None, + "queued": {}, + "recent": [ + { + "task_id": "done", + "status": "completed", + "runtimeFingerprint": { + "fingerprint": "sha256:execution", + "resourceFingerprint": "sha256:resource", + "hardware": {"large": "payload"}, + }, + "workflow_snapshot": {"nodes": [{"id": "large"}], "edges": []}, + } + ], + } + ), + encoding="utf-8", + ) + worker = Mock(pid=4242) + worker.poll.return_value = None + controller = SupervisorController(state_path) + controller.set_worker(worker) + + task = controller.queue()["recent"][0] + + self.assertNotIn("workflow_snapshot", task) + self.assertTrue(task["has_workflow_snapshot"]) + self.assertEqual(task["runtimeFingerprint"], "sha256:resource") + + def test_stop_restarts_worker_when_its_snapshot_was_replaced_or_corrupted(self): + with tempfile.TemporaryDirectory() as directory: + state_path = Path(directory) / "supervisor-queue.json" + state_path.write_text( + json.dumps( + { + "workerPid": 1111, + "current": None, + "queued": {}, + "recent": [{"task_id": "done", "status": "completed"}], + } + ), + encoding="utf-8", + ) + worker = Mock(pid=2222) + worker.poll.return_value = None + controller = SupervisorController(state_path) + controller.set_worker(worker) + + status, payload = controller.stop() + + self.assertEqual(status, 200) + self.assertTrue(payload["snapshot_recovery"]) + self.assertTrue(payload["backend_restart"]) + worker.kill.assert_called_once_with() + persisted = json.loads(state_path.read_text(encoding="utf-8")) + self.assertEqual(persisted["workerPid"], 2222) + self.assertIsNone(persisted["current"]) + self.assertEqual(persisted["recent"][0]["task_id"], "done") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_torch_utils.py b/tests/test_torch_utils.py index c515a9e..11a61ab 100644 --- a/tests/test_torch_utils.py +++ b/tests/test_torch_utils.py @@ -117,6 +117,10 @@ def test_legacy_device_order_schema_and_cpu_retention(self): module.DEVICE_LIST["cuda:0"], { "arch": "cuda", + "backend": "cuda", + "vendor": None, + "architecture": None, + "memory_kind": "dedicated", "name": "First GPU 16.00GB (0)", "label": ["cuda:0"], "total_memory": 16 * GIB, @@ -125,7 +129,17 @@ def test_legacy_device_order_schema_and_cpu_retention(self): ) self.assertEqual( set(module.DEVICE_LIST["mps:0"]), - {"arch", "name", "label", "total_memory", "index"}, + { + "arch", + "backend", + "vendor", + "architecture", + "memory_kind", + "name", + "label", + "total_memory", + "index", + }, ) self.assertEqual( module.DEVICE_LIST["cpu:0"], diff --git a/tests/test_video_composition.py b/tests/test_video_composition.py new file mode 100644 index 0000000..6039b26 --- /dev/null +++ b/tests/test_video_composition.py @@ -0,0 +1,59 @@ +import numpy as np +from PIL import Image + +from modules.Video.main import Compose, ExportWithAudio, LyricOverlay +from modules import MODULE_MAP + + +def solid(color, count=16, size=(160, 90)): + return [Image.new("RGB", size, color) for _ in range(count)] + + +def test_compose_crossfades_model_agnostic_frame_lists(): + result = Compose("compose-test").execute( + clip_1=solid("red"), + clip_2=solid("blue"), + transition_seconds=0.25, + fps=16, + ) + assert result["frames"] == 28 + assert result["duration_seconds"] == 1.75 + assert result["video"][0].getpixel((0, 0)) == (255, 0, 0) + assert result["video"][-1].getpixel((0, 0)) == (0, 0, 255) + + +def test_compose_inputs_are_visible_to_static_node_registry(): + params = MODULE_MAP["modules.Video"]["Compose"]["params"] + assert params["clip_1"]["display"] == "input" + assert "video_collection" in params["clip_1"]["type"] + + +def test_lyric_overlay_requires_and_renders_lrc_timeline(): + result = LyricOverlay("lyrics-test").execute( + video=solid("black", count=20), + lrc="[00:00.00]First line\n[00:00.50]Second line", + fps=10, + font_size=18, + bottom_margin=12, + ) + assert len(result["output"]) == 20 + assert np.asarray(result["output"][0]).max() > 0 + assert np.asarray(result["output"][10]).max() > 0 + + +def test_export_with_audio_muxes_mp4(tmp_path): + samples = np.zeros((48000, 2), dtype=np.float32) + samples[:, 0] = 0.1 * np.sin(np.linspace(0, 440 * 2 * np.pi, 48000)) + samples[:, 1] = samples[:, 0] + output = tmp_path / "lyric.mp4" + result = ExportWithAudio("export-av-test").execute( + video=solid("navy", count=16), + audio={"samples": samples, "sample_rate": 48000}, + filename=str(output), + fps=16, + quality=5, + ) + assert output.exists() + assert output.stat().st_size > 0 + assert result["frames"] == 16 + assert result["duration_seconds"] == 1 diff --git a/tests/test_video_conditioning.py b/tests/test_video_conditioning.py new file mode 100644 index 0000000..1fe8569 --- /dev/null +++ b/tests/test_video_conditioning.py @@ -0,0 +1,44 @@ +import unittest + +from PIL import Image + +from modules.VideoConditioning.main import AlignMask, ReferenceImages + + +class AlignVideoMaskTests(unittest.TestCase): + def test_grow_pixels_expands_white_generated_region(self): + source = Image.new("RGB", (9, 9), (10, 20, 30)) + mask = Image.new("L", (9, 9), 0) + mask.putpixel((4, 4), 255) + + result = AlignMask().execute(video=[source], mask=[mask], threshold=127, grow_pixels=2)["output"][0] + + self.assertEqual(result.getpixel((2, 2)), (255, 255, 255)) + self.assertEqual(result.getpixel((6, 6)), (255, 255, 255)) + self.assertEqual(result.getpixel((1, 1)), (0, 0, 0)) + + def test_default_does_not_expand_mask(self): + source = Image.new("RGB", (5, 5), 0) + mask = Image.new("L", (5, 5), 0) + mask.putpixel((2, 2), 255) + + result = AlignMask().execute(video=[source], mask=[mask], threshold=127)["output"][0] + + self.assertEqual(result.getpixel((2, 2)), (255, 255, 255)) + self.assertEqual(result.getpixel((1, 2)), (0, 0, 0)) + + +class VideoReferenceImagesTests(unittest.TestCase): + def test_packages_a_single_image_without_model_specific_translation(self): + image = Image.new("RGB", (8, 8), (10, 20, 30)) + + self.assertEqual(ReferenceImages().execute(images=image)["references"], [image]) + + def test_preserves_an_ordered_reference_list(self): + images = [Image.new("RGB", (8, 8), color) for color in ((10, 20, 30), (40, 50, 60))] + + self.assertEqual(ReferenceImages().execute(images=images)["references"], images) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_video_conditioning_preprocessors.py b/tests/test_video_conditioning_preprocessors.py new file mode 100644 index 0000000..cc3d0d8 --- /dev/null +++ b/tests/test_video_conditioning_preprocessors.py @@ -0,0 +1,36 @@ +import unittest + +import numpy as np +from PIL import Image, ImageDraw + +from modules.VideoConditioning import main as video_conditioning +from modules.VideoConditioning.main import EdgePreprocessor, ObjectMaskPropagate + + +class VideoConditioningPreprocessorTests(unittest.TestCase): + def test_video_conditioning_has_no_independent_model_preprocessors(self): + self.assertFalse(hasattr(video_conditioning, "DepthPreprocessor")) + self.assertFalse(hasattr(video_conditioning, "PosePreprocessor")) + + def test_edge_preprocessor_preserves_shape_and_frame_count(self): + source = Image.new("RGB", (16, 12), "black") + ImageDraw.Draw(source).rectangle((4, 3, 11, 8), fill="white") + output = EdgePreprocessor().execute(video=[source, source], algorithm="canny", low_threshold=50, high_threshold=100)["output"] + + self.assertEqual(len(output), 2) + self.assertEqual(output[0].size, source.size) + self.assertGreater(np.asarray(output[0]).max(), 0) + + def test_static_video_keeps_mask_and_reports_high_confidence(self): + frame = Image.new("RGB", (16, 12), "gray") + mask = Image.new("L", frame.size, 0) + ImageDraw.Draw(mask).rectangle((4, 3, 10, 8), fill=255) + result = ObjectMaskPropagate().execute(video=[frame, frame, frame], first_mask=mask, threshold=127, smooth_pixels=0) + + self.assertEqual(len(result["masks"]), 3) + self.assertTrue(all(score > 0.99 for score in result["confidence"])) + self.assertEqual(np.asarray(result["masks"][0]).sum(), np.asarray(result["masks"][-1]).sum()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_video_operations.py b/tests/test_video_operations.py new file mode 100644 index 0000000..4f8d23c --- /dev/null +++ b/tests/test_video_operations.py @@ -0,0 +1,228 @@ +import unittest +import tempfile +from pathlib import Path +from unittest.mock import patch + +from PIL import Image + +from modules.Video.main import ( + Concatenate, + Crossfade, + ConcatenateAssets, + FirstLastSegmentBuilder, + FrameExtract, + KeyframeChain, + MaskedComposite, + MuxAudioAsset, + Reverse, + StackTile, + TemporalCleanPlate, + ExtendCleanPlate, + Trim, + TrimAsset, +) + + +def solid(color, count=1, size=(8, 6)): + return [Image.new("RGB", size, color) for _ in range(count)] + + +class VideoOperationTests(unittest.TestCase): + def test_file_native_trim_and_join_delegate_to_ffmpeg_without_decoding_frames(self): + with tempfile.TemporaryDirectory() as directory: + first_path = Path(directory) / "first.mp4" + second_path = Path(directory) / "second.mp4" + first_path.write_bytes(b"first") + second_path.write_bytes(b"second") + first = { + "asset_id": "first", + "path": str(first_path), + "width": 16, + "height": 12, + "fps": 8, + "frame_count": 16, + "duration_seconds": 2, + } + second = {**first, "asset_id": "second", "path": str(second_path)} + third_path = Path(directory) / "third.mp4" + third_path.write_bytes(b"third") + third = {**first, "asset_id": "third", "path": str(third_path)} + commands = [] + + def capture(arguments, destination): + commands.append(arguments) + return destination + + result = {"asset": {}, "file": "result.mp4", "duration_seconds": 3.5, "frames": 28} + with ( + patch("modiff.media_assets.run_ffmpeg", side_effect=capture), + patch("modules.Video.main._derived_asset_result", return_value=result), + ): + self.assertIs(TrimAsset().execute(video=first, start_seconds=0.5, end_seconds=1.5), result) + self.assertIs( + ConcatenateAssets().execute(clips=[first, second, third], transition_seconds=0.5), + result, + ) + + self.assertIn("-ss", commands[0]) + self.assertIn("-filter_complex", commands[1]) + filter_graph = commands[1][commands[1].index("-filter_complex") + 1] + self.assertEqual(filter_graph.count("xfade=transition=fade"), 2) + self.assertIn("settb=expr=1/8", filter_graph) + self.assertIn("[raw_x1]settb=expr=1/8,setpts=PTS-STARTPTS,fps=8[x1]", filter_graph) + self.assertEqual(commands[1][commands[1].index("-r") + 1], "8.0") + + def test_file_native_audio_mux_preserves_video_stream_and_matches_duration(self): + with tempfile.TemporaryDirectory() as directory: + video_path = Path(directory) / "video.mp4" + audio_path = Path(directory) / "audio.wav" + video_path.write_bytes(b"video") + audio_path.write_bytes(b"audio") + source = { + "asset_id": "video", + "path": str(video_path), + "width": 16, + "height": 12, + "fps": 8, + "frame_count": 16, + "duration_seconds": 2, + } + commands = [] + result = {"asset": {}, "file": "muxed.mp4", "duration_seconds": 2, "frames": 16} + with ( + patch("modiff.media_assets.run_ffmpeg", side_effect=lambda args, output: commands.append(args)), + patch("modules.Video.main._derived_asset_result", return_value=result), + ): + self.assertIs(MuxAudioAsset().execute(video=source, audio=str(audio_path), fit="match_video"), result) + + self.assertIn("copy", commands[0]) + self.assertIn("apad", commands[0]) + self.assertIn("2", commands[0]) + + def test_frame_extract_supports_negative_indices_and_timecodes(self): + clip = solid("red", 5) + indexed = FrameExtract().execute(video=clip, mode="indices", indices="0, 2, -1", fps=2) + timed = FrameExtract().execute(video=clip, mode="timecodes", timecodes="0.5, 1.5", fps=2) + + self.assertEqual(indexed["selected_indices"], [0, 2, 4]) + self.assertEqual(indexed["timestamps"], [0, 1, 2]) + self.assertEqual(timed["selected_indices"], [1, 3]) + + def test_frame_extract_reads_only_requested_boundaries_from_a_file_asset(self): + import imageio.v2 as imageio + import numpy as np + + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "boundaries.mp4" + writer = imageio.get_writer(path, fps=2, codec="libx264", macro_block_size=1) + try: + for value in (0, 80, 160): + writer.append_data(np.full((16, 16, 3), value, dtype=np.uint8)) + finally: + writer.close() + + result = FrameExtract().execute(video=str(path), mode="first_last") + + self.assertEqual(result["selected_indices"], [0, 2]) + self.assertEqual(len(result["frames"]), 2) + + def test_trim_concatenate_reverse_and_crossfade_have_exact_frame_counts(self): + red = solid("red", 4) + blue = solid("blue", 4) + trimmed = Trim().execute(video=red, range_mode="frames", start=1, end=3, fps=2)["output"] + joined = Concatenate().execute(clips=[trimmed, blue], transition_seconds=0.5, fps=2) + faded = Crossfade().execute(first=red, second=blue, duration_seconds=1, fps=2) + + self.assertEqual(len(trimmed), 2) + self.assertEqual(joined["frames"], 5) + self.assertEqual(faded["frames"], 6) + self.assertEqual(Reverse().execute(video=trimmed)["output"][0].getpixel((0, 0)), (255, 0, 0)) + + def test_video_tile_holds_short_clips_and_builds_requested_grid(self): + result = StackTile().execute( + videos=[solid("red", 1), solid("blue", 3)], + columns=2, + sync="longest_hold", + gap=2, + background="black", + ) + + self.assertEqual(result["frames"], 3) + self.assertEqual(result["output"][0].size, (18, 6)) + self.assertEqual(result["output"][-1].getpixel((10, 0)), (0, 0, 255)) + + def test_masked_composite_preserves_source_outside_white_region(self): + source = solid("red", 2, size=(4, 2)) + generated = solid("blue", 2, size=(4, 2)) + mask = Image.new("L", (4, 2), 0) + for x in (2, 3): + for y in (0, 1): + mask.putpixel((x, y), 255) + + result = MaskedComposite().execute(source=source, generated=generated, mask=mask) + + self.assertEqual(result["frames"], 2) + self.assertEqual(result["output"][0].getpixel((0, 0)), (255, 0, 0)) + self.assertEqual(result["output"][0].getpixel((3, 1)), (0, 0, 255)) + + def test_masked_composite_rejects_frame_count_mismatch(self): + with self.assertRaisesRegex(ValueError, "frame mismatch"): + MaskedComposite().execute( + source=solid("red", 2), + generated=solid("blue", 1), + mask=Image.new("L", (8, 6), 255), + ) + + def test_temporal_clean_plate_preserves_endpoints_and_frame_count(self): + result = TemporalCleanPlate().execute( + video=[ + Image.new("RGB", (2, 2), (0, 0, 0)), + Image.new("RGB", (2, 2), (255, 0, 0)), + Image.new("RGB", (2, 2), (255, 255, 255)), + ], + start_index=0, + end_index=-1, + easing="linear", + ) + + self.assertEqual(result["frames"], 3) + self.assertEqual(result["output"][0].getpixel((0, 0)), (0, 0, 0)) + # PIL blends 8-bit channel values with floor rounding. + self.assertEqual(result["output"][1].getpixel((0, 0)), (127, 127, 127)) + self.assertEqual(result["output"][2].getpixel((0, 0)), (255, 255, 255)) + + def test_extend_clean_plate_mirrors_clean_strip_to_the_left(self): + frame = Image.new("RGB", (6, 2), "black") + frame.putpixel((3, 0), (10, 0, 0)) + frame.putpixel((4, 0), (20, 0, 0)) + + result = ExtendCleanPlate().execute( + video=[frame], boundary_x=3, extend_left=2, mode="mirror", top=0, bottom=1 + ) + + self.assertEqual(result["frames"], 1) + self.assertEqual(result["output"][0].getpixel((1, 0)), (20, 0, 0)) + self.assertEqual(result["output"][0].getpixel((2, 0)), (10, 0, 0)) + self.assertEqual(result["output"][0].getpixel((3, 0)), (10, 0, 0)) + + def test_keyframe_jobs_pair_neighbors_and_chain_loop_results(self): + keyframes = [solid("red")[0], solid("green")[0], solid("blue")[0]] + jobs = FirstLastSegmentBuilder().execute( + keyframes=keyframes, + prompts='["move right", "move closer"]', + settings='{"steps": 20}', + ) + chain = KeyframeChain().execute( + clips=[solid("red", 3), solid("blue", 3)], + boundary="drop_duplicate", + fps=2, + ) + + self.assertEqual(jobs["count"], 2) + self.assertIs(jobs["jobs"][0]["last_frame"], keyframes[1]) + self.assertEqual(jobs["jobs"][1]["prompt"], "move closer") + self.assertEqual(chain["frames"], 5) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_wan_vace.py b/tests/test_wan_vace.py index 337fb5a..d10b877 100644 --- a/tests/test_wan_vace.py +++ b/tests/test_wan_vace.py @@ -1,10 +1,16 @@ import unittest from unittest.mock import Mock, patch +import numpy as np import torch +from PIL import Image from modiff.config import CONFIG -from modules.WanVACE.main import LoadPipeline, WAN_VACE_DEFAULT_REPO +from modules.DiffusersVideo.main import Generate, LoadPipeline +from modules.DiffusersVideo.wan_vace import ( + WAN_VACE_DEFAULT_REPO, + _neutralize_masked_region, +) class WanVaceLoaderTests(unittest.TestCase): @@ -20,10 +26,10 @@ def test_loader_uses_the_app_configured_hugging_face_cache(self): with ( patch.dict(CONFIG.hf, {"cache_dir": "E:/MoDiff/huggingface/hub", "online_status": "Auto"}), - patch("modules.WanVACE.main.local_files_only", return_value=True), + patch("modules.DiffusersVideo.wan_vace.local_files_only", return_value=True), patch("diffusers.AutoencoderKLWan.from_pretrained", return_value=vae) as vae_from_pretrained, patch("diffusers.WanVACEPipeline.from_pretrained", return_value=pipeline) as from_pretrained, - patch("modules.WanVACE.main.apply_pipeline_offload"), + patch("modules.DiffusersVideo.wan_vace.apply_pipeline_offload"), ): result = node.execute( model_id={"source": "hub", "value": WAN_VACE_DEFAULT_REPO}, @@ -55,5 +61,72 @@ def test_loader_uses_the_app_configured_hugging_face_cache(self): self.assertTrue(load_kwargs["low_cpu_mem_usage"]) +class WanVaceLongVideoTests(unittest.TestCase): + def test_long_masked_video_uses_native_overlapping_segments_and_generated_anchor(self): + class Output: + def __init__(self, frames): + self.frames = [frames] + + class Pipeline: + _execution_device = "cpu" + vae_scale_factor_temporal = 4 + vae_scale_factor_spatial = 8 + + def __init__(self): + self.calls = [] + + def __call__(self, **kwargs): + call_index = len(self.calls) + self.calls.append(kwargs) + return Output([f"generated-{call_index}-{index}" for index in range(kwargs["num_frames"])]) + + pipeline = Pipeline() + video = [np.full((4, 4, 3), index % 255, dtype=np.uint8) for index in range(161)] + mask = [np.full((4, 4), 255, dtype=np.uint8) for _ in range(161)] + output = Generate().execute( + pipeline=pipeline, + video=video, + mask=mask, + prompt="Replace the masked vessel with one stable amber vessel.", + width=832, + height=480, + num_frames=161, + num_inference_steps=4, + seed=17, + ) + + self.assertEqual(len(pipeline.calls), 2) + self.assertEqual([call["num_frames"] for call in pipeline.calls], [81, 81]) + self.assertEqual(pipeline.calls[0]["generator"].initial_seed(), 17) + self.assertEqual(pipeline.calls[1]["generator"].initial_seed(), 17) + self.assertTrue(np.all(pipeline.calls[0]["video"][0] == 127)) + self.assertEqual(pipeline.calls[1]["video"][0], "generated-0-80") + self.assertTrue(np.all(pipeline.calls[1]["video"][1] == 127)) + self.assertTrue(np.all(pipeline.calls[1]["mask"][0] == 0)) + self.assertTrue(np.all(pipeline.calls[1]["mask"][1] == 255)) + self.assertEqual(output["frames_out"], 161) + self.assertEqual(output["video_out"][80], "generated-0-80") + self.assertEqual(output["video_out"][81], "generated-1-1") + + def test_mask_neutralization_preserves_black_regions(self): + frame = np.full((2, 2, 3), 23, dtype=np.uint8) + mask = np.array([[0, 255], [0, 255]], dtype=np.uint8) + + result = _neutralize_masked_region(frame, mask) + + self.assertTrue(np.all(result[:, 0] == 23)) + self.assertTrue(np.all(result[:, 1] == 127)) + + def test_rgb_mask_neutralization_uses_gray_not_packed_red(self): + frame = Image.new("RGB", (2, 1), (23, 41, 59)) + mask = Image.new("L", (2, 1), 0) + mask.putpixel((1, 0), 255) + + result = _neutralize_masked_region(frame, mask) + + self.assertEqual(result.getpixel((0, 0)), (23, 41, 59)) + self.assertEqual(result.getpixel((1, 0)), (127, 127, 127)) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_workflow_control_nodes.py b/tests/test_workflow_control_nodes.py new file mode 100644 index 0000000..3ae6b06 --- /dev/null +++ b/tests/test_workflow_control_nodes.py @@ -0,0 +1,131 @@ +import unittest + +from PIL import Image + +from modules.Image.main import ImageGrid, SplitImageGrid +from modules.WorkflowControl.main import ( + AuthorShotList, + CollectionBatch, + CollectionFlatten, + CollectionItem, + FanOut, + GetField, + LoopItems, + LoopResult, + ParameterMatrix, + ParameterPreset, + SeedSequence, + parse_shot_list, +) + + +class WorkflowControlNodeTests(unittest.TestCase): + def test_visual_loop_node_schemas_match_the_published_client_contract(self): + self.assertEqual( + list(LoopItems.params), + ["collection", "item_index", "item", "index", "count"], + ) + self.assertEqual( + list(LoopResult.params), + ["value_input", "stop_input", "value", "collection", "stopped"], + ) + + def test_authored_shot_lists_are_validated_and_loop_ready(self): + result = AuthorShotList().execute( + shots_json='[{"title":"Open","prompt":"A cyclist enters frame as the camera tracks left.","duration_seconds":4.5},' + '{"prompt":"The same cyclist stops beside the lake.","duration_seconds":5.5,"transition":"match cut"}]', + maximum_shots=4, + ) + + self.assertEqual(len(result["shots"]), 2) + self.assertEqual(result["shots"][1]["index"], 1) + self.assertEqual(result["total_duration_seconds"], 10) + + def test_authored_shot_list_accepts_json_fenced_by_surrounding_text(self): + decoded = 'Plan:\n```json\n{"shots":[{"prompt":"One continuous action.","duration_seconds":6}]}\n```' + + result = parse_shot_list(decoded, maximum=2) + + self.assertEqual(result["shots"][0]["duration_seconds"], 6) + + def test_seed_sequence_and_parameter_matrix_are_bounded_and_deterministic(self): + seeds = SeedSequence().execute(start=10, count=4, step=3) + matrix = ParameterMatrix().execute( + parameters='{"steps": [20, 30], "guidance": [3.5, 5.0]}', + max_combinations=4, + ) + + self.assertEqual(seeds["seeds"], [10, 13, 16, 19]) + self.assertEqual(matrix["count"], 4) + self.assertEqual(matrix["combinations"][0], {"steps": 20, "guidance": 3.5}) + with self.assertRaisesRegex(ValueError, "above the configured maximum"): + ParameterMatrix().execute(parameters='{"a": [1, 2], "b": [3, 4]}', max_combinations=3) + + def test_collection_batch_and_flatten_round_trip(self): + batches = CollectionBatch().execute(collection=[1, 2, 3, 4, 5], batch_size=2)["batches"] + flattened = CollectionFlatten().execute(collection=batches)["flattened"] + + self.assertEqual(batches, [[1, 2], [3, 4], [5]]) + self.assertEqual(flattened, [1, 2, 3, 4, 5]) + + def test_collection_item_has_explicit_out_of_range_policies(self): + node = CollectionItem() + + self.assertEqual(node.execute(collection=["opening", "ending"], index=1), {"item": "ending", "count": 2}) + self.assertEqual( + node.execute(collection=["opening", "ending"], index=9, out_of_range="use_last"), + {"item": "ending", "count": 2}, + ) + with self.assertRaisesRegex(IndexError, "outside a collection"): + node.execute(collection=["opening"], index=1) + + def test_parameter_zip_broadcasts_single_values_and_fanout_keeps_overrides(self): + matrix = ParameterMatrix().execute( + parameters='{"steps": [20, 30], "guidance": [4.5]}', + mode="zip", + max_combinations=2, + ) + branches = FanOut().execute( + value="source", + count=2, + branch_overrides='[{"angle": "front"}, {"angle": "side"}]', + )["branches"] + + self.assertEqual(matrix["combinations"], [{"steps": 20, "guidance": 4.5}, {"steps": 30, "guidance": 4.5}]) + self.assertEqual(branches[1], {"index": 1, "value": "source", "overrides": {"angle": "side"}}) + + def test_named_presets_and_dotted_record_fields_feed_loop_bodies(self): + preset = ParameterPreset().execute( + name="Low memory", values='{"steps": 20, "runtime": {"offload": "group_cpu"}}' + )["preset"] + field = GetField().execute(record=preset, field="values.runtime.offload") + missing = GetField().execute(record=preset, field="values.unknown", default_value="fallback") + + self.assertEqual(field, {"value": "group_cpu", "found": True}) + self.assertEqual(missing, {"value": "fallback", "found": False}) + + def test_image_grid_and_split_preserve_uniform_cell_dimensions(self): + red = Image.new("RGB", (20, 10), "red") + blue = Image.new("RGB", (10, 20), "blue") + grid_result = ImageGrid().execute( + images=[red, blue], + columns=2, + cell_width=24, + cell_height=24, + gap=4, + background="#000000", + fit="contain", + ) + cells = SplitImageGrid().execute( + image=grid_result["grid"], + rows=grid_result["rows"], + columns=grid_result["column_count"], + gap=4, + )["images"] + + self.assertEqual(grid_result["grid"].size, (52, 24)) + self.assertEqual([cell.size for cell in cells], [(24, 24), (24, 24)]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_workflow_loops.py b/tests/test_workflow_loops.py new file mode 100644 index 0000000..25b597b --- /dev/null +++ b/tests/test_workflow_loops.py @@ -0,0 +1,235 @@ +import unittest + +from modiff.server import WebServer +from modules import MODULE_MAP + + +def _param(value=None, *, source_id=None, source_key=None): + result = {"value": value} + if source_id: + result.update({"sourceId": source_id, "sourceKey": source_key}) + return result + + +class WorkflowLoopTests(unittest.TestCase): + def setUp(self): + self.server = WebServer.__new__(WebServer) + self.server.modules = MODULE_MAP + self.server.node_cache = {} + self.server.current_task = {"task_id": "loop-task", "attempt_index": 0} + self.server.interrupt_flag = False + self.messages = [] + self.server.queue_message = lambda message, _sid=None: self.messages.append(message) + + def _graph(self): + return { + "sid": "test", + "nodes": { + "input": { + "module": "modules.WorkflowControl", + "action": "LoopInput", + "params": {"initial": _param("initial")}, + }, + "index": { + "module": "modules.WorkflowControl", + "action": "LoopIndex", + "params": { + "index_value": _param(0), + "iteration_count": _param(1), + }, + }, + "result": { + "module": "modules.WorkflowControl", + "action": "LoopResult", + "params": { + "value_input": _param(source_id="index", source_key="index"), + "stop_input": _param(False), + }, + }, + }, + "paths": [["input", "index", "result"]], + "loops": [ + { + "id": "loop-container", + "bodyNodeIds": ["input", "index", "result"], + "iterations": 3, + "maxIterations": 10, + "inputNodeId": "input", + "indexNodeId": "index", + "resultNodeId": "result", + "carry": True, + "collect": True, + } + ], + } + + def test_loop_collects_each_iteration_and_keeps_last_value(self): + graph = self._graph() + prepared = self.server._prepare_graph_loops(graph) + result = self.server._execute_graph_loop(prepared["loops"][0], graph["nodes"], graph["sid"]) + + self.assertEqual(result["collection"], [0, 1, 2]) + self.assertEqual(self.server.node_cache["result"].output["value"], 2) + self.assertEqual(self.server.node_cache["result"].output["collection"], [0, 1, 2]) + loop_progress = [item for item in self.messages if item.get("node") == "loop-container"] + self.assertEqual(loop_progress[-1]["status"], "succeeded") + + def test_loop_retries_a_failed_iteration_without_losing_previous_results(self): + graph = self._graph() + graph["loops"][0]["maxRetries"] = 1 + prepared = self.server._prepare_graph_loops(graph) + original_execute = self.server.execute_node + failures = 0 + + def flaky_execute(node_id, *args, **kwargs): + nonlocal failures + if node_id == "result" and failures == 0: + failures += 1 + raise RuntimeError("transient") + return original_execute(node_id, *args, **kwargs) + + self.server.execute_node = flaky_execute + result = self.server._execute_graph_loop(prepared["loops"][0], graph["nodes"], graph["sid"]) + + self.assertEqual(result["collection"], [0, 1, 2]) + self.assertTrue(any("Retrying iteration" in item.get("message", "") for item in self.messages)) + + def test_loop_resumes_completed_iterations_after_graph_cache_clear(self): + graph = self._graph() + prepared = self.server._prepare_graph_loops(graph) + original_execute = self.server.execute_node + failed = False + + def interrupted_attempt(node_id, node, sid, **kwargs): + nonlocal failed + overrides = kwargs.get("param_overrides") or {} + if node_id == "index" and overrides.get("index_value") == 1 and not failed: + failed = True + raise RuntimeError("graph retry") + return original_execute(node_id, node, sid, **kwargs) + + self.server.execute_node = interrupted_attempt + with self.assertRaisesRegex(RuntimeError, "graph retry"): + self.server._execute_graph_loop(prepared["loops"][0], graph["nodes"], graph["sid"]) + + self.server.node_cache.clear() + result = self.server._execute_graph_loop(prepared["loops"][0], graph["nodes"], graph["sid"]) + + self.assertEqual(result["collection"], [0, 1, 2]) + self.assertTrue(any("Resuming after 1" in item.get("message", "") for item in self.messages)) + + def test_loop_rejects_unbounded_or_overlapping_body_contracts(self): + graph = self._graph() + graph["loops"][0]["iterations"] = 11 + with self.assertRaisesRegex(ValueError, "between 1"): + self.server._prepare_graph_loops(graph) + + graph = self._graph() + graph["loops"].append({**graph["loops"][0], "id": "other-loop"}) + with self.assertRaisesRegex(ValueError, "overlaps another loop"): + self.server._prepare_graph_loops(graph) + + def test_collection_mode_maps_over_loop_items(self): + graph = self._graph() + graph["nodes"]["items"] = { + "module": "modules.WorkflowControl", + "action": "LoopItems", + "params": { + "collection": _param(["first", "second", "third"]), + "item_index": _param(0), + }, + } + graph["nodes"]["result"]["params"]["value_input"] = _param(source_id="items", source_key="item") + graph["paths"] = [["input", "index", "items", "result"]] + loop = graph["loops"][0] + loop.update( + { + "bodyNodeIds": ["input", "index", "items", "result"], + "iterationMode": "collection", + "itemNodeId": "items", + } + ) + + prepared = self.server._prepare_graph_loops(graph) + result = self.server._execute_graph_loop(prepared["loops"][0], graph["nodes"], graph["sid"]) + + self.assertEqual(result["collection"], ["first", "second", "third"]) + + def test_only_loop_result_may_cross_the_container_boundary(self): + graph = self._graph() + graph["nodes"]["outside"] = { + "module": "modules.WorkflowControl", + "action": "LoopResult", + "params": { + "value_input": _param(source_id="index", source_key="index"), + "stop_input": _param(False), + }, + } + graph["paths"] = [["input", "index", "result", "outside"]] + with self.assertRaisesRegex(ValueError, "only expose values through its Loop Result"): + self.server._prepare_graph_loops(graph) + + def test_strictly_nested_loop_runs_once_per_parent_iteration(self): + graph = { + "sid": "test", + "nodes": { + "outer-index": { + "module": "modules.WorkflowControl", + "action": "LoopIndex", + "params": {"index_value": _param(0), "iteration_count": _param(1)}, + }, + "inner-index": { + "module": "modules.WorkflowControl", + "action": "LoopIndex", + "params": {"index_value": _param(0), "iteration_count": _param(1)}, + }, + "inner-result": { + "module": "modules.WorkflowControl", + "action": "LoopResult", + "params": { + "value_input": _param(source_id="inner-index", source_key="index"), + "stop_input": _param(False), + }, + }, + "outer-result": { + "module": "modules.WorkflowControl", + "action": "LoopResult", + "params": { + "value_input": _param(source_id="inner-result", source_key="value"), + "stop_input": _param(False), + }, + }, + }, + "paths": [["outer-index", "inner-index", "inner-result", "outer-result"]], + "loops": [ + { + "id": "outer", + "bodyNodeIds": ["outer-index", "inner-index", "inner-result", "outer-result"], + "iterations": 2, + "maxIterations": 10, + "indexNodeId": "outer-index", + "resultNodeId": "outer-result", + }, + { + "id": "inner", + "bodyNodeIds": ["inner-index", "inner-result"], + "iterations": 2, + "maxIterations": 10, + "indexNodeId": "inner-index", + "resultNodeId": "inner-result", + }, + ], + } + + prepared = self.server._prepare_graph_loops(graph) + result = self.server._execute_graph_loop(prepared["loops_by_id"]["outer"], graph["nodes"], graph["sid"]) + + self.assertEqual(result["collection"], [1, 1]) + inner_completions = [ + item for item in self.messages if item.get("node") == "inner" and item.get("status") == "succeeded" + ] + self.assertEqual(len(inner_completions), 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_workflow_store.py b/tests/test_workflow_store.py new file mode 100644 index 0000000..2b4256b --- /dev/null +++ b/tests/test_workflow_store.py @@ -0,0 +1,588 @@ +import asyncio +import json +import tempfile +import unittest +from unittest.mock import patch + +from modiff.NodeBase import NodeBase +from modiff.server import WebServer +from modiff.workflow_store import delete_workflow, get_workflow, list_workflows, save_workflow + + +class FakeRequest: + def __init__(self, workflow_id, payload=None): + self.match_info = {"workflow_id": workflow_id, "task_id": workflow_id, "output_id": workflow_id} + self._payload = payload + + async def json(self): + return self._payload + + +class WorkflowStoreTests(unittest.IsolatedAsyncioTestCase): + def setUp(self): + self.directory = tempfile.TemporaryDirectory() + + def tearDown(self): + self.directory.cleanup() + + def test_atomic_saved_workflow_revisions_and_delete(self): + first = save_workflow(self.directory.name, "workflow-1", {"title": "One", "snapshot": {"nodes": []}}) + second = save_workflow(self.directory.name, "workflow-1", {"title": "Two", "snapshot": {"nodes": [1]}}) + + self.assertEqual(first["revision"], 1) + self.assertEqual(second["revision"], 2) + self.assertEqual(get_workflow(self.directory.name, "workflow-1")["title"], "Two") + self.assertEqual([item["id"] for item in list_workflows(self.directory.name)], ["workflow-1"]) + self.assertTrue(delete_workflow(self.directory.name, "workflow-1")) + self.assertIsNone(get_workflow(self.directory.name, "workflow-1")) + + def test_workflow_id_cannot_escape_backend_storage(self): + with self.assertRaisesRegex(ValueError, "Workflow id"): + save_workflow(self.directory.name, "../escape", {"snapshot": {}}) + + async def test_routes_broadcast_backend_workflow_updates(self): + server = WebServer(modules={}, work_dir=self.directory.name, data_dir=self.directory.name) + messages = [] + server.queue_message = messages.append + + response = await server.workflow_put( + FakeRequest("shared", {"title": "Shared", "snapshot": {"nodes": [], "edges": []}, "clientId": "tab-a"}) + ) + record = json.loads(response.text) + self.assertEqual(record["revision"], 1) + self.assertEqual(messages[-1]["type"], "workflow_updated") + + listing = json.loads((await server.workflows_list(None)).text) + self.assertEqual(listing["workflows"][0]["id"], "shared") + + await server.workflow_delete(FakeRequest("shared")) + self.assertEqual(messages[-1], {"type": "workflow_deleted", "workflow_id": "shared"}) + + async def test_run_snapshot_retains_workflow_identity_after_frontend_disconnect(self): + server = WebServer(modules={}, work_dir=self.directory.name, data_dir=self.directory.name) + server.queue_message = lambda *_args, **_kwargs: None + graph = { + "nodes": [], + "runtimeHints": { + "clientRunId": "client-run-a", + "workflowTabId": "workflow-a", + "workflowTitle": "Shared workflow", + "workflowSnapshot": {"nodes": [], "edges": []}, + }, + } + task_id = await server.queue_task(lambda *_args: None, (graph,), None, "closed-tab", name="Graph execution") + server.current_task = { + "task_id": task_id, + "name": "Graph execution", + "sid": "closed-tab", + "started_at": 1, + "runtimeHints": graph["runtimeHints"], + } + server.queued_tasks.pop(task_id, None) + server._record_terminal_task("completed") + recent = server.recent_tasks[0] + self.assertEqual(recent["workflow_tab_id"], "workflow-a") + self.assertEqual(recent["workflow_title"], "Shared workflow") + self.assertEqual(recent["workflow_snapshot"], {"nodes": [], "edges": []}) + + queue_payload = json.loads((await server.get_queue(None)).text) + self.assertNotIn("workflow_snapshot", queue_payload["recent"][0]) + self.assertTrue(queue_payload["recent"][0]["has_workflow_snapshot"]) + + response = await server.get_run(FakeRequest(task_id)) + payload = json.loads(response.text) + self.assertEqual(payload["workflow_id"], "workflow-a") + self.assertEqual(payload["workflow_snapshot"], {"nodes": [], "edges": []}) + self.assertEqual(payload["outputs"], []) + + async def test_queued_and_early_running_snapshots_retain_submission_identity(self): + server = WebServer(modules={}, work_dir=self.directory.name, data_dir=self.directory.name) + server.loop = asyncio.get_running_loop() + messages = [] + server.queue_message = messages.append + graph = { + "nodes": {}, + "paths": [], + "runtimeHints": { + "clientRunId": "queued-client", + "runInputHash": "queued-hash", + "workflowTabId": "queued-workflow", + "nodeId": "preview-node", + "autoResourceCandidateId": "queued-auto-candidate", + }, + } + future = asyncio.get_running_loop().create_future() + task_id = await server.queue_task( + lambda _graph: "finished", + (graph,), + future, + "queued-session", + name="Graph execution", + ) + + queued_payload = json.loads((await server.get_queue(None)).text)["queued"][task_id] + self.assertEqual(queued_payload["client_run_id"], "queued-client") + self.assertEqual(queued_payload["run_input_hash"], "queued-hash") + self.assertEqual(queued_payload["workflow_tab_id"], "queued-workflow") + self.assertEqual(queued_payload["node_id"], "preview-node") + + worker = asyncio.create_task(server._main_worker()) + self.assertEqual(await asyncio.wait_for(future, timeout=2), "finished") + await asyncio.wait_for(server.main_queue.join(), timeout=2) + server._shutdown_event.set() + await server.main_queue.put(None) + await asyncio.wait_for(worker, timeout=2) + + started = next(message for message in messages if message.get("type") == "task_started") + self.assertEqual(started["client_run_id"], "queued-client") + self.assertEqual(started["workflow_tab_id"], "queued-workflow") + self.assertEqual(started["current"]["run_input_hash"], "queued-hash") + self.assertEqual(started["current"]["resourceCandidateId"], "queued-auto-candidate") + completed = next(message for message in messages if message.get("type") == "task_completed") + self.assertEqual(completed["client_run_id"], "queued-client") + self.assertEqual(completed["workflow_tab_id"], "queued-workflow") + + async def test_queued_field_action_echoes_workflow_canvas_ownership_on_mutations_and_completion(self): + class FieldNode(NodeBase): + def refresh(self, _values, _ref): + self.set_field_params("dtype", {"options": ["float16", "bfloat16"]}) + + module_name = ".".join(FieldNode.__module__.split(".")[:-1]) + definition = {module_name: {"FieldNode": {"params": {}}}} + with patch("modiff.NodeBase._module_map", return_value=definition): + node = FieldNode("field-node") + + server = WebServer(modules={}, work_dir=self.directory.name, data_dir=self.directory.name) + server.loop = asyncio.get_running_loop() + server.node_cache["field-node"] = node + messages = [] + server.queue_message = lambda message, *_args, **_kwargs: messages.append(message) + request = FakeRequest( + "field-node", + { + "node": "field-node", + "sid": "field-session", + "fn": "refresh", + "values": {"dtype": "float16"}, + "fieldKey": "dtype", + "queue": True, + "workflowTabId": "workflow-field", + "workflowCanvasEpoch": 17, + }, + ) + + with patch("modiff.NodeBase._server", return_value=server): + response = json.loads((await server.field_action(request)).text) + worker = asyncio.create_task(server._main_worker()) + await asyncio.wait_for(server.main_queue.join(), timeout=2) + server._shutdown_event.set() + await server.main_queue.put(None) + await asyncio.wait_for(worker, timeout=2) + + task_id = response["task_id"] + field_message = next(message for message in messages if message.get("type") == "set_field_params") + self.assertEqual(field_message["task_id"], task_id) + self.assertEqual(field_message["workflow_tab_id"], "workflow-field") + self.assertEqual(field_message["workflow_canvas_epoch"], 17) + self.assertEqual(field_message["sid"], "field-session") + + completed = next(message for message in messages if message.get("type") == "task_completed") + self.assertEqual(completed["task_id"], task_id) + self.assertEqual(completed["workflow_tab_id"], "workflow-field") + self.assertEqual(completed["workflow_canvas_epoch"], 17) + self.assertEqual(completed["args"][1]["node"], "field-node") + + async def test_generated_media_is_preserved_without_a_frontend_history_post(self): + server = WebServer(modules={}, work_dir=self.directory.name, data_dir=self.directory.name) + runtime_hints = { + "clientRunId": "disconnected-client", + "runInputHash": "disconnected-hash", + "workflowTabId": "disconnected-workflow", + "workflowSnapshot": { + "nodes": [], + "edges": [], + "studioForm": { + "mode": "text_to_image", + "modelType": "QwenImageModularPipeline", + "prompt": "Preserve this output", + }, + }, + } + server.current_task = { + "task_id": "disconnected-task", + "name": "Graph execution", + "sid": "gone-browser", + "started_at": 1, + "attempt_index": 0, + "runtimeHints": runtime_hints, + } + server.task_graphs["disconnected-task"] = {"runtimeHints": runtime_hints} + one_pixel_png = ( + "data:image/png;base64," + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUB" + "AScY42YAAAAASUVORK5CYII=" + ) + + output_id, persisted = server._persist_generated_output_update( + { + "type": "update_value", + "task_id": "disconnected-task", + "client_run_id": "disconnected-client", + "run_input_hash": "disconnected-hash", + "workflow_tab_id": "disconnected-workflow", + "attempt_index": 0, + "node": "preview", + "key": "images", + "data_type": "image", + "value": [one_pixel_png], + }, + display="ui_image", + ) + self.assertTrue(persisted) + self.assertTrue(output_id.startswith("run-output-")) + server._record_terminal_task("completed") + server.current_task = None + + payload = json.loads((await server.get_run(FakeRequest("disconnected-task"))).text) + self.assertEqual(len(payload["outputs"]), 1) + output = payload["outputs"][0] + self.assertEqual(output["id"], output_id) + self.assertEqual(output["taskId"], "disconnected-task") + self.assertEqual(output["clientRunId"], "disconnected-client") + self.assertEqual(output["workflowTabId"], "disconnected-workflow") + self.assertTrue(output["url"].startswith("/file?file=")) + self.assertEqual(output["displayType"], "image") + self.assertTrue(output["backendMediaPath"].startswith("@data/")) + self.assertTrue(server._resolve_managed_path_identifier(output["backendMediaPath"]).is_file()) + + async def test_preview_slot_changes_only_on_admission_and_output_promotion(self): + modules = { + "modules.Diffusers": { + "Preview": { + "params": { + "images": {"display": "ui_image"}, + } + } + } + } + server = WebServer(modules=modules, work_dir=self.directory.name, data_dir=self.directory.name) + server.queue_message = lambda *_args, **_kwargs: None + + def graph(client_run_id): + return { + "nodes": { + "preview": { + "module": "modules.Diffusers", + "action": "Preview", + "params": {"images": {"sourceId": "generate", "sourceKey": "images"}}, + } + }, + "runtimeHints": { + "clientRunId": client_run_id, + "workflowTabId": "workflow-a", + }, + } + + first_graph = graph("client-a") + first_task_id = await server.queue_task( + lambda *_args: None, (first_graph,), None, "session", name="Graph execution" + ) + state = server._read_studio_output_state() + slot = next(iter(state["previewSlots"].values())) + self.assertEqual(slot["status"], "pending") + self.assertIsNone(slot["currentOutputId"]) + self.assertEqual(slot["pendingTaskId"], first_task_id) + + server.current_task = { + "task_id": first_task_id, + "name": "Graph execution", + "sid": "session", + "attempt_index": 0, + "runtimeHints": first_graph["runtimeHints"], + } + output_id, persisted = server._persist_generated_output_update( + { + "type": "update_value", + "task_id": first_task_id, + "client_run_id": "client-a", + "workflow_tab_id": "workflow-a", + "attempt_index": 0, + "node": "preview", + "key": "images", + "value": ["data:image/png;base64,iVBORw0KGgo="], + }, + display="ui_image", + ) + self.assertTrue(persisted) + state = server._read_studio_output_state() + slot = next(iter(state["previewSlots"].values())) + self.assertEqual(slot["status"], "ready") + self.assertEqual(slot["currentOutputId"], output_id) + self.assertIsNone(slot["pendingTaskId"]) + + second_graph = graph("client-b") + second_task_id = await server.queue_task( + lambda *_args: None, (second_graph,), None, "session", name="Graph execution" + ) + state = server._read_studio_output_state() + slot = next(iter(state["previewSlots"].values())) + self.assertEqual(slot["status"], "pending") + self.assertIsNone(slot["currentOutputId"]) + self.assertEqual(slot["pendingTaskId"], second_task_id) + self.assertEqual([output["id"] for output in state["outputs"]], [output_id]) + + server._mark_studio_preview_run_terminal(second_task_id, "completed") + state = server._read_studio_output_state() + slot = next(iter(state["previewSlots"].values())) + self.assertEqual(slot["status"], "completed_without_output") + self.assertIsNone(slot["currentOutputId"]) + self.assertEqual([output["id"] for output in state["outputs"]], [output_id]) + + async def test_deleting_current_preview_never_promotes_an_older_output(self): + server = WebServer(modules={}, work_dir=self.directory.name, data_dir=self.directory.name) + outputs = [ + { + "id": "new", + "workflowTabId": "workflow-a", + "nodeId": "preview", + "fieldKey": "images", + "createdAt": 2, + }, + { + "id": "old", + "workflowTabId": "workflow-a", + "nodeId": "preview", + "fieldKey": "images", + "createdAt": 1, + }, + ] + key = server._studio_preview_slot_key("workflow-a", "preview", "images") + slot = { + "schemaVersion": 1, + "workflowTabId": "workflow-a", + "nodeId": "preview", + "fieldKey": "images", + "currentOutputId": "new", + "pendingClientRunId": None, + "pendingTaskId": None, + "generation": 2, + "attemptIndex": 0, + "status": "ready", + "updatedAt": 2, + } + server._write_studio_output_state(outputs, {key: slot}, revision=2) + await server.studio_outputs_delete(FakeRequest("new")) + + restored = server._read_studio_output_state() + self.assertEqual([output["id"] for output in restored["outputs"]], ["old"]) + self.assertIsNone(restored["previewSlots"][key]["currentOutputId"]) + self.assertEqual(restored["previewSlots"][key]["status"], "empty") + + async def test_terminal_failure_retains_actionable_recovery_metadata(self): + server = WebServer(modules={}, work_dir=self.directory.name, data_dir=self.directory.name) + server.current_task = { + "task_id": "failed-run", + "name": "Graph execution", + "sid": "closed-tab", + "started_at": 1, + "runtimeHints": { + "clientRunId": "failed-client", + "runInputHash": "failed-hash", + "workflowTabId": "failed-workflow", + }, + } + server.task_graphs["failed-run"] = {"runtimeHints": server.current_task["runtimeHints"]} + server._record_terminal_task( + "failed", + error_payload={ + "message": "CUDA out of memory while loading", + "exception_type": "OutOfMemoryError", + "category": "oom", + "error_code": "cuda_oom", + "recovery_hint": "Apply the Low-VRAM preset and retry.", + "node": "loader-node", + "node_name": "Load Models", + "oom": True, + }, + ) + server.current_task = None + + payload = json.loads((await server.get_run(FakeRequest("failed-run"))).text) + task = payload["task"] + self.assertEqual(task["client_run_id"], "failed-client") + self.assertEqual(task["workflow_tab_id"], "failed-workflow") + self.assertEqual(task["run_input_hash"], "failed-hash") + self.assertEqual(task["exception_type"], "OutOfMemoryError") + self.assertEqual(task["category"], "oom") + self.assertEqual(task["error_code"], "cuda_oom") + self.assertEqual(task["recovery_hint"], "Apply the Low-VRAM preset and retry.") + self.assertEqual(task["node"], "loader-node") + self.assertEqual(task["node_name"], "Load Models") + self.assertTrue(task["oom"]) + + async def test_run_details_return_only_strictly_correlated_outputs(self): + server = WebServer(modules={}, work_dir=self.directory.name, data_dir=self.directory.name) + server.queue_message = lambda *_args, **_kwargs: None + graph = { + "nodes": [], + "runtimeHints": { + "clientRunId": "client-run-a", + "workflowTabId": "workflow-a", + "workflowTitle": "Shared workflow", + "workflowSnapshot": {"nodes": [], "edges": []}, + }, + } + task_id = await server.queue_task(lambda *_args: None, (graph,), None, "closed-tab", name="Graph execution") + server.current_task = { + "task_id": task_id, + "name": "Graph execution", + "sid": "closed-tab", + "started_at": 1, + "runtimeHints": graph["runtimeHints"], + } + server.queued_tasks.pop(task_id, None) + server._record_terminal_task("completed") + server._write_studio_outputs( + [ + { + "id": "direct-match", + "taskId": task_id, + "clientRunId": "client-run-a", + "url": "/file?file=direct.webp", + }, + { + "id": "provenance-match", + "provenance": { + "backendExecutionId": task_id, + "clientRunId": "client-run-a", + }, + "url": "/file?file=provenance.webp", + }, + { + "id": "legacy-task-match", + "taskId": task_id, + "url": "/file?file=legacy.webp", + }, + { + "id": "media-item-match", + "mediaItems": [ + { + "taskId": task_id, + "clientRunId": "client-run-a", + "url": "/file?file=item.webp", + } + ], + "url": "/file?file=item.webp", + }, + { + "id": "wrong-task", + "taskId": "another-task", + "clientRunId": "client-run-a", + "url": "/file?file=wrong-task.webp", + }, + { + "id": "wrong-client", + "taskId": task_id, + "clientRunId": "client-run-b", + "url": "/file?file=wrong-client.webp", + }, + { + "id": "workflow-only", + "workflowTabId": "workflow-a", + "url": "/file?file=workflow-only.webp", + }, + { + "id": "conflicting-task-identities", + "taskId": "another-task", + "backendProvenance": { + "backendExecutionId": task_id, + "clientRunId": "client-run-a", + }, + "url": "/file?file=conflict.webp", + }, + ] + ) + + payload = json.loads((await server.get_run(FakeRequest(task_id))).text) + + self.assertEqual( + [output["id"] for output in payload["outputs"]], + ["direct-match", "provenance-match", "legacy-task-match", "media-item-match"], + ) + + async def test_run_details_use_terminal_task_client_identity_without_a_saved_graph(self): + server = WebServer(modules={}, work_dir=self.directory.name, data_dir=self.directory.name) + server.recent_tasks = [ + { + "task_id": "remote-task", + "name": "Direct render", + "status": "completed", + "client_run_id": "remote-client-a", + } + ] + server._write_studio_outputs( + [ + { + "id": "remote-match", + "taskId": "remote-task", + "clientRunId": "remote-client-a", + "url": "/file?file=remote.webp", + }, + { + "id": "remote-wrong-client", + "taskId": "remote-task", + "clientRunId": "remote-client-b", + "url": "/file?file=wrong-client.webp", + }, + ] + ) + + payload = json.loads((await server.get_run(FakeRequest("remote-task"))).text) + + self.assertEqual([output["id"] for output in payload["outputs"]], ["remote-match"]) + + async def test_run_details_recover_workflow_navigation_from_durable_output_after_restart(self): + server = WebServer(modules={}, work_dir=self.directory.name, data_dir=self.directory.name) + server.recent_tasks = [ + { + "task_id": "restarted-task", + "name": "Graph execution", + "status": "completed", + "client_run_id": "restarted-client", + "workflow_tab_id": "workflow-after-restart", + } + ] + workflow_snapshot = { + "nodes": [{"id": "preview"}], + "edges": [], + "viewport": {"x": 0, "y": 0, "zoom": 1}, + } + server._write_studio_outputs( + [ + { + "id": "restarted-output", + "taskId": "restarted-task", + "clientRunId": "restarted-client", + "workflowTabId": "workflow-after-restart", + "url": "/file?file=restarted.webp", + "apiGraphSnapshot": { + "runtimeHints": { + "clientRunId": "restarted-client", + "workflowTabId": "workflow-after-restart", + "workflowTitle": "Recovered workflow name", + "workflowSnapshot": workflow_snapshot, + } + }, + } + ] + ) + + payload = json.loads((await server.get_run(FakeRequest("restarted-task"))).text) + + self.assertEqual(payload["workflow_id"], "workflow-after-restart") + self.assertEqual(payload["workflow_title"], "Recovered workflow name") + self.assertEqual(payload["workflow_snapshot"], workflow_snapshot) + + +if __name__ == "__main__": + unittest.main() diff --git a/utils/huggingface.py b/utils/huggingface.py index 287a54f..6a8384a 100644 --- a/utils/huggingface.py +++ b/utils/huggingface.py @@ -1,12 +1,20 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging logger = logging.getLogger('modiff') -from huggingface_hub import scan_cache_dir, logging as hf_logging, repo_exists, HfApi, try_to_load_from_cache +from huggingface_hub import scan_cache_dir, logging as hf_logging, repo_exists as hf_repo_exists, HfApi, try_to_load_from_cache +from huggingface_hub.utils import validate_repo_id hf_logging.set_verbosity_error() from modiff.config import CONFIG +from modiff.model_artifact_catalog import resolve_model_revision from collections import Counter +from fnmatch import fnmatchcase from pathlib import Path +import hashlib import json import os +import re +import shutil +import tempfile import threading import time from typing import Optional, Callable @@ -69,6 +77,8 @@ def _common_appdata_hf_cache_candidates(): HF_CACHE_REPO_PREFIXES = ('models--', 'datasets--', 'spaces--') +HF_DOWNLOAD_PLAN_FILE_PREVIEW_LIMIT = 200 +_HF_XET_MODE_LOCK = threading.RLock() def _path_looks_like_hf_cache_root(path_obj: Path): @@ -162,7 +172,6 @@ def _appdata_candidates(): roots = [ ('AppData OpenStudio models', Path(local_app_data) / 'OpenStudio' / 'models'), ('AppData OpenStudio', Path(local_app_data) / 'OpenStudio'), - ('AppData ComfyUI models', Path(local_app_data) / 'ComfyUI' / 'models'), ] return [(label, str(path)) for label, path in roots] @@ -179,9 +188,22 @@ def _count_entries_bounded(path_obj: Path, limit: int = 5000): CONFIG_FILE_NAMES = {'model_index.json', 'model_config.json', 'config.json'} +def validate_hf_repo_id(repo_id: str): + if not isinstance(repo_id, str) or not repo_id.strip() or '\\' in repo_id: + raise ValueError('Hugging Face repository IDs must use the namespace/repository form with forward slashes.') + validate_repo_id(repo_id) + return repo_id + + def _repo_cache_dir(repo_id: str, cache_dir: str | None = None): + validate_hf_repo_id(repo_id) root = Path(cache_dir or CONFIG.hf['cache_dir'] or str(HUGGINGFACE_HUB_CACHE)).expanduser() - return root / f"models--{repo_id.replace('/', '--')}" + candidate = root / f"models--{repo_id.replace('/', '--')}" + try: + candidate.resolve(strict=False).relative_to(root.resolve(strict=False)) + except (OSError, RuntimeError, ValueError) as error: + raise ValueError(f'Hugging Face repository ID resolves outside the configured cache: {repo_id!r}.') from error + return candidate def _directory_summary(path_obj: Path, limit: int = 5000, sample_limit: int = 12): @@ -430,7 +452,81 @@ def _get_sibling_size(sibling): return None -def _snapshot_file_status(path_obj: Path, expected_files: list[dict]): +def _get_sibling_blob_hash(sibling): + lfs = getattr(sibling, 'lfs', None) + candidates = [] + if isinstance(lfs, dict): + candidates.extend([lfs.get('sha256'), lfs.get('oid')]) + elif lfs is not None: + candidates.extend([getattr(lfs, 'sha256', None), getattr(lfs, 'oid', None)]) + candidates.extend([getattr(sibling, 'sha256', None), getattr(sibling, 'blob_id', None)]) + for candidate in candidates: + value = str(candidate or '').lower().removeprefix('sha256:') + if re.fullmatch(r'[a-f0-9]{64}', value): + return value + return None + + +def _snapshot_dir_for_plan(repo_path: Path, plan: dict | None = None, snapshot_path=None) -> Path | None: + """Resolve only the snapshot identified by a download plan or Hub result. + + Hugging Face repositories can retain several revisions. Selecting by mtime + can validate or repair an unrelated revision, so an explicit path, resolved + Hub commit, or revision ref always takes precedence. The newest-snapshot + fallback exists only for legacy callers that supply no revision identity. + """ + + snapshots_dir = repo_path / 'snapshots' + snapshots_root = snapshots_dir.resolve(strict=False) + + def contained_snapshot(candidate) -> Path | None: + if not candidate: + return None + candidate = Path(candidate).expanduser() + if not candidate.is_absolute(): + candidate = repo_path / candidate + try: + candidate.resolve(strict=False).relative_to(snapshots_root) + except (OSError, RuntimeError, ValueError): + return None + return candidate if candidate.is_dir() else None + + plan = plan if isinstance(plan, dict) else {} + explicit_snapshot = snapshot_path or plan.get('snapshot_path') + if explicit_snapshot: + return contained_snapshot(explicit_snapshot) + + snapshot_commit = str(plan.get('snapshot_commit') or '').strip() + if snapshot_commit: + return contained_snapshot(snapshots_dir / snapshot_commit) + + revision = str(plan.get('revision') or '').strip() + if revision: + direct = contained_snapshot(snapshots_dir / revision) + if direct is not None: + return direct + refs_root = repo_path / 'refs' + ref_path = refs_root / revision + try: + ref_path.resolve(strict=False).relative_to(refs_root.resolve(strict=False)) + commit = ref_path.read_text(encoding='utf-8').strip() + except (OSError, RuntimeError, UnicodeDecodeError, ValueError): + commit = '' + if commit: + return contained_snapshot(snapshots_dir / commit) + return None + + if not snapshots_dir.exists(): + return None + try: + snapshots = [entry for entry in snapshots_dir.iterdir() if entry.is_dir()] + snapshots.sort(key=lambda entry: entry.stat().st_mtime, reverse=True) + return snapshots[0] if snapshots else None + except OSError: + return None + + +def _snapshot_file_status(path_obj: Path, expected_files: list[dict], *, snapshot_dir: Path | None = None): snapshots_dir = path_obj / 'snapshots' if not snapshots_dir.exists() or not expected_files: return { @@ -438,15 +534,7 @@ def _snapshot_file_status(path_obj: Path, expected_files: list[dict]): 'completed_bytes': 0, } - latest_snapshot = None - try: - snapshot_dirs = [entry for entry in snapshots_dir.iterdir() if entry.is_dir()] - snapshot_dirs.sort(key=lambda entry: entry.stat().st_mtime, reverse=True) - latest_snapshot = snapshot_dirs[0] if snapshot_dirs else None - except OSError: - latest_snapshot = None - - if latest_snapshot is None: + if snapshot_dir is None: return { 'completed_file_count': 0, 'completed_bytes': 0, @@ -458,7 +546,7 @@ def _snapshot_file_status(path_obj: Path, expected_files: list[dict]): name = expected_file.get('name') if not name: continue - file_path = latest_snapshot / str(name) + file_path = snapshot_dir / str(name) if not file_path.exists(): continue try: @@ -475,7 +563,7 @@ def _snapshot_file_status(path_obj: Path, expected_files: list[dict]): } -def _active_download_files(path_obj: Path, limit: int = 5): +def _active_download_files(path_obj: Path, limit: int = 5, expected_blob_hashes: set[str] | None = None): active_files = [] if not path_obj.exists(): return active_files @@ -486,6 +574,9 @@ def _active_download_files(path_obj: Path, limit: int = 5): continue name = entry.name.lower() if name.endswith('.incomplete') or name.endswith('.lock'): + blob_hash = name.rsplit('.', 1)[0] + if expected_blob_hashes is not None and blob_hash not in expected_blob_hashes: + continue candidates.append(entry) candidates.sort(key=lambda entry: entry.stat().st_mtime, reverse=True) for entry in candidates[:limit]: @@ -497,7 +588,7 @@ def _active_download_files(path_obj: Path, limit: int = 5): def _download_progress_snapshot(repo_id: str, cache_dir: str | None, plan: dict | None = None): path_obj = _repo_cache_dir(repo_id, cache_dir) - expected_files = plan.get('files', []) if isinstance(plan, dict) else [] + expected_files = _plan_validation_files(plan) if not path_obj.exists(): return { 'path': str(path_obj), @@ -509,8 +600,21 @@ def _download_progress_snapshot(repo_id: str, cache_dir: str | None, plan: dict } summary = _directory_summary(path_obj, limit=20000, sample_limit=0) - snapshot_status = _snapshot_file_status(path_obj, expected_files) - active_files = _active_download_files(path_obj) + snapshot_dir = _snapshot_dir_for_plan(path_obj, plan) + snapshot_status = _snapshot_file_status(path_obj, expected_files, snapshot_dir=snapshot_dir) + expected_blob_hashes = None + if isinstance(plan, dict) and plan.get('selection_limited'): + expected_blob_hashes = { + str(item.get('blob_hash') or '').lower() + for item in expected_files + if isinstance(item, dict) and item.get('blob_hash') + } + # If Hub metadata omitted every hash, retain the conservative behavior + # and report all partials rather than accidentally accepting a selected + # file that is still incomplete. + if not expected_blob_hashes: + expected_blob_hashes = None + active_files = _active_download_files(path_obj, expected_blob_hashes=expected_blob_hashes) return { 'path': str(path_obj), 'cache_dir': str(Path(cache_dir or CONFIG.hf['cache_dir'] or str(HUGGINGFACE_HUB_CACHE)).expanduser()), @@ -523,23 +627,37 @@ def _download_progress_snapshot(repo_id: str, cache_dir: str | None, plan: dict } -def _repo_download_plan(repo_id: str): +def _repo_download_plan( + repo_id: str, + allow_patterns: list[str] | tuple[str, ...] | None = None, + revision: str | None = None, +): + _repo_cache_dir(repo_id) + revision = resolve_model_revision(repo_id, revision) try: api = HfApi(token=CONFIG.hf['token'], library_name='MoDiff') + revision_kwargs = {'revision': revision} if revision else {} try: - info = api.model_info(repo_id, files_metadata=True) + info = api.model_info(repo_id, files_metadata=True, **revision_kwargs) except TypeError: - info = api.model_info(repo_id) + try: + info = api.model_info(repo_id, **revision_kwargs) + except TypeError: + info = api.model_info(repo_id) siblings = getattr(info, 'siblings', []) or [] + selected = {str(name) for name in (allow_patterns or []) if str(name).strip()} files = [] total_bytes = 0 known_count = 0 for sibling in siblings: filename = getattr(sibling, 'rfilename', None) + if selected and not any(fnmatchcase(str(filename or ""), pattern) for pattern in selected): + continue size = _get_sibling_size(sibling) files.append({ 'name': filename, 'size': size, + 'blob_hash': _get_sibling_blob_hash(sibling), }) if isinstance(size, int) and size >= 0: total_bytes += size @@ -547,8 +665,17 @@ def _repo_download_plan(repo_id: str): return { 'total_bytes': total_bytes if known_count > 0 else None, 'total_file_count': len(files), - 'files': files[:200], + # Keep the user-facing/persisted plan compact, while retaining the + # complete metadata set for validation and repair in this process. + 'files': files[:HF_DOWNLOAD_PLAN_FILE_PREVIEW_LIMIT], + 'validation_files': files, + 'files_truncated': len(files) > HF_DOWNLOAD_PLAN_FILE_PREVIEW_LIMIT, 'size_known': known_count > 0, + 'private': bool(getattr(info, 'private', False)), + 'gated': bool(getattr(info, 'gated', False)), + 'selection_limited': bool(selected), + 'revision': revision, + 'snapshot_commit': str(getattr(info, 'sha', None) or '').strip() or None, } except Exception as e: logger.debug(f"Could not build download plan for {repo_id}: {e}") @@ -556,11 +683,26 @@ def _repo_download_plan(repo_id: str): 'total_bytes': None, 'total_file_count': None, 'files': [], + 'validation_files': [], + 'files_truncated': False, 'size_known': False, 'plan_error': str(e), + 'selection_limited': bool(allow_patterns), + 'revision': revision, + 'snapshot_commit': None, } +def _plan_validation_files(plan: dict | None): + if not isinstance(plan, dict): + return [] + validation_files = plan.get('validation_files') + if isinstance(validation_files, list): + return validation_files + files = plan.get('files') + return files if isinstance(files, list) else [] + + def _write_repo_download_plan(repo_id: str, cache_dir: str | None, plan: dict): try: repo_path = _repo_cache_dir(repo_id, cache_dir) @@ -571,8 +713,12 @@ def _write_repo_download_plan(repo_id: str, cache_dir: str | None, plan: dict): 'total_bytes': plan.get('total_bytes'), 'total_file_count': plan.get('total_file_count'), 'files': plan.get('files') if isinstance(plan.get('files'), list) else [], + 'files_truncated': bool(plan.get('files_truncated')), 'size_known': plan.get('size_known'), 'plan_error': plan.get('plan_error'), + 'selection_limited': bool(plan.get('selection_limited')), + 'revision': plan.get('revision'), + 'snapshot_commit': plan.get('snapshot_commit'), } with (repo_path / '.modiff_download_plan.json').open('w', encoding='utf-8') as handle: json.dump(payload, handle, indent=2, sort_keys=True) @@ -593,16 +739,308 @@ def _repair_validation_summary(repo_id: str, cache_dir: str | None, plan: dict | reasons.append(f"Active partial download files remain: {', '.join(active_files[:3])}.") if isinstance(total_file_count, int) and isinstance(completed_file_count, int) and completed_file_count < total_file_count: complete = False - reasons.append(f"Only {completed_file_count} of {total_file_count} expected files were found in the latest snapshot.") + reasons.append(f"Only {completed_file_count} of {total_file_count} expected files were found in the requested snapshot.") if isinstance(total_bytes, int) and total_bytes > 0 and (snapshot.get('completed_bytes') or 0) < total_bytes: complete = False - reasons.append("Latest snapshot byte count is lower than Hugging Face metadata.") + reasons.append("Requested snapshot byte count is lower than Hugging Face metadata.") + loader_smoke = _loader_config_smoke_summary(repo_id, cache_dir, plan) + if not loader_smoke.get('complete'): + complete = False + reasons.append(loader_smoke.get('reason') or 'The Diffusers loader configuration smoke test failed.') return { 'repo_id': repo_id, 'complete': complete, 'repair_required': not complete, 'reason': ' '.join(reasons) if reasons else 'Download completed and local snapshot metadata looks complete.', 'snapshot': snapshot, + 'loader_smoke': loader_smoke, + } + + +def _latest_snapshot_dir(repo_path: Path) -> Path | None: + """Compatibility wrapper for callers without a revision-aware plan.""" + return _snapshot_dir_for_plan(repo_path) + + +def _prepare_snapshot_repair(repo_id: str, cache_dir: str | None, plan: dict | None): + """Invalidate only demonstrably bad cached files before a resumed repair.""" + repo_path = _repo_cache_dir(repo_id, cache_dir) + snapshot_dir = _snapshot_dir_for_plan(repo_path, plan) + removed = [] + removed.extend(_cleanup_redundant_incomplete_files(repo_id, cache_dir)) + if snapshot_dir is not None: + for expected in _plan_validation_files(plan): + name = expected.get('name') if isinstance(expected, dict) else None + expected_size = expected.get('size') if isinstance(expected, dict) else None + if not name or not isinstance(expected_size, int) or expected_size < 0: + continue + snapshot_file = snapshot_dir / str(name) + if not snapshot_file.exists(): + continue + try: + actual_size = snapshot_file.stat().st_size + except OSError: + continue + if actual_size == expected_size: + continue + target = snapshot_file.resolve(strict=False) if snapshot_file.is_symlink() else snapshot_file + try: + snapshot_file.unlink() + removed.append(str(snapshot_file.relative_to(repo_path))) + except OSError: + continue + try: + target.relative_to(repo_path / 'blobs') + except ValueError: + continue + try: + if target.is_file() and target.stat().st_size != expected_size: + target.unlink() + removed.append(str(target.relative_to(repo_path))) + except OSError: + pass + + blobs_dir = repo_path / 'blobs' + if blobs_dir.exists(): + try: + for partial in blobs_dir.glob('*.incomplete'): + if partial.stat().st_size != 0: + continue + partial.unlink() + removed.append(str(partial.relative_to(repo_path))) + except OSError: + pass + partial_repair = _promote_verified_complete_partials(repo_path, snapshot_dir, plan) + removed.extend(partial_repair['invalidated']) + return {'removed': removed, 'promoted': partial_repair['promoted']} + + +def _promote_verified_complete_partials(repo_path: Path, snapshot_dir: Path | None, plan: dict | None): + """Atomically adopt a complete Xet partial only after size and SHA-256 verification.""" + if snapshot_dir is None: + return {'promoted': [], 'invalidated': []} + expected_sizes = {} + for expected in _plan_validation_files(plan): + name = expected.get('name') if isinstance(expected, dict) else None + expected_size = expected.get('size') if isinstance(expected, dict) else None + if not name or not isinstance(expected_size, int) or expected_size < 0: + continue + blob_hash = str(expected.get('blob_hash') or '') + snapshot_file = snapshot_dir / str(name) + if not re.fullmatch(r'[a-f0-9]{64}', blob_hash) and snapshot_file.is_symlink(): + blob_hash = snapshot_file.resolve(strict=False).name + if re.fullmatch(r'[a-f0-9]{64}', blob_hash): + expected_sizes[blob_hash] = expected_size + + blobs_dir = repo_path / 'blobs' + promoted = [] + invalidated = [] + if not blobs_dir.exists(): + return {'promoted': promoted, 'invalidated': invalidated} + candidates = sorted(blobs_dir.glob('*.incomplete'), key=lambda path: path.stat().st_size, reverse=True) + for partial in candidates: + blob_hash = partial.name.split('.', 1)[0] + expected_size = expected_sizes.get(blob_hash) + final_blob = blobs_dir / blob_hash + try: + if final_blob.exists() or expected_size is None or partial.stat().st_size != expected_size: + continue + digest = hashlib.sha256() + with partial.open('rb') as handle: + for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b''): + digest.update(chunk) + if digest.hexdigest() != blob_hash: + partial.unlink() + invalidated.append(str(partial.relative_to(repo_path))) + continue + partial.replace(final_blob) + promoted.append(str(final_blob.relative_to(repo_path))) + except OSError: + continue + return {'promoted': promoted, 'invalidated': invalidated} + + +def _cleanup_redundant_incomplete_files(repo_id: str, cache_dir: str | None): + """Remove stale partials only when their immutable final blob now exists.""" + repo_path = _repo_cache_dir(repo_id, cache_dir) + blobs_dir = repo_path / 'blobs' + removed = [] + if not blobs_dir.exists(): + return removed + try: + for partial in blobs_dir.glob('*.incomplete'): + blob_hash = partial.name.split('.', 1)[0] + final_blob = blobs_dir / blob_hash + if not final_blob.is_file() or final_blob.stat().st_size <= 0: + continue + partial.unlink() + removed.append(str(partial.relative_to(repo_path))) + except OSError: + pass + return removed + + +def _retryable_download_error(error: Exception): + status_code = getattr(getattr(error, 'response', None), 'status_code', None) + return isinstance(error, (TimeoutError, ConnectionError)) or status_code in {408, 429, 500, 502, 503, 504} + + +def _repair_from_verified_source( + repo_id: str, + source_repo_id: str, + cache_dir: str | None, + plan: dict, +): + """Stage only missing files from a byte-identical Hub repository. + + The source is never trusted by name alone. Both repositories must publish + the same filename, size, and LFS SHA-256, and the downloaded bytes are + hashed again before being linked into the requested repository snapshot. + """ + from huggingface_hub import hf_hub_download + + source_plan = _repo_download_plan(source_repo_id) + source_files = { + str(item.get('name')): item + for item in _plan_validation_files(source_plan) + if isinstance(item, dict) and item.get('name') + } + repo_path = _repo_cache_dir(repo_id, cache_dir) + snapshot_dir = _snapshot_dir_for_plan(repo_path, plan) + if snapshot_dir is None: + return [] + + missing = [] + for expected in _plan_validation_files(plan): + if not isinstance(expected, dict) or not expected.get('name'): + continue + name = str(expected['name']) + expected_size = expected.get('size') + expected_hash = str(expected.get('blob_hash') or '') + target = snapshot_dir / name + try: + if target.is_file() and (not isinstance(expected_size, int) or target.stat().st_size == expected_size): + continue + except OSError: + pass + source = source_files.get(name) + if ( + source is None + or not isinstance(expected_size, int) + or expected_size < 0 + or source.get('size') != expected_size + or not re.fullmatch(r'[a-f0-9]{64}', expected_hash) + or source.get('blob_hash') != expected_hash + ): + continue + missing.append((name, expected_size, expected_hash)) + + if not missing: + return [] + + cache_root = Path(cache_dir or CONFIG.hf['cache_dir'] or str(HUGGINGFACE_HUB_CACHE)).expanduser() + staging_root = cache_root / '.modiff-repair-staging' + staging_root.mkdir(parents=True, exist_ok=True) + staging_dir = Path(tempfile.mkdtemp(prefix='verified-source-', dir=staging_root)) + repaired = [] + try: + for name, expected_size, expected_hash in missing: + download_kwargs = { + 'repo_id': source_repo_id, + 'filename': name, + 'token': CONFIG.hf['token'], + 'local_dir': str(staging_dir), + 'force_download': False, + 'revision': source_plan.get('revision'), + } + try: + staged = Path(hf_hub_download(**download_kwargs)) + except Exception as error: + status_code = getattr(getattr(error, 'response', None), 'status_code', None) + if status_code != 403 or source_plan.get('private') or source_plan.get('gated'): + raise + # A configured account can occasionally receive a stale or + # permission-scoped CAS signature for an otherwise public + # object. Retry the already metadata-verified public mirror + # anonymously; private/gated sources still fail actionably. + download_kwargs['token'] = False + # Refresh the signed redirect instead of reusing local-dir + # metadata produced by the authenticated attempt. + download_kwargs['force_download'] = True + staged = Path(hf_hub_download(**download_kwargs)) + if staged.stat().st_size != expected_size: + raise OSError(f"Verified repair source returned the wrong size for {name}.") + digest = hashlib.sha256() + with staged.open('rb') as handle: + for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b''): + digest.update(chunk) + if digest.hexdigest() != expected_hash: + raise OSError(f"Verified repair source returned the wrong SHA-256 for {name}.") + + blobs_dir = repo_path / 'blobs' + blobs_dir.mkdir(parents=True, exist_ok=True) + final_blob = blobs_dir / expected_hash + if not final_blob.exists(): + try: + os.link(staged, final_blob) + except OSError: + shutil.copyfile(staged, final_blob) + target = snapshot_dir / name + target.parent.mkdir(parents=True, exist_ok=True) + if target.exists() or target.is_symlink(): + target.unlink() + target.symlink_to(os.path.relpath(final_blob, target.parent)) + repaired.append(name) + finally: + shutil.rmtree(staging_dir, ignore_errors=True) + try: + staging_root.rmdir() + except OSError: + pass + return repaired + + +def _loader_config_smoke_summary(repo_id: str, cache_dir: str | None, plan: dict | None): + """Parse an expected local loader config without materializing model weights.""" + expected = { + str(item.get('name')) + for item in _plan_validation_files(plan) + if isinstance(item, dict) and item.get('name') + } + config_names = [name for name in ('model_index.json', 'config.json') if name in expected] + if not config_names: + return { + 'attempted': False, + 'complete': True, + 'reason': 'No Diffusers loader configuration is declared for this artifact.', + } + + snapshot = _snapshot_dir_for_plan(_repo_cache_dir(repo_id, cache_dir), plan) + if snapshot is None: + return {'attempted': True, 'complete': False, 'reason': 'No local snapshot exists for loader validation.'} + + for name in config_names: + config_path = snapshot / name + if not config_path.exists(): + continue + try: + payload = json.loads(config_path.read_text(encoding='utf-8')) + except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc: + return {'attempted': True, 'complete': False, 'reason': f'{name} is not readable JSON: {exc}'} + if not isinstance(payload, dict): + return {'attempted': True, 'complete': False, 'reason': f'{name} must contain a JSON object.'} + return { + 'attempted': True, + 'complete': True, + 'config': name, + 'class_name': payload.get('_class_name'), + 'reason': f'{name} parsed successfully from the local snapshot.', + } + + return { + 'attempted': True, + 'complete': False, + 'reason': f"Expected loader config was not found: {', '.join(config_names)}.", } @@ -854,7 +1292,7 @@ def repo_exists(model_id: str, token: Optional[str] = None): return False try: - return repo_exists(model_id, token=token) + return hf_repo_exists(model_id, token=token) except Exception as e: logger.error(f'Error checking if repo exists for {model_id}: {e}') return False @@ -925,7 +1363,7 @@ def delete_model(*revisions: str): strategy = cache.delete_revisions(*revisions) if not strategy.repos: - logger.error(f'No models to delete') + logger.error('No models to delete') return False try: @@ -957,15 +1395,23 @@ def search_hub(query: str, limit: int = 100): return models -# TODO: not yet implemented -def download_hub_model(model_id: str, progress_cb: Optional[Callable[[object], None]] = None, repair: bool = False): - from huggingface_hub import snapshot_download +def download_hub_model( + model_id: str, + progress_cb: Optional[Callable[[object], None]] = None, + repair: bool = False, + repair_source_repo_id: str | None = None, + allow_patterns: list[str] | tuple[str, ...] | None = None, + revision: str | None = None, +): + from huggingface_hub import constants as hf_constants, snapshot_download cache_dir = CONFIG.hf['cache_dir'] token = CONFIG.hf['token'] stop_event = threading.Event() monitor_thread = None - plan = _repo_download_plan(model_id) + requested_files = [str(name) for name in (allow_patterns or []) if str(name).strip()] + revision = resolve_model_revision(model_id, revision) + plan = _repo_download_plan(model_id, requested_files, revision) _write_repo_download_plan(model_id, cache_dir, plan) started_at = time.time() last_state = {'bytes': 0, 'time': started_at} @@ -999,6 +1445,7 @@ def build_payload(status: str, progress: float | None = None, error: str | None payload = { 'repo_id': model_id, + 'revision': revision, 'status': status, 'phase': status, **snapshot, @@ -1018,7 +1465,14 @@ def build_payload(status: str, progress: float | None = None, error: str | None if computed_progress is not None: payload['progress'] = computed_progress if error: - payload['error'] = error + if status == 'error': + payload['error'] = error + else: + # A transient retry/fallback message must not poison the + # client-side merged state as a terminal failure. + payload['error'] = None + payload['last_error'] = error + payload['message'] = error last_state['bytes'] = downloaded_bytes last_state['time'] = now @@ -1041,17 +1495,60 @@ def monitor_download(): try: emit('planning', 0.0) + if repair: + _prepare_snapshot_repair(model_id, cache_dir, plan) emit('downloading', 0.0) if progress_cb: monitor_thread = threading.Thread(target=monitor_download, daemon=True) monitor_thread.start() - snapshot_download( - repo_id=model_id, - cache_dir=cache_dir, - token=token, - force_download=bool(repair), - resume_download=not bool(repair), - ) + # huggingface_hub exposes Xet disabling only as process-global state. + # Serialize every app-owned snapshot download so a repair cannot leak + # its temporary mode into a concurrent normal download. + with _HF_XET_MODE_LOCK: + previous_disable_xet = hf_constants.HF_HUB_DISABLE_XET + if repair: + # A repair must not repeat a wedged Xet reconstruction session. + # Standard Hub HTTP resumes immutable blobs with bounded request + # retries and leaves every already-valid cache blob untouched. + hf_constants.HF_HUB_DISABLE_XET = True + try: + attempts = 3 if repair else 1 + for attempt in range(attempts): + try: + download_kwargs = { + 'repo_id': model_id, + 'cache_dir': cache_dir, + 'token': token, + 'force_download': False, + 'revision': revision, + } + if requested_files: + download_kwargs['allow_patterns'] = requested_files + downloaded_snapshot = snapshot_download( + **download_kwargs, + ) + if isinstance(downloaded_snapshot, (str, os.PathLike)): + # Carry the authoritative path returned by the Hub into + # every post-download validation step. This also covers + # branch/tag revisions whose local snapshot directory is + # named after the resolved commit rather than the ref. + plan['snapshot_path'] = str(downloaded_snapshot) + break + except Exception as error: + if attempt + 1 >= attempts or not _retryable_download_error(error): + if repair and repair_source_repo_id: + emit('repairing_from_verified_source', None, str(error)) + repaired = _repair_from_verified_source( + model_id, repair_source_repo_id, cache_dir, plan + ) + if repaired: + break + raise + emit('retrying', None, str(error)) + time.sleep(2 ** attempt) + finally: + hf_constants.HF_HUB_DISABLE_XET = previous_disable_xet + _cleanup_redundant_incomplete_files(model_id, cache_dir) stop_event.set() if monitor_thread: monitor_thread.join(timeout=1.0) @@ -1062,6 +1559,7 @@ def monitor_download(): logger.warning(f"Model download validation found repair issues for {model_id}: {validation.get('reason')}") return { 'repo_id': model_id, + 'revision': revision, 'complete': False, 'repair_required': True, 'validation': validation, @@ -1080,11 +1578,20 @@ def monitor_download(): return { 'repo_id': model_id, + 'revision': revision, + 'requested_files': requested_files, 'complete': True, 'repair_required': False, - 'validation': _repair_validation_summary(model_id, cache_dir, plan), + 'validation': validation, } def local_files_only(model_id: str): - online_status = CONFIG.hf['online_status'] - return online_status == 'Offline' or (online_status == 'Auto' and model_id in get_local_model_ids()) + """Keep inference/model loaders read-only with respect to the Hub cache. + + Model installation, repair, authentication, and progress reporting belong to + the app's Model Manager endpoint. A loader must never turn a graph execution + into an untracked Hugging Face download merely because the app is online. + Local paths are also safe with this flag because ``from_pretrained`` ignores + Hub lookup when the supplied directory already exists. + """ + return True diff --git a/utils/memory_menager.py b/utils/memory_menager.py index 08b3190..e1631d9 100644 --- a/utils/memory_menager.py +++ b/utils/memory_menager.py @@ -1,11 +1,67 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging logger = logging.getLogger('modiff') import torch import gc import time import nanoid +import os from utils.torch_utils import DEFAULT_DEVICE +GIB = 1024 ** 3 + + +def _tensor_bytes(tensor): + try: + return int(tensor.numel()) * int(tensor.element_size()) + except Exception: + return 0 + + +def _model_size_bytes(model): + """Count unique parameter/buffer storage across modules or pipelines.""" + seen_tensors = set() + seen_modules = set() + total = 0 + candidates = [model] + components = getattr(model, 'components', None) + if isinstance(components, dict): + candidates.extend(components.values()) + for candidate in candidates: + if not isinstance(candidate, torch.nn.Module) or id(candidate) in seen_modules: + continue + seen_modules.add(id(candidate)) + for tensor in [*candidate.parameters(recurse=True), *candidate.buffers(recurse=True)]: + identity = id(tensor) + if identity not in seen_tensors: + seen_tensors.add(identity) + total += _tensor_bytes(tensor) + return total + + +def _model_device(model): + direct = getattr(model, 'device', None) + if direct is not None: + return str(direct) + candidates = [model] + components = getattr(model, 'components', None) + if isinstance(components, dict): + candidates.extend(components.values()) + for candidate in candidates: + if isinstance(candidate, torch.nn.Module): + try: + return str(next(candidate.parameters()).device) + except (StopIteration, AttributeError): + continue + return 'cpu' + + +def _accelerator_reserve_bytes(total_bytes): + os_reserve = (600 if os.name == 'nt' else 400) * 1024 ** 2 + if os.name == 'nt' and total_bytes > 15 * GIB: + os_reserve += 100 * 1024 ** 2 + return int(0.8 * GIB) + os_reserve + def memory_flush(): gc.collect() @@ -43,6 +99,9 @@ def memory_flush(): class MemoryManager: def __init__(self): self.cache = {} + self.policy = str(os.environ.get('MODIFF_MODEL_CACHE_POLICY') or 'lru').strip().lower() + if self.policy not in {'lru', 'no_cache', 'high_ram'}: + self.policy = 'lru' def add(self, model, priority=1) -> str: if hasattr(model, '_mm_id'): @@ -57,8 +116,9 @@ def add(self, model, priority=1) -> str: 'model': model, 'priority': priority, 'last_used': time.time(), - #'size': 0 + 'size': _model_size_bytes(model), } + self._evict_system_ram_pressure(exclude_ids=[model_id]) return model_id def remove(self, model): @@ -84,6 +144,7 @@ def update(self, model_id, model=None, priority=None): if model is not None: self.cache[model_id]['model'] = model + self.cache[model_id]['size'] = _model_size_bytes(model) if priority is not None: self.cache[model_id]['priority'] = priority @@ -97,13 +158,13 @@ def get_model(self, model_id): return self.cache[model_id]['model'] - def load_model(self, model, device, exclude=[]): + def load_model(self, model, device, exclude=None): model_id = model if isinstance(model, str) else model._mm_id if hasattr(model, '_mm_id') else None if model_id is None or model_id not in self.cache: return None exclude_ids = [] - for v in exclude: + for v in (exclude or []): k = v if isinstance(v, str) else v._mm_id if hasattr(v, '_mm_id') else None if k and k in self.cache: exclude_ids.append(k) @@ -112,22 +173,31 @@ def load_model(self, model, device, exclude=[]): self.cache[model_id]['last_used'] = time.time() x = self.cache[model_id]['model'] - if str(x.device) == str(device): + if _model_device(x) == str(device): return x cache_priority = self._get_unload_candidates(device, exclude_ids) memory_flush() - # First we try to unload models based on the size if we have set it - # memory_required = self.cache[model_id]['size'] - # memory_available = torch.cuda.mem_get_info()[0] if 'cuda' in device else 0 - # if memory_available > 0 and memory_required > memory_available and all(v['size'] > 0 for v in self.cache.values()): - # while memory_required > memory_available and cache_priority: - # k = cache_priority.pop(0)[3] - # logger.debug(f"Unloading model {k} to free memory. Memory available: {memory_available}, memory required: {memory_required}") - # self.unload_model(k) - # memory_available = torch.cuda.mem_get_info()[0] + # Proactively evict by actual parameter bytes before relying on an OOM. + if str(device).startswith('cuda'): + try: + index = torch.device(device).index or 0 + free_bytes, total_bytes = torch.cuda.mem_get_info(index) + required = int(self.cache[model_id].get('size') or 0) + _accelerator_reserve_bytes(total_bytes) + while required > free_bytes and cache_priority: + candidate_id = cache_priority.pop(0)[2] + logger.debug( + "Evicting model %s before load: %d bytes free, %d bytes required", + candidate_id, + free_bytes, + required, + ) + self.unload_model(candidate_id) + free_bytes, _ = torch.cuda.mem_get_info(index) + except Exception: + logger.debug("Could not apply proactive accelerator eviction", exc_info=True) while True: try: @@ -165,29 +235,36 @@ def unload_model(self, model): def unload_all(self, device=None): device = device if device else DEFAULT_DEVICE for k, v in self.cache.items(): - if str(v['model'].device) == str(device): + if _model_device(v['model']) == str(device): self.unload_model(k) def clear(self): cleared = len(self.cache) - for model_id in list(self.cache.keys()): - try: - self.remove(model_id) - except Exception: - logger.debug(f"Failed to remove cached model {model_id}", exc_info=True) - self.cache.pop(model_id, None) + # Cleanup is discarding these objects, not keeping CPU-resident copies. + # Calling pipeline.to('cpu') here can transiently materialize an entire + # Accelerate-offloaded model in system RAM while its GPU allocation is + # still live. On unified-memory ROCm hosts that peak can OOM the server + # during a model-family switch. Drop every manager-owned reference + # atomically, let object destruction release hooks/storage, then flush + # allocator caches once. + records = list(self.cache.values()) + self.cache.clear() + for record in records: + record['model'] = None + del records memory_flush() return cleared - def exec(self, func, device, models=[], exclude=[], args=None, kwargs=None, inference_mode=True): + def exec(self, func, device, models=None, exclude=None, args=None, kwargs=None, inference_mode=True): exclude_ids = [] - for v in exclude: + for v in (exclude or []): k = v if isinstance(v, str) else v._mm_id if hasattr(v, '_mm_id') else None if k and k in self.cache: exclude_ids.append(k) # auto load the models, add them to the exclude list - for v in models: + active_models = list(models or []) + for v in active_models: k = v if isinstance(v, str) else v._mm_id if hasattr(v, '_mm_id') else None if k and k in self.cache: exclude_ids.append(k) @@ -199,29 +276,35 @@ def exec(self, func, device, models=[], exclude=[], args=None, kwargs=None, infe args = args or [] kwargs = kwargs or {} - while True: - try: - if inference_mode: - with torch.inference_mode(): + try: + while True: + try: + if inference_mode: + with torch.inference_mode(): + return func(*args, **kwargs) + else: return func(*args, **kwargs) - else: - return func(*args, **kwargs) - except torch.cuda.OutOfMemoryError as e: - # If we're out of memory, we need to unload a model. - if not cache_priority: - # If there are no more models to unload, we have failed. - logger.error(f"OOM during exec. No models left to unload to free memory.") + except torch.cuda.OutOfMemoryError as e: + # If we're out of memory, we need to unload a model. + if not cache_priority: + # If there are no more models to unload, we have failed. + logger.error("OOM during exec. No models left to unload to free memory.") + raise e + + # Unload the lowest-priority model. + k = cache_priority.pop(0)[2] + logger.debug(f"OOM during exec. Unloading model '{k}' to free VRAM.") + self.unload_model(k) + except Exception as e: + logger.error(f"An unexpected error occurred during exec: {e}") raise e + finally: + if self.policy == 'no_cache': + for active in active_models: + self.unload_model(active) + self._evict_system_ram_pressure(exclude_ids=exclude_ids) - # Unload the lowest-priority model. - k = cache_priority.pop(0)[2] - logger.debug(f"OOM during exec. Unloading model '{k}' to free VRAM.") - self.unload_model(k) - except Exception as e: - logger.error(f"An unexpected error occurred during exec: {e}") - raise e - - def _get_unload_candidates(self, device, exclude_ids=[]): + def _get_unload_candidates(self, device, exclude_ids=None): """ Gets a sorted list of models that are candidates for unloading from a device. The list is sorted by priority and last-used time, so the first element @@ -230,11 +313,43 @@ def _get_unload_candidates(self, device, exclude_ids=[]): cache_priority = [] for k, v in self.cache.items(): # Check if the model is on the target device and not in the exclude list - if str(v['model'].device) == str(device) and k not in exclude_ids: + if _model_device(v['model']) == str(device) and k not in (exclude_ids or []): cache_priority.append((v['priority'], v['last_used'], k)) # Sort by priority then last_used to find the best unload candidate cache_priority.sort(key=lambda x: (x[0], x[1])) return cache_priority + def _evict_system_ram_pressure(self, exclude_ids=None): + if self.policy == 'high_ram': + return [] + try: + from modiff.hardware import system_memory_snapshot + + memory = system_memory_snapshot() + available = memory.get('available_bytes') + total = memory.get('total_bytes') + floor = max(4 * GIB, int(total * 0.1)) if isinstance(total, int) else 4 * GIB + if not isinstance(available, int) or available >= floor: + return [] + except Exception: + return [] + excluded = set(exclude_ids or []) + candidates = sorted( + ( + (record['priority'], record['last_used'], model_id) + for model_id, record in self.cache.items() + if model_id not in excluded and _model_device(record['model']).startswith('cpu') + ), + key=lambda item: (item[0], item[1]), + ) + evicted = [] + while candidates and available < floor: + model_id = candidates.pop(0)[2] + self.remove(model_id) + evicted.append(model_id) + memory = system_memory_snapshot() + available = int(memory.get('available_bytes') or 0) + return evicted + memory_manager = MemoryManager() diff --git a/utils/paths.py b/utils/paths.py index 753c290..8aeee66 100644 --- a/utils/paths.py +++ b/utils/paths.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import os import re from modiff.config import CONFIG diff --git a/utils/quantization.py b/utils/quantization.py deleted file mode 100644 index 920f8d0..0000000 --- a/utils/quantization.py +++ /dev/null @@ -1,148 +0,0 @@ -def getQuantizationConfig(method, **kwargs): - #weights = kwargs.get('weights', None) - bnb_type = kwargs.get('bnb_type', '4bit') - dtype = kwargs.get('dtype', None) - - if method == 'bnb': - double_quant = kwargs.get('bnb_double_quant', True) - return getBnBConfig(bnb_type, dtype=dtype, double_quant=double_quant) - elif method == 'torchao': - quant_type = kwargs.get('torchao_quant_type', 'float8wo_e4m3') - return getTorchAOConfig(quant_type) - elif method == 'quanto': - weights = kwargs.get('quanto_type', 'float8') - return getQuantoConfig(weights) - - return None - -def getBnBConfig(bnb_type, dtype=None, double_quant=True): - from diffusers import BitsAndBytesConfig - - if bnb_type == '8bit': - config = BitsAndBytesConfig(load_in_8bit=True) - elif bnb_type == '4bit': - config = BitsAndBytesConfig(load_in_4bit=True, - bnb_4bit_quant_type="nf4", - bnb_4bit_use_double_quant=double_quant, - bnb_4bit_compute_dtype=dtype) - - return config - -def getTorchAOConfig(quant_type): - from diffusers import TorchAoConfig - - config = TorchAoConfig(quant_type=quant_type) - - return config - -def getQuantoConfig(weights): - from diffusers import QuantoConfig - - config = QuantoConfig(weights=weights) - - return config - -def quantize(model, method, **kwargs): - quanto_weights = kwargs.get('quanto_weights', 'qfloat8') - torchao_quant_type = kwargs.get('torchao_quant_type', 'float8wo_e4m3') - activations = kwargs.get('activations', None) - exclude = kwargs.get('quant_exclude', None) - - if method == 'torchao': - torchao(model, quant_type=torchao_quant_type) - elif method == 'quanto': - quanto(model, weights=quanto_weights, activations=activations, exclude=exclude) - - return model - -def torchao(model, quant_type): - from torchao.quantization import quantize_ - - supported_quant_types = [ - 'int4wo', 'int4dq', 'int8wo', 'int8dq', 'int8dq_int4w', - 'uint1wo', 'uint2wo', 'uint3wo', 'uint4wo', 'uint5wo', 'uint6wo', 'uint7wo', - 'float8wo_e5m2', 'float8wo_e4m3', 'float8dq_e4m3', 'float8dq_e4m3_tensor', 'float8dq_e4m3_row', - 'fp3_e1m1', 'fp3_e2m0', 'fp4_e1m2', 'fp4_e2m1', 'fp4_e3m0', 'fp5_e1m3', 'fp5_e2m2', - 'fp5_e3m1', 'fp5_e4m0', 'fp6_e1m4', 'fp6_e2m3', 'fp6_e3m2', 'fp6_e4m1', 'fp6_e5m0', - 'fp7_e1m5', 'fp7_e2m4', 'fp7_e3m3', 'fp7_e4m2', 'fp7_e5m1', 'fp7_e6m0' - ] - quant_type = quant_type if quant_type in supported_quant_types else 'float8wo_e4m3' - dtype = get_torchao_quant_method(quant_type) - quantize_(model, dtype()) - return model - -def quanto(model, weights, activations=None, exclude=None): - from optimum.quanto import freeze, quantize, qfloat8, qint8, qint4, qint2 - - weights_map = { 'float8': qfloat8, 'int8': qint8, 'int4': qint4, 'int2': qint2 } - weights = weights.lower() - weights = weights_map.get(weights, qfloat8) - - activations_map = { 'float8': qfloat8, 'int8': qint8 } - activations = activations_map.get(activations) if activations else None - - if exclude is None: - exclude = [] - - quantize(model, weights=weights, activations=activations, exclude=exclude) - freeze(model) - - return model - - -def get_torchao_quant_method(quant_type): - import torch - from functools import partial - from torchao.quantization import ( - float8_dynamic_activation_float8_weight, - float8_static_activation_float8_weight, - float8_weight_only, - fpx_weight_only, - int4_weight_only, - int4_dynamic_activation_int4_weight, - int8_dynamic_activation_int8_weight, - int8_weight_only, - uintx_weight_only, - ) - from torchao.quantization.observer import PerRow, PerTensor - - # Integer quantization - int_map = { - 'int4wo': int4_weight_only, - 'int4dq': int4_dynamic_activation_int4_weight, - 'int8wo': int8_weight_only, - 'int8dq': int8_dynamic_activation_int8_weight, - } - if quant_type in int_map: - return int_map[quant_type] - - # Floating point 8-bit quantization - float8_map = { - 'float8wo': float8_weight_only, - 'float8sq': float8_static_activation_float8_weight, - 'float8wo_e5m2': partial(float8_weight_only, weight_dtype=torch.float8_e5m2), - 'float8wo_e4m3': partial(float8_weight_only, weight_dtype=torch.float8_e4m3fn), - 'float8dq': float8_dynamic_activation_float8_weight, - 'float8dq_e4m3': partial(float8_dynamic_activation_float8_weight, activation_dtype=torch.float8_e4m3fn, weight_dtype=torch.float8_e4m3fn), - 'float8dq_e4m3_tensor': partial(float8_dynamic_activation_float8_weight, activation_dtype=torch.float8_e4m3fn, weight_dtype=torch.float8_e4m3fn, granularity=PerTensor()), - 'float8dq_e4m3_row': partial(float8_dynamic_activation_float8_weight, activation_dtype=torch.float8_e4m3fn, weight_dtype=torch.float8_e4m3fn, granularity=PerRow()) - } - if quant_type in float8_map: - return float8_map[quant_type] - - # Floating point X-bit quantization - import re - fp_match = re.match(r'fp(\d)_e(\d+)m(\d+)', quant_type) - if fp_match: - X, A, B = int(fp_match.group(1)), int(fp_match.group(2)), int(fp_match.group(3)) - if X == A + B + 1: - return partial(fpx_weight_only, A, B) - - # Unsigned integer quantization - uint_match = re.match(r'uint(\d)wo', quant_type) - if uint_match: - X = int(uint_match.group(1)) - dtype = getattr(torch, f'uint{X}', 'uint7') - return partial(uintx_weight_only, dtype=dtype) - - return None \ No newline at end of file diff --git a/utils/spline.py b/utils/spline.py deleted file mode 100644 index 66cafdf..0000000 --- a/utils/spline.py +++ /dev/null @@ -1,74 +0,0 @@ -import numpy as np - -def interpolate_nc_spline(control_points, num_steps=10): - """ - Calculates points along a natural cubic spline from control points. - - This function takes 0-1 normalized control points (where the origin is at - the bottom-left) and interpolates them to generate a smooth curve. - - Args: - control_points (list[dict]): [{'x': 0, 'y': 0}, ..., {'x': 1, 'y': 1}] - num_steps (int): The total number of points to generate for the spline. - - Returns: - list[tuple]: A list of (x, y) tuples representing the interpolated - points on the spline. - """ - if not control_points or len(control_points) < 2: - return [] - - # Sort points by x-coordinate - points = sorted(control_points, key=lambda p: p['x']) - - x = np.array([p['x'] for p in points]) - y = np.array([p['y'] for p in points]) - n = len(points) - 1 - - if np.any(np.diff(x) <= 0): - return [] - - # Direct translation of the JavaScript `calculateNaturalCubicSpline` - # function used on the client spline component. - - h = np.diff(x) - alpha = np.zeros(n) - for i in range(1, n): - alpha[i] = (3 / h[i]) * (y[i + 1] - y[i]) - (3 / h[i - 1]) * (y[i] - y[i - 1]) - - l = np.ones(n + 1) - mu = np.zeros(n + 1) - z = np.zeros(n + 1) - c = np.zeros(n + 1) - - for i in range(1, n): - l[i] = 2 * (x[i + 1] - x[i - 1]) - h[i - 1] * mu[i - 1] - mu[i] = h[i] / l[i] - z[i] = (alpha[i] - h[i - 1] * z[i - 1]) / l[i] - - c[n] = 0 - for j in range(n - 1, -1, -1): - c[j] = z[j] - mu[j] * c[j + 1] - - d = np.diff(c) / (3 * h) - b = (np.diff(y) / h) - h * (c[1:] + 2 * c[:-1]) / 3 - - # Evaluate the spline at the desired steps - spline_points = [] - x_out = np.linspace(x[0], x[-1], num_steps) - - for x_val in x_out: - # Find the correct spline segment for the given x_val - segment_index = np.searchsorted(x, x_val, side='right') - 1 - segment_index = max(0, min(segment_index, n - 1)) - - dx = x_val - x[segment_index] - y_val = ( - y[segment_index] + - b[segment_index] * dx + - c[segment_index] * dx**2 + - d[segment_index] * dx**3 - ) - spline_points.append((x_val, y_val)) - - return spline_points \ No newline at end of file diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 3e7315d..14c6156 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -1,3 +1,4 @@ +# Derived from cubiq/Mellon@5fd242921d13bff9fb03f4de405fdd39c2335e1f; modified by MoDiff. import logging try: diff --git a/uv.lock b/uv.lock deleted file mode 100644 index e6bf959..0000000 --- a/uv.lock +++ /dev/null @@ -1,3330 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.12" -resolution-markers = [ - "sys_platform == 'linux'", - "sys_platform == 'win32'", - "sys_platform != 'linux' and sys_platform != 'win32'", -] - -[[package]] -name = "accelerate" -version = "1.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "psutil" }, - { name = "pyyaml" }, - { name = "safetensors" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/14/787e5498cd062640f0f3d92ef4ae4063174f76f9afd29d13fc52a319daae/accelerate-1.13.0.tar.gz", hash = "sha256:d631b4e0f5b3de4aff2d7e9e6857d164810dfc3237d54d017f075122d057b236", size = 402835, upload-time = "2026-03-04T19:34:12.359Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl", hash = "sha256:cf1a3efb96c18f7b152eb0fa7490f3710b19c3f395699358f08decca2b8b62e0", size = 383744, upload-time = "2026-03-04T19:34:10.313Z" }, -] - -[[package]] -name = "aiofiles" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, -] - -[[package]] -name = "aiohappyeyeballs" -version = "2.6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, -] - -[[package]] -name = "aiohttp" -version = "3.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ee/ab/93ce242f899b68c51b0578c027aafa791ab3614cb9345fa5d37b5f5c8e3e/aiohttp-3.14.0.tar.gz", hash = "sha256:2882de819734c715fd1b9c11c97e09fa020d14438203d1d354d8ed1702791c9b", size = 7940674, upload-time = "2026-06-01T19:41:02.763Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/97/2b6889bfb6b6847520d50d95eb8c4307a45e28aaca39faf4a9454b3d1b2f/aiohttp-3.14.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b29518c9c2ec7e373e68259206a137c7f4f5439c58baaec4b5ab3ab799850a4e", size = 750194, upload-time = "2026-06-01T19:37:48.164Z" }, - { url = "https://files.pythonhosted.org/packages/21/e2/62634b7fff918ed98c3c6b2f0e70d520f7f28846cb412d451b04354c6459/aiohttp-3.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dbec68ce61b64cb73cab4d33df9433427b1713c8bcccb181dce695c1b6f8e87c", size = 506966, upload-time = "2026-06-01T19:37:50.014Z" }, - { url = "https://files.pythonhosted.org/packages/dd/fb/5ce075150828c797a5106f1c2fb26034e709d4289b9d2bf8b07f1e59fac6/aiohttp-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cdf534aa455593e589302990c5097aa5c92c06c4262a20da22934f9186a5fff", size = 507527, upload-time = "2026-06-01T19:37:51.96Z" }, - { url = "https://files.pythonhosted.org/packages/01/d5/405a0ae4e6b081754a3609c1c97c63a950e000a2def16046f1e736933a0e/aiohttp-3.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb6c657104393b5fbff01a5f59b2023db74058a8077d94475d6c25d03882a108", size = 1762420, upload-time = "2026-06-01T19:37:53.839Z" }, - { url = "https://files.pythonhosted.org/packages/ae/1d/e05a7c896b15a6bc6fb8fc5319eb437861c2c49c34559ef928add6590315/aiohttp-3.14.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:46fbbec4e4fab7428d4396a3823f9320e4560aa3113b89eeebce712c27c9ed5a", size = 1733672, upload-time = "2026-06-01T19:37:55.791Z" }, - { url = "https://files.pythonhosted.org/packages/cc/22/a72f7c459e195fa41bf4f7abd1f925b91fe91f8097e51c654229ba144a33/aiohttp-3.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c2c7e05dd5335b298085abf45ddf98673934c3ee1c083d0b9ea13d4186ad500", size = 1805064, upload-time = "2026-06-01T19:37:57.931Z" }, - { url = "https://files.pythonhosted.org/packages/80/50/e85bdaba0be59ca4838005ebfef4048fcdd5f35a02b07057a9a123394440/aiohttp-3.14.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3c7139100fbaae76515b73051d8f0aa3a3ff02e415eec8a8eee8e2223d9ba955", size = 1902125, upload-time = "2026-06-01T19:38:00.225Z" }, - { url = "https://files.pythonhosted.org/packages/19/d8/51de5c6b971c27bb1ef620293b8d1ca611ec78736b34b3f6ccf68e4c8785/aiohttp-3.14.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78d6f9286a629ce52728430afe18f8ed2b6c39a1fddb3802d7244b9983910ad2", size = 1783112, upload-time = "2026-06-01T19:38:02.641Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/b4402bfde77e43dfb1b6ccff83c7b7ab63ed06b50c4754f0c5423fb374fe/aiohttp-3.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3c3e12cdaeb92d7dcf13db00e9f6b1956b910e47256e696df1cfa946d02159", size = 1586356, upload-time = "2026-06-01T19:38:04.637Z" }, - { url = "https://files.pythonhosted.org/packages/bc/05/750a3265ca4dc54a460bd0cb1121a8f2ce9171fce4a135fb47ea7fd594d2/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d6a998191f5ebe3b8c28463ff72bc030250008b3193c402464efadd08b5ca02", size = 1723119, upload-time = "2026-06-01T19:38:06.713Z" }, - { url = "https://files.pythonhosted.org/packages/37/01/8c0812c50b3b1b1c37b323bf170d6be8847a8f234060485b7d1e71953f60/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0fc2b75ae8d169d853be2862d960be8550da6c5c65711d5476407eb3fdb006bd", size = 1757216, upload-time = "2026-06-01T19:38:08.736Z" }, - { url = "https://files.pythonhosted.org/packages/47/2a/50fb98028a26887cbe48dcc1df92a90825615bc73b5584301304090cded8/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:16eee56bcc72d04600bc56c1759982c2385ec0b41d3fd3521f836bf64a0957ef", size = 1770500, upload-time = "2026-06-01T19:38:11.111Z" }, - { url = "https://files.pythonhosted.org/packages/bd/32/0ffd598a2fa2b9a423daf242e700cfdabda35d6e602394ad9ae58972c1c7/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5a2e7ca615c3ddc15b82687e05a624e5f5cba3f1d6c20cb81172d70ea498451e", size = 1576224, upload-time = "2026-06-01T19:38:13.391Z" }, - { url = "https://files.pythonhosted.org/packages/0b/f9/b9fc381dd9b66afb33f2634c40e229d106467be0afcabe79648631ab6712/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0b7b8bbbec3ce9467ee0ebe334622fd90624f593edd3136c567811453fc4fae", size = 1794252, upload-time = "2026-06-01T19:38:15.498Z" }, - { url = "https://files.pythonhosted.org/packages/a8/fb/05d9214c975f23225a8cd5c439325e338c7c377b315480ef3871db51f54e/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ba10966d4f03dd96a14365be4b8e37c327c76f11c3ca867116966cdd9f98066", size = 1760193, upload-time = "2026-06-01T19:38:17.624Z" }, - { url = "https://files.pythonhosted.org/packages/d9/4b/02992fc4fb9e1b6673ee3f888a8e587a6447afda1f6f4aca776c148c2876/aiohttp-3.14.0-cp312-cp312-win32.whl", hash = "sha256:101df7779c80c0636014a6b2c6642acd3efb5b355d48347c9d7dfb720aee9430", size = 448650, upload-time = "2026-06-01T19:38:19.545Z" }, - { url = "https://files.pythonhosted.org/packages/39/e9/246532214c3abda518477cbaaf16d420295ad8effa5233844cbb38f299ab/aiohttp-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:b0a5747586d4467efd1f932710b269131c9717a872dce082cd92a00c1c13123a", size = 476145, upload-time = "2026-06-01T19:38:21.505Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c3/63f8c20090048915711598b0adf475b149216d736157961de06480a45b15/aiohttp-3.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:5f1c5be60add78fabb4aacd13c5a348ae79d2fcbfc7fa78da8f1eb192273b370", size = 444250, upload-time = "2026-06-01T19:38:24.027Z" }, - { url = "https://files.pythonhosted.org/packages/21/61/d11f7d9a3144bffe825247d6367cd93053666da50b94707c9129c78868d5/aiohttp-3.14.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:25400d710641a8040bf022a8a99f579e581ffa1c5bd42c33255d7d6f3957c127", size = 502399, upload-time = "2026-06-01T19:38:25.955Z" }, - { url = "https://files.pythonhosted.org/packages/4f/9b/a7e317625d36356844f8bb022cabd305b541f968856cc3c2e0b58e53ee6e/aiohttp-3.14.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:c5492b9929826e07cc3fcb9739ae87aab05dff6b5e67a9b73fd1700c6d008981", size = 510068, upload-time = "2026-06-01T19:38:27.828Z" }, - { url = "https://files.pythonhosted.org/packages/11/41/cc2d2cfbfbdc3126ba258f3cd27d1ac8a33492ae3c35a4583ee21f0ba7f1/aiohttp-3.14.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3366751d68d237c621264233a32f3078bbc21b7904ab90a77e03d21390c742c6", size = 481670, upload-time = "2026-06-01T19:38:29.836Z" }, - { url = "https://files.pythonhosted.org/packages/3c/07/381f4023c3b08cb616e520f566d8c58957abad54e56441d41fe67cfb0195/aiohttp-3.14.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:57ea07d28695a7a40304d42251892a8df765e5588c10ee32afeddcd5df33c0a2", size = 487591, upload-time = "2026-06-01T19:38:31.704Z" }, - { url = "https://files.pythonhosted.org/packages/fb/4d/4506fdb7a022bdf70011a3bbb4ca00c5c570026ef6a3c5bd7bc70c39089c/aiohttp-3.14.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:076cb014191ae2e65d949e1ad01f1dcfe33e32789b5172510f3e79c79fc04d50", size = 496503, upload-time = "2026-06-01T19:38:33.6Z" }, - { url = "https://files.pythonhosted.org/packages/ef/7d/c814111e04894a45d9e2defc94443879a6f118d9633d5fedfe6e2e8af5f0/aiohttp-3.14.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2f3fc37054564dee64a855b5b092d87ec35dcddfaabf7dacb1c8a2b1f83dc0a9", size = 745870, upload-time = "2026-06-01T19:38:36.013Z" }, - { url = "https://files.pythonhosted.org/packages/c6/ee/80eee0efddfe187e7cd05027086b7ce1c0e492e82a4eda58f5c5543a44a0/aiohttp-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8fcaef74d2ab0f607d7ff85a0d15e21bb5a258c4a58df1908396eb50d7f4ed3c", size = 505588, upload-time = "2026-06-01T19:38:38.282Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f8/0f28f04eef75d52fc9c715dde7ce9c0abb810fd20cfeb0fea7afd2ab1e98/aiohttp-3.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4c01b0bfc6209590960e68eac083cd22d5d87c21f974dd6208cafa5d3542bc8", size = 504492, upload-time = "2026-06-01T19:38:40.611Z" }, - { url = "https://files.pythonhosted.org/packages/ff/db/44c755232085545065c94378dfce38641b1aee647f4939fcd32f5b32e719/aiohttp-3.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f12eb7896e81caf403a2b18c9406426f1207361e7239c057ab29c076d4257e83", size = 1752111, upload-time = "2026-06-01T19:38:42.682Z" }, - { url = "https://files.pythonhosted.org/packages/5e/6a/42e030a46743841414402a3b00cd3d78419055e86c66fb5822c14b5abfc6/aiohttp-3.14.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6c79a044cacf360ec46738d863d2f41c9300d2a06ef4a7402ea0df306a350e61", size = 1729674, upload-time = "2026-06-01T19:38:44.79Z" }, - { url = "https://files.pythonhosted.org/packages/34/26/3199beb415202e3108e7b83ecebe10914d806d33fb9860c3e4aa60a19be3/aiohttp-3.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85e0675f47be4eff0636bf88c02140ea89168ae0df3ff1f3f464e9de9610d277", size = 1798808, upload-time = "2026-06-01T19:38:47.01Z" }, - { url = "https://files.pythonhosted.org/packages/bd/94/b9b6fcf0ee17c21d0d19fb8c22bf83ad18f82e702a9c3bd901a868f5e446/aiohttp-3.14.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b33e751cab03fdc960095b1e326cb5a03f5ee577d6ded59f3d1c100f8668882", size = 1891921, upload-time = "2026-06-01T19:38:49.233Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a3/3800dbd095cb2bb165a7ea5d94d790914677e27f45638c7d80e3f34c8945/aiohttp-3.14.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26d9224c6dd7f5c749aba4f61315a894601448b28d94d12f4dea0903e26d2096", size = 1777241, upload-time = "2026-06-01T19:38:52.04Z" }, - { url = "https://files.pythonhosted.org/packages/21/2a/45be91ad1b860508557448d4cc2e165a2ee68dd865657b73bf66cc5a00fb/aiohttp-3.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6281aecdf2732940f4fe06bd6adec5ae4d59b78b080b8e3a6b81467301010988", size = 1579554, upload-time = "2026-06-01T19:38:54.508Z" }, - { url = "https://files.pythonhosted.org/packages/b4/3d/dc94df99ed1511fdf28314f722643ed334112643cab00223577085e788c4/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:23e8314e7aed8576fbe33314d218bd81447a3adbc91dc36f1163bf583cd3084c", size = 1714864, upload-time = "2026-06-01T19:38:56.788Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e4/1f1c8acbb3acd5c8f795473b92c9c3d44eb60a5692c6104256c8a1c83a0c/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3b54fbff46127aeafdd764cecd0d99fa2f24a0e37ea5c18a7c3a4ac450df1db3", size = 1749803, upload-time = "2026-06-01T19:38:59.367Z" }, - { url = "https://files.pythonhosted.org/packages/0b/c8/c45ea6e7ed84cebba939b9c334498a045ba19d79c61b0110df5f21580de3/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b27d89af91a555f58e08e4902dbcbc48862fd40095720ca705990476bd93b7ac", size = 1765023, upload-time = "2026-06-01T19:39:01.651Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a1/a932941784432962fe390e1066823aaef64b4e5ac9fa595df57b5fe472a9/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:25d2326a4967bf705a9f9913a13005e93b6020ad8a9f6bd6bd78850d5171332e", size = 1571671, upload-time = "2026-06-01T19:39:04.044Z" }, - { url = "https://files.pythonhosted.org/packages/b0/01/e1280feac522597a4d46eb67a0cdfa053cfae263033030b761ab146f29fb/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a1d209375c503472b3c0a340cdf3c55fcd82e84b46dda7caeaced59faba373ec", size = 1789904, upload-time = "2026-06-01T19:39:06.294Z" }, - { url = "https://files.pythonhosted.org/packages/fa/10/ab28818262f4d26bdb47ed5f1fc7999b69e2fc6e0370b02d0f49011f45ea/aiohttp-3.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:666c7c5036df57b693026398b69b41874a1931ac5b3485fd910e57bfac253869", size = 1754516, upload-time = "2026-06-01T19:39:08.788Z" }, - { url = "https://files.pythonhosted.org/packages/af/cc/c122eabd7a1b7e0c9bbdd6be60e4715905b858399145d9df872bb94f1427/aiohttp-3.14.0-cp313-cp313-win32.whl", hash = "sha256:23f094a1ef64823fd35854ddf5c7a80a078162f37f9d2f7c6142b51a6affa456", size = 448656, upload-time = "2026-06-01T19:39:11.171Z" }, - { url = "https://files.pythonhosted.org/packages/41/a5/bab07d79848a00eedd8ed979ccb302aaea3ac6eb9fa16bd0ed87135869b4/aiohttp-3.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e03abdaa17d553f17e1d1d06bb266b3970106c78051d06795723e748d8e49d11", size = 475803, upload-time = "2026-06-01T19:39:13.439Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/f03ade8566c153666a3871afccbedf6d99911da006325e1fc6cf72a2de99/aiohttp-3.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:acdb400538cf4769543548bb5d1eb23d39bed4f96554a6078cb728c7cb2c268b", size = 443889, upload-time = "2026-06-01T19:39:15.945Z" }, - { url = "https://files.pythonhosted.org/packages/28/03/5f36ab196a88ba5e9648ae5643e6531e67a3a8c0e96f9c6510ff41540fec/aiohttp-3.14.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:363ef9e91014e7891679bfb2ac0a7c6ea93435dbbfd10ecf41b9f06fcf506c5f", size = 503330, upload-time = "2026-06-01T19:39:18.195Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ce/8b49ec2f30f68e02f314f4832186cd45e583360a5a386058be36855d23b6/aiohttp-3.14.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:884a4edbdad77be9d0ef36142c8b504351b170df0bf62b51e784fadabf311c42", size = 509822, upload-time = "2026-06-01T19:39:20.396Z" }, - { url = "https://files.pythonhosted.org/packages/1a/fe/6edbf5d39bf29322b6816365b17ed8ede4dace164a3aea1abcd30110eb78/aiohttp-3.14.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:70ea956f6cc4a37620966b56c2e205d88ca3e6d85ec063277e414b1035cddad3", size = 483329, upload-time = "2026-06-01T19:39:22.607Z" }, - { url = "https://files.pythonhosted.org/packages/1b/5a/fae531bdbc6456fb6241f46b7b81e4d8a0dd3fc09118a0055dc7141ac1ec/aiohttp-3.14.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:ea3b9806c89f61da22fddf1f12dd524fb368e5e28f1261fbdafe5c3cd8ce893b", size = 489502, upload-time = "2026-06-01T19:39:24.881Z" }, - { url = "https://files.pythonhosted.org/packages/36/f4/48a7b0414db7fed77a03d5dde34508c026afd83510ab6bca08c313855776/aiohttp-3.14.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a071be341c2bd9b0188e62d173509f024e0a35b1c342c53c50f8daaeda8c3bd8", size = 497357, upload-time = "2026-06-01T19:39:27.197Z" }, - { url = "https://files.pythonhosted.org/packages/75/75/e85a13a370acc007fca5feb1fd1b88ac2d8426e6dadd625479b7cadd55a3/aiohttp-3.14.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:198cfe61bf253b19da1fb3e0fa122249dc4f14c12709493fed8054aa0411cc76", size = 750898, upload-time = "2026-06-01T19:39:29.563Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e4/3d637f800c724eff0e2bed64df72557444482366fd0a35b0cec0e6968f6c/aiohttp-3.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9dc203d6ce6b9106d54e2a93f41dfdfebfbca2d99962ba503bfd3e5921a6549e", size = 506986, upload-time = "2026-06-01T19:39:31.872Z" }, - { url = "https://files.pythonhosted.org/packages/1d/df/35161f3598bf7501d2b2a805b41ab4f45a2e34150c421bcb4ef8c0d281a7/aiohttp-3.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9e19d17ab02bf16832a2c8c0d55a486792c5b1645665652ee9531aebcc30cb72", size = 508033, upload-time = "2026-06-01T19:39:34.137Z" }, - { url = "https://files.pythonhosted.org/packages/e5/39/b36e5d3d31e850fb4691dd3e941684ac490a2559249f6fa634b6b0fdf020/aiohttp-3.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d925fba0c14d5b498a8028b0107beebdfd16c5d48d702ff54f879cb017aaaca3", size = 1746213, upload-time = "2026-06-01T19:39:36.654Z" }, - { url = "https://files.pythonhosted.org/packages/b1/28/24e1409e605a9aa5d84abe0e2acb365354b70ae56d40948101cabe3341ab/aiohttp-3.14.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d33e61021222ce7f9792bcac870d6f58d8adfceda33ab857b01264f4560f2c5f", size = 1705862, upload-time = "2026-06-01T19:39:38.968Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d0/e5eb3ff1daeaf644c7e36a957517672494122628e067c38b263fa04eda77/aiohttp-3.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:44eca38755d0105bb32f47d085f5dd449846a449e1245fc105889e3279dcf8e3", size = 1798909, upload-time = "2026-06-01T19:39:41.334Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ba/8943f906f0570342886ababb9a722a44e360f786a028c5e0b0e29e3f735b/aiohttp-3.14.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f13087e06f68fea4941c21a0c541c00553aa16e4f8fd7bbe2b198df761e964d6", size = 1868892, upload-time = "2026-06-01T19:39:43.807Z" }, - { url = "https://files.pythonhosted.org/packages/3a/05/27df32c844b2156e1675a8d8ec22d963e3c8ba469ed7ceb1863320c7b521/aiohttp-3.14.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ff82be7f1ef73634cb77890a770743239bc3d487b848669be1c599889336dc0a", size = 1751659, upload-time = "2026-06-01T19:39:46.398Z" }, - { url = "https://files.pythonhosted.org/packages/7f/62/da182e5910ab912b2e88aa919b61a16046a37a95714a5795b02eb57b2d18/aiohttp-3.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a150c0875ac8fd87f1c398650841308a30d65facf7416b12dbdb9cfdcbe5a48c", size = 1578775, upload-time = "2026-06-01T19:39:48.902Z" }, - { url = "https://files.pythonhosted.org/packages/66/e3/53c67097e8a5ce98625e91e3fa7f43c9c6940de680345d03b3509a72a078/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:edc01ea4e1ec5a1649a28866262bf24195889ff7b27bdd947029a6086741de9b", size = 1710090, upload-time = "2026-06-01T19:39:51.392Z" }, - { url = "https://files.pythonhosted.org/packages/dd/55/0e2732ca598c7a4dfe8a775662376d0ca2977cb1030e48386d4da5d9a456/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:540632bf882ff8fc88f2e1697be0761578e89e0d79fb4a8a6d65dc5da7e729d4", size = 1715016, upload-time = "2026-06-01T19:39:53.807Z" }, - { url = "https://files.pythonhosted.org/packages/5a/96/f0b73730798c9ca525afc30b39f1f81bbe24e245d9654c54d3b39d63212d/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:860a86bc2c80237f5dff52edcf427e10a8d8352271fd84845429a3e60199e02c", size = 1763810, upload-time = "2026-06-01T19:39:56.31Z" }, - { url = "https://files.pythonhosted.org/packages/71/cc/11acb6c4518f448323405a7312b6f255d0f974a34373ad1db7633c4aadc8/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5cbd50e6a50d6b99283a826b18cbdebf65b0797689a7535cb0e9dd37be0f63c3", size = 1573064, upload-time = "2026-06-01T19:39:58.718Z" }, - { url = "https://files.pythonhosted.org/packages/de/2d/28c31dde0a7dc98c0ee7d0da2ddcec3f7688c4fc131e5989e278d0c03c0a/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:20144819e99db593e22bbd2f3f2691a5e149f879142d6b8670254708853ff4fb", size = 1775765, upload-time = "2026-06-01T19:40:01.195Z" }, - { url = "https://files.pythonhosted.org/packages/b8/69/155c4ef3aec96417d47024800472b33b16c5d8a665371dcd044c2afdf25d/aiohttp-3.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:26b6d79aa54cb4ed50cc7d41ed14e99e0f1fc8e7c2d42f2e05b37aea897b2b52", size = 1733716, upload-time = "2026-06-01T19:40:03.631Z" }, - { url = "https://files.pythonhosted.org/packages/5f/44/6126116fd8a316b712bb615660b855c78466bb67ba1bb1742427eafcf7ac/aiohttp-3.14.0-cp314-cp314-win32.whl", hash = "sha256:106ed074a856f3e21d186b8579e2c8afb6da598e267cdaab01059e13db2fc44d", size = 453684, upload-time = "2026-06-01T19:40:06.277Z" }, - { url = "https://files.pythonhosted.org/packages/a2/d7/eff4c58a88c5cac5e38b55f44fb8a6d3929c3cbd77356e383e094d3220bd/aiohttp-3.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f770846edae8f00ecc57af825bce811f787f87a7dcf0e90d191790efe5b31f7", size = 481758, upload-time = "2026-06-01T19:40:08.653Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ed/17b5bd9fbcb46e688f02e572f517754a9a75831e7b54702f027761dc4fa5/aiohttp-3.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:acf1581c4f21ed4b80a2dded504d87b055a071a84d5737ea966435f768275ac6", size = 450557, upload-time = "2026-06-01T19:40:11.03Z" }, - { url = "https://files.pythonhosted.org/packages/12/34/6180103ce9aabc8ebff3f7bb55a1228ffe60f61042823031d9692cb7b101/aiohttp-3.14.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6aa1a40f9cbb3da9f80714c5966b8946c21e6a2530d809b9498b33161e3c8733", size = 787878, upload-time = "2026-06-01T19:40:13.401Z" }, - { url = "https://files.pythonhosted.org/packages/92/e9/08954a40e8b7baa3d8beadd2b074b186e9b1e9c8ddabc288678a6265de50/aiohttp-3.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b62af5a8cc96a194eaa01a9ed7b34a3ffa58d3d8daaa1a0d7a749353ad12d228", size = 524400, upload-time = "2026-06-01T19:40:15.972Z" }, - { url = "https://files.pythonhosted.org/packages/08/6a/b5965a634ac4d5ba99a463314cf4ab214ca073fcdc38a15e0294273701fc/aiohttp-3.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6eb63b1417efaf7d1002a6ad034a40d44376afcc16508a57f8e74b49ad26a095", size = 527904, upload-time = "2026-06-01T19:40:18.28Z" }, - { url = "https://files.pythonhosted.org/packages/06/b4/932bcdd850c354d9bcca30f360e475d7852e30413fbbd44b182782ed5432/aiohttp-3.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c20b9ad156a79eb97be5cf9e069eec01d2f0dc8472ffbd75299a8b2d4c2cbbde", size = 1912162, upload-time = "2026-06-01T19:40:20.825Z" }, - { url = "https://files.pythonhosted.org/packages/c6/85/ce79bab0310d2e3fd2d7bc7e44412abeff7c8338f8a21dd0f2f1714989e5/aiohttp-3.14.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:40ae7b0642c25632c7eabc4a04754012691864d2a1b93becf7cddb76027b838a", size = 1778813, upload-time = "2026-06-01T19:40:23.726Z" }, - { url = "https://files.pythonhosted.org/packages/05/54/ba62ac2d1bc87e010aad23751e383b8794e45d931df67677313a2da78823/aiohttp-3.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95f5217e76a046b9f228a101717ef8d42b1eb3d9d196d15202db5bf41df88936", size = 1899969, upload-time = "2026-06-01T19:40:26.406Z" }, - { url = "https://files.pythonhosted.org/packages/dc/82/7cc7907725d83a19f31551334061e1ab8e108b1d7ac52632a2a844a4acb5/aiohttp-3.14.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1a4a9f17e85b80878c176695c1998c790e83731d8271881e5d356488652a1f9e", size = 1991771, upload-time = "2026-06-01T19:40:29.061Z" }, - { url = "https://files.pythonhosted.org/packages/d0/1c/a57de71a4508c93a830b77c28af3d08cd97f606dedfc6b94275347744508/aiohttp-3.14.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:145262119b07d7f95abc1839add35ba2bfc84551d4b4660ca11542c0b215455b", size = 1868606, upload-time = "2026-06-01T19:40:31.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ae/3839726cd49150a53ed340cc24ce5ba09d4c2117020ef9d45542bec5eb2f/aiohttp-3.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:49a33ded29b0b2fa7a367a02cf0fb89af602bb87542a16177ec8ce1c9c51d12a", size = 1665437, upload-time = "2026-06-01T19:40:35.01Z" }, - { url = "https://files.pythonhosted.org/packages/35/1e/c237923232c7da7f0392ea25d89fc5e60c0e93f685f4ebca8e7bcdd5271c/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2cc736a9c9fc2bc4dd71fd404815741b6573df27c3f985948ec4076989ac57de", size = 1834090, upload-time = "2026-06-01T19:40:37.733Z" }, - { url = "https://files.pythonhosted.org/packages/98/02/a5a7a2524f92d3911761b405a7c067c751891942144adc13e2ad79611e39/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b4141a3e5342ee3053a9cab54d25b64ed28289c1041e4c54b3d99839314d90ce", size = 1816907, upload-time = "2026-06-01T19:40:40.46Z" }, - { url = "https://files.pythonhosted.org/packages/fa/76/a8b9f0d09234d516af9f2d7dd715557f33b5da3b0b56ead41d1170e86e3c/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e30871b2d58996cb81aac52d2b1d15ac05257131ef0f90f18c2115a380fbfe7c", size = 1840382, upload-time = "2026-06-01T19:40:43.48Z" }, - { url = "https://files.pythonhosted.org/packages/c9/8e/140e715a0a4bbc211979ea30ec8396ad2ed5bf90ab87d8058fc4668b1923/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:667b881d083ccae3900ea5a241e17e5007ca78844c53ed389bb63d48f729d9c7", size = 1659497, upload-time = "2026-06-01T19:40:46.265Z" }, - { url = "https://files.pythonhosted.org/packages/10/c7/7ba5de8af9650b9767b063c675427b8685f43fa7ce563673a7bc3af60f08/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:b584dfe615d151e9b8f0a8ecb3aee6147f2927ec5b95ba25fe621f5377510928", size = 1870829, upload-time = "2026-06-01T19:40:49.583Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bc/2aaab2f85cadb26ea59c091fa2b8e370d625154b5c14b478f1b489d07551/aiohttp-3.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6199707cc40e0e9cd39c36fbc97bec416c704e1d0ddce03412bb3b3e6a90ccd0", size = 1832281, upload-time = "2026-06-01T19:40:52.303Z" }, - { url = "https://files.pythonhosted.org/packages/39/98/31b9ad9fbc01f0075ee7221002df5fd2d10b647f451ca5f30edc802d9dd6/aiohttp-3.14.0-cp314-cp314t-win32.whl", hash = "sha256:a8d93334d4961c9d566b1f046c81dee475b7c21eb730728d38237bfa70d1c8e6", size = 490597, upload-time = "2026-06-01T19:40:54.937Z" }, - { url = "https://files.pythonhosted.org/packages/59/1f/299b21441c8de42ff70fddc7cfe65e92f810abcf740739a09b56f7835364/aiohttp-3.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d2ffe9b614f50f069068b3b52e73414e4107fc10b7efc939a76acff9251fdd2", size = 525789, upload-time = "2026-06-01T19:40:57.306Z" }, - { url = "https://files.pythonhosted.org/packages/70/11/7f83fcba9ee05d4c54d61b3f8104da0d43a59adac44dd28effc0c9a10422/aiohttp-3.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:7a3fc4358e65826c515350f199c210de747cf669998211b1ee6c2e46de364b24", size = 467399, upload-time = "2026-06-01T19:40:59.993Z" }, -] - -[[package]] -name = "aiohttp-cors" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/d89e846a5444b3d5eb8985a6ddb0daef3774928e1bfbce8e84ec97b0ffa7/aiohttp_cors-0.8.1.tar.gz", hash = "sha256:ccacf9cb84b64939ea15f859a146af1f662a6b1d68175754a07315e305fb1403", size = 38626, upload-time = "2025-03-31T14:16:20.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/3b/40a68de458904bcc143622015fff2352b6461cd92fd66d3527bf1c6f5716/aiohttp_cors-0.8.1-py3-none-any.whl", hash = "sha256:3180cf304c5c712d626b9162b195b1db7ddf976a2a25172b35bb2448b890a80d", size = 25231, upload-time = "2025-03-31T14:16:18.478Z" }, -] - -[[package]] -name = "aiosignal" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozenlist" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, -] - -[[package]] -name = "albucore" -version = "0.0.24" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "opencv-python-headless" }, - { name = "simsimd" }, - { name = "stringzilla" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/13/69/d4cbcf2a5768bf91cd14ffef783520458431e5d2b22fbc08418d3ba09a88/albucore-0.0.24.tar.gz", hash = "sha256:f2cab5431fadf94abf87fd0c89d9f59046e49fe5de34afea8f89bc8390253746", size = 16981, upload-time = "2025-03-09T18:46:51.409Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/e2/91f145e1f32428e9e1f21f46a7022ffe63d11f549ee55c3b9265ff5207fc/albucore-0.0.24-py3-none-any.whl", hash = "sha256:adef6e434e50e22c2ee127b7a3e71f2e35fa088bcf54431e18970b62d97d0005", size = 15372, upload-time = "2025-03-09T18:46:50.177Z" }, -] - -[[package]] -name = "albumentations" -version = "2.0.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "albucore" }, - { name = "numpy" }, - { name = "opencv-python-headless" }, - { name = "pydantic" }, - { name = "pyyaml" }, - { name = "scipy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/f4/85eb56c3217b53bcfc2d12e840a0b18ca60902086321cafa5a730f9c0470/albumentations-2.0.8.tar.gz", hash = "sha256:4da95e658e490de3c34af8fcdffed09e36aa8a4edd06ca9f9e7e3ea0b0b16856", size = 354460, upload-time = "2025-05-27T21:23:17.415Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/64/013409c451a44b61310fb757af4527f3de57fc98a00f40448de28b864290/albumentations-2.0.8-py3-none-any.whl", hash = "sha256:c4c4259aaf04a7386ad85c7fdcb73c6c7146ca3057446b745cc035805acb1017", size = 369423, upload-time = "2025-05-27T21:23:15.609Z" }, -] - -[[package]] -name = "annotated-doc" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, -] - -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - -[[package]] -name = "anyio" -version = "4.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, -] - -[[package]] -name = "attrs" -version = "26.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, -] - -[[package]] -name = "beautifulsoup4" -version = "4.14.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, -] - -[[package]] -name = "bitsandbytes" -version = "0.49.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "packaging", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/71/acff7af06c818664aa87ff73e17a52c7788ad746b72aea09d3cb8e424348/bitsandbytes-0.49.2-py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:2fc0830c5f7169be36e60e11f2be067c8f812dfcb829801a8703735842450750", size = 31442815, upload-time = "2026-02-16T21:26:06.783Z" }, - { url = "https://files.pythonhosted.org/packages/19/57/3443d6f183436fbdaf5000aac332c4d5ddb056665d459244a5608e98ae92/bitsandbytes-0.49.2-py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:54b771f06e1a3c73af5c7f16ccf0fc23a846052813d4b008d10cb6e017dd1c8c", size = 60651714, upload-time = "2026-02-16T21:26:11.579Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d4/501655842ad6771fb077f576d78cbedb5445d15b1c3c91343ed58ca46f0e/bitsandbytes-0.49.2-py3-none-win_amd64.whl", hash = "sha256:2e0ddd09cd778155388023cbe81f00afbb7c000c214caef3ce83386e7144df7d", size = 55372289, upload-time = "2026-02-16T21:26:16.267Z" }, -] - -[[package]] -name = "certifi" -version = "2026.5.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, - { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, - { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, - { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, - { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, - { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, - { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, - { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, - { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, - { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, - { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, - { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, - { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, - { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, - { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, - { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, - { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, - { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, - { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, - { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, - { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, - { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, - { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, - { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, - { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, - { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, - { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, - { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, -] - -[[package]] -name = "click" -version = "8.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cuda-bindings" -version = "12.9.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cuda-pathfinder", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/45/557d4ed1fa54f0c7db8aee083229f624990d69f7d00f55477eed5c7e169a/cuda_bindings-12.9.7-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0666d3c082ef8f4b2d670950589373550e9f3bf564d635dd883f24a0b40402ff", size = 7071026, upload-time = "2026-05-27T18:44:13.356Z" }, - { url = "https://files.pythonhosted.org/packages/91/97/e3c6e58ece26a053419ba0a18444b5443cfc64451bbf37f84e8143b8bdca/cuda_bindings-12.9.7-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c7ef48c5e13ae90f3b2ecfb72f8e99ac43c8f4c43e67e1325b8aae331453687", size = 7611059, upload-time = "2026-05-27T18:44:15.252Z" }, - { url = "https://files.pythonhosted.org/packages/eb/7b/f1575e41e1a17dc2f2a408b2e8e864c9324e41e3e23f6401e5efc54c152a/cuda_bindings-12.9.7-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:266379e4942051f544a8e7ea1a30ead8d7e8199b6b30fcdc8917cae2bf614e61", size = 6978549, upload-time = "2026-05-27T18:44:18.839Z" }, - { url = "https://files.pythonhosted.org/packages/9d/dc/62d62eb4f91eb721bcf46da51b13e9872ccd8fa7e60eb8ba7b7baeac72c6/cuda_bindings-12.9.7-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59cf4a37b0d662ba15037c9ceebe1a306ebf2c01a8235a09be13cd07094fdb74", size = 7457675, upload-time = "2026-05-27T18:44:20.637Z" }, - { url = "https://files.pythonhosted.org/packages/f9/77/94d9b85f26add6fe9c9cb7c4ec3b96bc598f7ea5cfbd7490cc0a36adf5be/cuda_bindings-12.9.7-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dbcd4801954eb3508f4dc2fa0d0c8eb93eb3f45326fd61be2731418c371e7a0", size = 6870886, upload-time = "2026-05-27T18:44:24.164Z" }, - { url = "https://files.pythonhosted.org/packages/04/dd/3ec34b569e1b990b11276feba306bf8f446656cc38e8ed0f49b5facfeffa/cuda_bindings-12.9.7-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3747ea132642416786a8e31bf229032df3a7856911ae5426a7be53d032df183d", size = 7345663, upload-time = "2026-05-27T18:44:26.333Z" }, - { url = "https://files.pythonhosted.org/packages/68/e4/075052d42872cf8162da53f14447a4b8abc004c3750e4b724ee502428da0/cuda_bindings-12.9.7-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:775960ac9e530717f3b48e165cc6f68684fa9a4141764fd923e4c1a9820acc73", size = 7060090, upload-time = "2026-05-27T18:44:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/ec/cd/3289c810a4d45e5364a3387a74b4c9b6f6f57ee96ae0e5b537cc61dec242/cuda_bindings-12.9.7-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c47ec1a7a441d91aab32339951df7a1be53451121a12c094bba51467717a35a", size = 7504419, upload-time = "2026-05-27T18:44:31.992Z" }, - { url = "https://files.pythonhosted.org/packages/11/43/472a6281c3d94e71687e27c657a8f60718d3579b4d94c41deea503165f8a/cuda_bindings-12.9.7-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00a833d399b31071fab4cf3de2929840ae462dc4848116eeff033d09219e7116", size = 6899146, upload-time = "2026-05-27T18:44:35.556Z" }, - { url = "https://files.pythonhosted.org/packages/2b/13/10c1d0b32a9da65142d213e0733d748457fb3fd066aee4317335266f15c6/cuda_bindings-12.9.7-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11aeafa2b33995f890086b3fb0f062075176d956e9b6a6fe1a699dddc413f6ad", size = 7369087, upload-time = "2026-05-27T18:44:37.359Z" }, -] - -[[package]] -name = "cuda-pathfinder" -version = "1.5.5" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/c8/26f2e4aae92f11522a96043892ba39a90eac610d5242523aa863212bc1c7/cuda_pathfinder-1.5.5-py3-none-any.whl", hash = "sha256:0228c023f95d1480f143ef5c8922d27a2ab052087a942e81dc289c9eb8f91689", size = 51671, upload-time = "2026-05-27T01:21:25.413Z" }, -] - -[[package]] -name = "cuda-toolkit" -version = "12.8.1" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/c8/7dce3a0b15b42a3b58e7d96eb22a687d3bf2c44e01d149a6874629cd9938/cuda_toolkit-12.8.1-py2.py3-none-any.whl", hash = "sha256:adc7906af4ecbf9a352f9dca5734eceb21daec281ccfcf5675e1d2f724fc2cba", size = 2283, upload-time = "2025-08-13T02:03:07.842Z" }, -] - -[package.optional-dependencies] -cublas = [ - { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, -] -cudart = [ - { name = "nvidia-cuda-runtime-cu12", marker = "sys_platform == 'linux'" }, -] -cufft = [ - { name = "nvidia-cufft-cu12", marker = "sys_platform == 'linux'" }, -] -cufile = [ - { name = "nvidia-cufile-cu12", marker = "sys_platform == 'linux'" }, -] -cupti = [ - { name = "nvidia-cuda-cupti-cu12", marker = "sys_platform == 'linux'" }, -] -curand = [ - { name = "nvidia-curand-cu12", marker = "sys_platform == 'linux'" }, -] -cusolver = [ - { name = "nvidia-cusolver-cu12", marker = "sys_platform == 'linux'" }, -] -cusparse = [ - { name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" }, -] -nvjitlink = [ - { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, -] -nvrtc = [ - { name = "nvidia-cuda-nvrtc-cu12", marker = "sys_platform == 'linux'" }, -] -nvtx = [ - { name = "nvidia-nvtx-cu12", marker = "sys_platform == 'linux'" }, -] - -[[package]] -name = "cupy-cuda12x" -version = "14.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cuda-pathfinder", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/6e/290ee2d7cc4ad63d66e67acfd7ff3026f2b648dd04449a1bf88ffaa36b1e/cupy_cuda12x-14.1.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7aae7d3bed37985e2aa39f0914b88ad90dbd3a6141d3e8198d73fce65859013c", size = 144383812, upload-time = "2026-06-01T04:52:23.799Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6e/dc03c1ddc940f33b3d32803898e2fdae5c9538a2127a25f499494c84b183/cupy_cuda12x-14.1.1-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:a1138f20080489a46209291498cd12f792226d0a57d50c64a586c162a875a069", size = 133516927, upload-time = "2026-06-01T04:52:35.765Z" }, - { url = "https://files.pythonhosted.org/packages/cc/da/d4a8045b533af634bc791572e8c87981065e4a27b5d3e09d0d4d285742fd/cupy_cuda12x-14.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:85bebce86ffc25ecf31727b25da7b3793daf07b6fd9952704546af574d250988", size = 95238722, upload-time = "2026-06-01T04:52:46.296Z" }, - { url = "https://files.pythonhosted.org/packages/30/90/00fe874c47207b26c9b6ac950d0cecc533b4a145491641932df17e573f3c/cupy_cuda12x-14.1.1-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:afbb3d1fa9484b0ae20d76372c5939a8c5da327e3fc8711b77b2354566cac355", size = 143920086, upload-time = "2026-06-01T04:52:51.726Z" }, - { url = "https://files.pythonhosted.org/packages/89/a4/c46ff91dba0dbe2a0a557974faf4c090a3159d6e7296431ca6846038d047/cupy_cuda12x-14.1.1-cp313-cp313-manylinux2014_x86_64.whl", hash = "sha256:76ea35469e2aa0a8332b88f72505ea2f7871a0bc8f9b0c87184f57e47c9aa3bf", size = 133071615, upload-time = "2026-06-01T04:52:57.428Z" }, - { url = "https://files.pythonhosted.org/packages/ec/a0/46778424035ad3fc920d49471f079687a054f74d179142e9520014c2514e/cupy_cuda12x-14.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:64072f4139b44df38215f0519a6badc14138fa0e4bb5b2db44fe94d05f8b9c8b", size = 95219598, upload-time = "2026-06-01T04:53:02.774Z" }, - { url = "https://files.pythonhosted.org/packages/b6/99/d72336481264c3483b162ea128d58f80abb50009f1df82ca82905e0b8fd7/cupy_cuda12x-14.1.1-cp314-cp314-manylinux2014_aarch64.whl", hash = "sha256:22d0ff2755a7f29cb225d1d5fb979a73428c5534ea0bca91b0c02698e9948f84", size = 143788629, upload-time = "2026-06-01T04:53:09.404Z" }, - { url = "https://files.pythonhosted.org/packages/c7/77/c43a67e6809e03780d88caf690fa44a8b3152db2d8f848714bec327c9881/cupy_cuda12x-14.1.1-cp314-cp314-manylinux2014_x86_64.whl", hash = "sha256:1059581507343e7cf6231facce30932a195c7aad4fa7771d00e4a252683915a1", size = 132406367, upload-time = "2026-06-01T04:53:16.232Z" }, - { url = "https://files.pythonhosted.org/packages/7d/dc/96cd37de6da41239e02fc7f17e3364d60f99bd6816673d622916a06113ec/cupy_cuda12x-14.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:e707e0eceee174d323be21652e87bb97be982e6966b5dc241756307df42842aa", size = 95793971, upload-time = "2026-06-01T04:53:21.478Z" }, - { url = "https://files.pythonhosted.org/packages/20/c6/0ddec1be851de546e883ae3da5f03c1ea69738628b38234dce4362b5e38b/cupy_cuda12x-14.1.1-cp314-cp314t-manylinux2014_aarch64.whl", hash = "sha256:e09897636b7468a90efa1152109f0b19ba49ebc9a423d5dbd4682ed589e57843", size = 144093057, upload-time = "2026-06-01T04:53:28.647Z" }, - { url = "https://files.pythonhosted.org/packages/a4/80/5e05de89ba61df072aab6f8a6ee3ffeec57db68a0a456825b3b4ce608426/cupy_cuda12x-14.1.1-cp314-cp314t-manylinux2014_x86_64.whl", hash = "sha256:238080487174268d0f09770fe518de7c5b206527bef5c6792aef7ba0626a1c48", size = 132635338, upload-time = "2026-06-01T04:53:35.229Z" }, -] - -[[package]] -name = "dahuffman" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/71/a0733bd3a40213f42005bdc60a4fe066bb790100b89c56f47aadf74dc88c/dahuffman-0.4.2.tar.gz", hash = "sha256:e260e5279e4e4989bab325cc073db1810e914453e61d9210906fee57373e0130", size = 17183, upload-time = "2024-09-09T07:52:42.868Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/e7/b0beac9a3655219574d2b59cf71346f44d2d5064d9a2ab228ecb256d3069/dahuffman-0.4.2-py3-none-any.whl", hash = "sha256:37968b2102402206367298f62f5b11aa588d3d827ccdf52f475bfccaea2a1732", size = 18184, upload-time = "2024-09-09T07:52:40.098Z" }, -] - -[[package]] -name = "dfloat11" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "accelerate", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "dahuffman", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "huggingface-hub", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "safetensors", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "tqdm", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "transformers", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/b1/29a14bf8b4273cce70fe059b6a9be54afab7357cc093cec43b6fa6b0e8eb/dfloat11-0.5.0.tar.gz", hash = "sha256:ecc24c01d82eb88ee08d41082a13e4f694b141d46305ac7384e2bc200679c5ea", size = 27305, upload-time = "2025-08-24T20:15:46.152Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/a5/0a3010662584944616e2668474eaab2a4c00598ff8019e896f5143b54f6e/dfloat11-0.5.0-py3-none-any.whl", hash = "sha256:85e83fbeeb0865493a89e5ad3acb02cfa7aeba4c46f751ff8d4d6f42f808389a", size = 24203, upload-time = "2025-08-24T20:15:45.019Z" }, -] - -[package.optional-dependencies] -cuda12 = [ - { name = "cupy-cuda12x", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] - -[[package]] -name = "diffusers" -version = "0.39.0.dev0" -source = { git = "https://github.com/huggingface/diffusers#9b0818cf87413b4b9ca2501bf49406eed6d881af" } -dependencies = [ - { name = "filelock" }, - { name = "httpx" }, - { name = "huggingface-hub" }, - { name = "importlib-metadata" }, - { name = "numpy" }, - { name = "pillow" }, - { name = "regex" }, - { name = "requests" }, - { name = "safetensors" }, -] - -[[package]] -name = "easydict" -version = "1.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/9f/d18d6b5e19244788a6d09c14a8406376b4f4bfcc008e6d17a4f4c15362e8/easydict-1.13.tar.gz", hash = "sha256:b1135dedbc41c8010e2bc1f77ec9744c7faa42bce1a1c87416791449d6c87780", size = 6809, upload-time = "2024-03-04T12:04:41.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/ec/fa6963f1198172c2b75c9ab6ecefb3045991f92f75f5eb41b6621b198123/easydict-1.13-py3-none-any.whl", hash = "sha256:6b787daf4dcaf6377b4ad9403a5cee5a86adbc0ca9a5bcf5410e9902002aeac2", size = 6804, upload-time = "2024-03-04T12:04:39.508Z" }, -] - -[[package]] -name = "einops" -version = "0.8.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261, upload-time = "2026-01-26T04:13:17.638Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, -] - -[[package]] -name = "filelock" -version = "3.29.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/f9/f38573ed5844586db374d085911740a501ccfa373b455fc9413f09f85237/filelock-3.29.1.tar.gz", hash = "sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e", size = 59335, upload-time = "2026-06-03T15:19:04.053Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/a0/614c5fe402fd88951df45f4dda2fa3b4e17a99ecd92340771929169b3b95/filelock-3.29.1-py3-none-any.whl", hash = "sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b", size = 40750, upload-time = "2026-06-03T15:19:02.959Z" }, -] - -[[package]] -name = "flatbuffers" -version = "25.12.19" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" }, -] - -[[package]] -name = "frozenlist" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, - { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, - { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, - { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, - { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, - { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, - { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, - { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, - { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, - { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, - { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, - { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, - { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, - { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, - { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, - { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, - { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, - { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, - { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, - { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, - { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, - { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, - { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, - { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, - { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, - { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, - { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, - { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, - { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, - { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, - { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, - { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, - { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, - { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, - { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, - { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, - { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, - { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, - { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, - { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, - { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, - { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, -] - -[[package]] -name = "fsspec" -version = "2026.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d5/8d/1c51c094345df128ca4a990d633fe1a0ff28726c9e6b3c41ba65087bba1d/fsspec-2026.4.0.tar.gz", hash = "sha256:301d8ac70ae90ef3ad05dcf94d6c3754a097f9b5fe4667d2787aa359ec7df7e4", size = 312760, upload-time = "2026-04-29T20:42:38.635Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" }, -] - -[[package]] -name = "ftfy" -version = "6.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927, upload-time = "2024-10-26T00:50:35.149Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821, upload-time = "2024-10-26T00:50:33.425Z" }, -] - -[[package]] -name = "gdown" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, - { name = "filelock" }, - { name = "requests", extra = ["socks"] }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6a/b5/a45f62f20664031bf74a6aeb6f8d8cd5910e411bf90d756bd6b09bdc6c35/gdown-6.1.0.tar.gz", hash = "sha256:361c6e04c6ca335df50b9d71f40bcfe9ab70fb26a1b0e890a427267781389553", size = 269670, upload-time = "2026-05-30T11:56:21.322Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/56/a99f0f159cce5b26d267317d436afee184f45fc7911938757d7cbbd2d10c/gdown-6.1.0-py3-none-any.whl", hash = "sha256:38a36a94275b8272f684db469bbd73b4d1f64cbbc1751bcb993a1b2be8f013c8", size = 19216, upload-time = "2026-05-30T11:56:20.016Z" }, -] - -[[package]] -name = "gguf" -version = "0.19.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/48/ae/17f1308ae45cd7b08ebb521747d5b23f4efc4d172038a4e228dd5106c3ff/gguf-0.19.0.tar.gz", hash = "sha256:dbadcd6cc7ccd44256f2229fe7c2dff5e8aa5cf0612ab987fd2b1a57e428923f", size = 111220, upload-time = "2026-05-06T13:04:03.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/bb/d71d6da82763528c2c2ed6b59a9d6142c6595545a4c448e2085d155e88c2/gguf-0.19.0-py3-none-any.whl", hash = "sha256:70bcd10edfe697fb2dad6e40af2234b9d8ece9a41a99761405121ebda1c3c1cd", size = 118475, upload-time = "2026-05-06T13:04:02.588Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "hf-xet" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/d8/5c06fc76461418326a7decf8367480c35be11a41fd938633929c60a9ec6b/hf_xet-1.5.0.tar.gz", hash = "sha256:e0fb0a34d9f406eed88233e829a67ec016bec5af19e480eac65a233ea289a948", size = 837196, upload-time = "2026-05-06T06:18:15.583Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/9b/6912c99070915a4f28119e3c5b52a9abd1eec0ad5cb293b8c967a0c6f5a2/hf_xet-1.5.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:7d70fe2ce97b9db73b9c9b9c81fe3693640aec83416a966c446afea54acfae3c", size = 4023383, upload-time = "2026-05-06T06:17:53.947Z" }, - { url = "https://files.pythonhosted.org/packages/0f/6d/9563cfde59b5d8128a9c7ec972a087f4c782e4f7bac5a85234edfd5d5e49/hf_xet-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:73a0dae8c71de3b0633a45c73f4a4a5ed09e94b43441d82981a781d4f12baa42", size = 3792751, upload-time = "2026-05-06T06:17:51.791Z" }, - { url = "https://files.pythonhosted.org/packages/07/a5/ed5a0cf35b49a0571af5a8f53416dad1877a718c021c9937c3a53cb45781/hf_xet-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a60290ec57e9b71767fba7c3645ddafdd0759974b540441510c629c6db6db24a", size = 4456058, upload-time = "2026-05-06T06:17:40.735Z" }, - { url = "https://files.pythonhosted.org/packages/60/fb/3ae8bf2a7a37a4197d0195d7247fd25b3952e15cb8a599e285dfaa6f52b3/hf_xet-1.5.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e5de0f6deada0dada870bb376a11bcd1f08abf3a968a6d118f33e72d1b1eb480", size = 4250783, upload-time = "2026-05-06T06:17:38.412Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9b/8bae40d4d91525085137196e84eb0ed49cf65b5e96e5c3ecdadd8bd0fac2/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c799d49f1a5544a0ef7591c0ee75e0d6b93d6f56dc7a4979f59f7518d2872216", size = 4445594, upload-time = "2026-05-06T06:18:04.219Z" }, - { url = "https://files.pythonhosted.org/packages/13/59/c74efbbd4e8728172b2cc72a2bc014d2947a4b7bdced932fbd3f5da1a4e5/hf_xet-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2baea1b0b989e5c152fe81425f7745ddc8901280ba3d97c98d8cdece7b706c60", size = 4663995, upload-time = "2026-05-06T06:18:06.1Z" }, - { url = "https://files.pythonhosted.org/packages/73/32/8e1e0410af64cda9b139d1dcebdc993a8ff9c8c7c0e2696ae356d75ccc0d/hf_xet-1.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:526345b3ed45f374f6317349df489167606736c876241ba984105afe7fd4839d", size = 3966608, upload-time = "2026-05-06T06:18:19.74Z" }, - { url = "https://files.pythonhosted.org/packages/fc/34/a8febc8f4edbea8b3e21b02ebc8b628679b84ba7e45cde624a7736b51500/hf_xet-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:786d28e2eb8315d5035544b9d137b4a842d600c434bb91bf7d0d953cce906ad4", size = 3796946, upload-time = "2026-05-06T06:18:17.568Z" }, - { url = "https://files.pythonhosted.org/packages/2a/20/8fc8996afe5815fa1a6be8e9e5c02f24500f409d599e905800d498a4e14d/hf_xet-1.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:872d5601e6deea30d15865ede55d29eac6daf5a534ab417b99b6ef6b076dd96c", size = 4023495, upload-time = "2026-05-06T06:18:01.94Z" }, - { url = "https://files.pythonhosted.org/packages/32/6a/93d84463c00cecb561a7508aa6303e35ee2894294eac14245526924415fe/hf_xet-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9929561f5abf4581c8ea79587881dfef6b8abb2a0d8a51915936fc2a614f4e73", size = 3792731, upload-time = "2026-05-06T06:18:00.021Z" }, - { url = "https://files.pythonhosted.org/packages/9d/5a/8ec8e0c863b382d00b3c2e2af6ded6b06371be617144a625903a6d562f4b/hf_xet-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7b7bbae318e583a86fb21e5a4a175d6721d628a2874f4bd022d0e660c32a682", size = 4456738, upload-time = "2026-05-06T06:17:49.574Z" }, - { url = "https://files.pythonhosted.org/packages/c5/ca/f7effa1a67717da2bcc6b6c28f71c6ca648c77acaec4e2c32f40cbe16d85/hf_xet-1.5.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cf7b2dc6f31a4ea754bb50f74cde482dcf5d366d184076d8530b9872787f3761", size = 4251622, upload-time = "2026-05-06T06:17:47.096Z" }, - { url = "https://files.pythonhosted.org/packages/65/f2/19247dba3e231cf77dec59ddfb878f00057635ff773d099c9b59d37812c3/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8dbcbab554c9ef158ef2c991545c3e970ddd8cc7acdcd0a78c5a41095dab4ded", size = 4445667, upload-time = "2026-05-06T06:18:11.983Z" }, - { url = "https://files.pythonhosted.org/packages/7f/64/6f116801a3bcfb6f59f5c251f48cadc47ea54026441c4a385079286a94fa/hf_xet-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5906bf7718d3636dc13402914736abe723492cb730f744834f5f5b67d3a12702", size = 4664619, upload-time = "2026-05-06T06:18:13.771Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e8/069542d37946ed08669b127e1496fa99e78196d71de8d41eda5e9f1b7a58/hf_xet-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5f3dc2248fc01cc0a00cd392ab497f1ca373fcbc7e3f2da1f452480b384e839e", size = 3966802, upload-time = "2026-05-06T06:18:28.162Z" }, - { url = "https://files.pythonhosted.org/packages/f9/91/fc6fdec27b14d04e88c386ac0a0129732b53fa23f7c4a78f4b83a039c567/hf_xet-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b285cea1b5bab46b758772716ba8d6854a1a0310fed1c249d678a8b38601e5a0", size = 3797168, upload-time = "2026-05-06T06:18:26.287Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fb/69ff198a82cae7eb1a69fb84d93b3a3e4816564d76817fe541ddc96874eb/hf_xet-1.5.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dad0dc84e941b8ba3c860659fe1fdc35c049d47cce293f003287757e971a8f56", size = 4030814, upload-time = "2026-05-06T06:17:57.933Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ff/edcc2b40162bef3ff78e14ab637e5f3b89243d6aee72f5949d3bb6a5af83/hf_xet-1.5.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6e5a9b0fdac4ed03ed45ef79254a655b1aaab514a02202617fbf643f5fdf7a", size = 3798444, upload-time = "2026-05-06T06:17:55.79Z" }, - { url = "https://files.pythonhosted.org/packages/49/4d/103f76b04310e5e57656696cc184690d20c466af0bca3ca88f8c8ea5d4f3/hf_xet-1.5.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3531b1823a0e6d77d80f9ed15ca0e00f0d115094f8ac033d5cae88f4564cc949", size = 4465986, upload-time = "2026-05-06T06:17:44.886Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a2/546f47f464737b3edbab6f8ddb57f2599b93d2cbb66f06abb475ccb48651/hf_xet-1.5.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a0ee58cd18d5ea799f7ed11290bbccbe56bdd8b1d97ca74b9cc49a3945d7a3b", size = 4259865, upload-time = "2026-05-06T06:17:42.639Z" }, - { url = "https://files.pythonhosted.org/packages/95/7f/1be593c1f28613be2e196473481cd81bfc5910795e30a34e8f744f6cac4f/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e60df5a42e9bed8628b6416af2cba4cba57ae9f02de226a06b020d98e1aab18", size = 4459835, upload-time = "2026-05-06T06:18:08.026Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b2/703569fc881f3284487e68cda7b42179978480da3c438042a6bbbb4a671c/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4b35549ce62601b84da4ff9b24d970032ace3d4430f52d91bcbb26c901d6c690", size = 4672414, upload-time = "2026-05-06T06:18:09.864Z" }, - { url = "https://files.pythonhosted.org/packages/af/37/1b6def445c567286b50aa3b33828158e135b1be44938dde59f11382a500c/hf_xet-1.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:2806c7c17b4d23f8d88f7c4814f838c3b6150773fe339c20af23e1cfaf2797e4", size = 3977238, upload-time = "2026-05-06T06:18:23.621Z" }, - { url = "https://files.pythonhosted.org/packages/62/94/3b66b148778ee100dcfd69c2ca22b57b41b44d3063ceec934f209e9184ce/hf_xet-1.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:b6c9df403040248c76d808d3e047d64db2d923bae593eb244c41e425cf6cd7be", size = 3806916, upload-time = "2026-05-06T06:18:21.7Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "huggingface-hub" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "filelock" }, - { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "httpx" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "tqdm" }, - { name = "typer" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/65/9826515abb600b5722bcf53f8b4a2fb58340b1f8bfcaee19f83561c13a44/huggingface_hub-1.17.0.tar.gz", hash = "sha256:fad842b6763ef70ebc3919665b1b9273645203185400a7d6c5eddc2323cc3435", size = 797082, upload-time = "2026-05-28T15:12:13.347Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/28/d7cef5e477b855c25d415b8f57e5bc7347c7a90cad3acf1725d0c92ca294/huggingface_hub-1.17.0-py3-none-any.whl", hash = "sha256:3b8156d23118e87f6a587648bfbc04f04a12a757ccb4ed298b35c4ae638bf24c", size = 671546, upload-time = "2026-05-28T15:12:11.441Z" }, -] - -[[package]] -name = "idna" -version = "3.18" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, -] - -[[package]] -name = "imageio" -version = "2.37.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "pillow" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/fa/391e437a34e55095173dca5f24070d89cbc233ff85bf1c29c93248c6588d/imageio-2.37.3-py3-none-any.whl", hash = "sha256:46f5bb8522cd421c0f5ae104d8268f569d856b29eb1a13b92829d1970f32c9f0", size = 317646, upload-time = "2026-03-09T11:31:10.771Z" }, -] - -[[package]] -name = "imageio-ffmpeg" -version = "0.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/bd/c3343c721f2a1b0c9fc71c1aebf1966a3b7f08c2eea8ed5437a2865611d6/imageio_ffmpeg-0.6.0.tar.gz", hash = "sha256:e2556bed8e005564a9f925bb7afa4002d82770d6b08825078b7697ab88ba1755", size = 25210, upload-time = "2025-01-16T21:34:32.747Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl", hash = "sha256:9d2baaf867088508d4a3458e61eeb30e945c4ad8016025545f66c4b5aaef0a61", size = 24932969, upload-time = "2025-01-16T21:34:20.464Z" }, - { url = "https://files.pythonhosted.org/packages/40/5c/f3d8a657d362cc93b81aab8feda487317da5b5d31c0e1fdfd5e986e55d17/imageio_ffmpeg-0.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b1ae3173414b5fc5f538a726c4e48ea97edc0d2cdc11f103afee655c463fa742", size = 21113891, upload-time = "2025-01-16T21:34:00.277Z" }, - { url = "https://files.pythonhosted.org/packages/33/e7/1925bfbc563c39c1d2e82501d8372734a5c725e53ac3b31b4c2d081e895b/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1d47bebd83d2c5fc770720d211855f208af8a596c82d17730aa51e815cdee6dc", size = 25632706, upload-time = "2025-01-16T21:33:53.475Z" }, - { url = "https://files.pythonhosted.org/packages/a0/2d/43c8522a2038e9d0e7dbdf3a61195ecc31ca576fb1527a528c877e87d973/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c7e46fcec401dd990405049d2e2f475e2b397779df2519b544b8aab515195282", size = 29498237, upload-time = "2025-01-16T21:34:13.726Z" }, - { url = "https://files.pythonhosted.org/packages/a0/13/59da54728351883c3c1d9fca1710ab8eee82c7beba585df8f25ca925f08f/imageio_ffmpeg-0.6.0-py3-none-win32.whl", hash = "sha256:196faa79366b4a82f95c0f4053191d2013f4714a715780f0ad2a68ff37483cc2", size = 19652251, upload-time = "2025-01-16T21:34:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/2c/c6/fa760e12a2483469e2bf5058c5faff664acf66cadb4df2ad6205b016a73d/imageio_ffmpeg-0.6.0-py3-none-win_amd64.whl", hash = "sha256:02fa47c83703c37df6bfe4896aab339013f62bf02c5ebf2dce6da56af04ffc0a", size = 31246824, upload-time = "2025-01-16T21:34:28.6Z" }, -] - -[[package]] -name = "importlib-metadata" -version = "9.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - -[[package]] -name = "jsonschema" -version = "4.26.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, -] - -[[package]] -name = "kernels" -version = "0.15.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, - { name = "kernels-data" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "tomlkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/85/b4/32f2aaf2e6d6b89477ecaf27b7e3aa22edfa9ccd98c705fb0a9b69773cab/kernels-0.15.2.tar.gz", hash = "sha256:5af4fab4a2d4eb67f01522dffabfdc3a4bcfc167ce68f7549c0d2828a73cbc19", size = 65065, upload-time = "2026-06-03T08:55:42.937Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/9d/1fc63a78fec897de35bc822dfd388985804a72040c75db811bf67dd9b8fe/kernels-0.15.2-py3-none-any.whl", hash = "sha256:2a66497d94152896b74c841089e3ee685708b8ddc21d4f4751a0505a0b693c91", size = 58698, upload-time = "2026-06-03T08:55:41.608Z" }, -] - -[[package]] -name = "kernels-data" -version = "0.15.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/2d/0c7a70c65aea2a614b75d20141b7622711dd5b000d99c4ac2ed6d06a661d/kernels_data-0.15.2.tar.gz", hash = "sha256:ebcf3c4c09a979d76ea5abf5e5132f8c055ff6afd7464510f84d65b14f5c33b9", size = 40664, upload-time = "2026-06-03T08:42:52.597Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/53/1176203e9dc429fd032407a7f9420a65e2b0502197ae5e486673368ec356/kernels_data-0.15.2-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:3117bb218ebabe13cce70448bc6d6ba268d2bf82577580e357573d7cdf97b4ca", size = 1056480, upload-time = "2026-06-03T08:42:31.808Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/3637f6fe7114fa469dd2facc38a224b6bc4b3e02cc77a43598c6d01c956c/kernels_data-0.15.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b2549df9b48eace960d27240581c904f83d2e27a875e86702937d683f6f76cec", size = 1018268, upload-time = "2026-06-03T08:42:27.909Z" }, - { url = "https://files.pythonhosted.org/packages/47/13/b480a346fee6247638bf322289c2459114a3541101345e2f556d230f0ac3/kernels_data-0.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8a32f6c7475e74946af05a0d11b0b28f11a433970d18264707438131b7514da", size = 1152787, upload-time = "2026-06-03T08:42:04.096Z" }, - { url = "https://files.pythonhosted.org/packages/0a/db/fc560cfbdb9dd4834f4b6fa090f280678866b46b9359b0124b902e1d90d3/kernels_data-0.15.2-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:65ff66c4c0c92e3735b6e5d29c2a80ae9ffa50f43ec998a41e5dab1db68e2bb9", size = 1096657, upload-time = "2026-06-03T08:42:08.246Z" }, - { url = "https://files.pythonhosted.org/packages/9a/da/e431a014f78123b9f8e18611710bd64339492b44eadd6375ff5b5b2f4fab/kernels_data-0.15.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86cd170868c0460958603c6d2c7e0b418a841f279207ab5db4bdf6c59f69648d", size = 1402489, upload-time = "2026-06-03T08:42:12.331Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ee/112bc7c2c7c8004693213e81b25fbe1e80412c0f3da887daa28b764d9ef7/kernels_data-0.15.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c383cb26ab712eccdcf654e7f063a6bb8f0a50d2b3b44eaaa064325fe54e4943", size = 1213311, upload-time = "2026-06-03T08:42:16.228Z" }, - { url = "https://files.pythonhosted.org/packages/cf/53/f5844aec451fa3fa791eceda03ffda0f1d23a13157f99e30e6ab8bee5411/kernels_data-0.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d67d8c97a65478f66e0aa6fabe149ec6c6b622336c880e9802c8f7369c591f", size = 1162763, upload-time = "2026-06-03T08:42:23.729Z" }, - { url = "https://files.pythonhosted.org/packages/aa/08/c1f290dd3b04b23a8efe56806b3ea5230918c14dd61efe86e218006d750d/kernels_data-0.15.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59e3612e2d42fa5dbf08bab0e7bbfe5621510c581d21def5e387aee99a889d76", size = 1195553, upload-time = "2026-06-03T08:42:20.194Z" }, - { url = "https://files.pythonhosted.org/packages/61/0d/810efcf0a7bfac5f1decce8925e434ddd8d01ebc4434a2cccb9134bf0c02/kernels_data-0.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:45a9e6682b2c0f850d44425ff09a7475fe5b899c26fb7491ca64866e7d278b70", size = 1328720, upload-time = "2026-06-03T08:42:36.635Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7e/6458109290952e654bebef03397df888017e8fab5ce6ac4d37db82fd3ce3/kernels_data-0.15.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e5bb4bfa8ac6a274a8b52580554a883b1d0907d44cbabe3c07e5f15cd3f4adee", size = 1373439, upload-time = "2026-06-03T08:42:40.378Z" }, - { url = "https://files.pythonhosted.org/packages/b8/2f/c1258b0b253365202122a98aed8aee1d889a6e81f47f991ba6fdeb226d4e/kernels_data-0.15.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ca06d4e4e68ae6ef522a65c7fe41790247b6012172bf3eab67524115a62b1836", size = 1377222, upload-time = "2026-06-03T08:42:44.497Z" }, - { url = "https://files.pythonhosted.org/packages/bc/6d/51e923892bcc487f4ccd8d8263f8d02855bf3911d5bab409c35dabe79cf3/kernels_data-0.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9a44817aa1c1dc84e4831c831f74139ce868cc826fe0492fc19a09c3bf8d2197", size = 1401080, upload-time = "2026-06-03T08:42:48.387Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d0/5a05cc2fb2253c40da0039de19b502efb8ff8adc93df62a9884d1366cfc9/kernels_data-0.15.2-cp313-cp313t-win32.whl", hash = "sha256:c015869855f8f669d12398af165174bf9a330a894d304e67e6a83f4a55611a9d", size = 830093, upload-time = "2026-06-03T08:42:59.227Z" }, - { url = "https://files.pythonhosted.org/packages/ca/6f/b5e44999bf1f2f4a976a56d80e32cf052912d093ab317c21f8e8ada0e9ab/kernels_data-0.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:a21a0783e5add117fa0297f5a1941385c79b493ba389b8bc9c1fbc9a1e54ce5b", size = 919439, upload-time = "2026-06-03T08:42:55.498Z" }, - { url = "https://files.pythonhosted.org/packages/3e/bd/e422270929e28d216291e2ce022958dc687e400cf3e4b72d0e060d2d727b/kernels_data-0.15.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfdb76a05cf2985effedb1e8cbd65ab4427ec4672bafc54a4318d02227a59cc4", size = 1054589, upload-time = "2026-06-03T08:42:33.704Z" }, - { url = "https://files.pythonhosted.org/packages/62/a5/30a08094a97e55b82cb4edfc17ab9c07413de2c032ab9249642efcb61fff/kernels_data-0.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f09b4b342d9f913aca6885c4d24c4a9ab52eeeb23eb607681b6d67ecc53f9ed8", size = 1017277, upload-time = "2026-06-03T08:42:29.086Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c8/b2cf68adc55f8f138e5aedc706cd8efb48f1a25569f1eb19e1271d774985/kernels_data-0.15.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ecdde2b57c4859fdee6123213b7714b781ddb4849efb9bc4e3735f0b4818dc9", size = 1151862, upload-time = "2026-06-03T08:42:05.59Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e1/ae92198d5bf8e845e67a78b0be7afdd1e8f3ad3e23e8a21e3d61fafd2cc1/kernels_data-0.15.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:264f729de696ba1b9a8e59b3cb3322d73e6dc58b2af0836ddb40ff8b6606246c", size = 1096580, upload-time = "2026-06-03T08:42:09.726Z" }, - { url = "https://files.pythonhosted.org/packages/5c/70/cb2480efe03ce99185bd042baeec1b9e636641c851ad73225af9930d655e/kernels_data-0.15.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c27eb307d717cb8a42d468804c9bc96733b6c27c1564dc3d3fcb512d9e5a1f9", size = 1401009, upload-time = "2026-06-03T08:42:13.503Z" }, - { url = "https://files.pythonhosted.org/packages/69/a7/5f382c59be517c09a8b0eb3ddca0cd8dd39aab384dc48bd77f9a20909c08/kernels_data-0.15.2-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce8728d315a63f715761405918409d1896db937969f27fe315d6b6b5e038c9d", size = 1212207, upload-time = "2026-06-03T08:42:17.595Z" }, - { url = "https://files.pythonhosted.org/packages/49/ac/bb525a818f7e27fa35fddd6938991a8c84ccd45fa86ab9a3c4adcb4e0dd7/kernels_data-0.15.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94b18cebe97f15a9413fa55118c84b9f1f6ee24706640c1dbc50b6bdcc9d6bc9", size = 1160318, upload-time = "2026-06-03T08:42:25.043Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/20fd160b3a98b028712e87776919704567919cd4be191e08952e95418ee6/kernels_data-0.15.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:620470e39cbc7bfe6228f84420beaa3d94209be803f857b42e485dc019f8b04d", size = 1195056, upload-time = "2026-06-03T08:42:21.364Z" }, - { url = "https://files.pythonhosted.org/packages/06/24/6b831d5e02582a71580ea76c1fd5a6a11b2dc88af2224c96455eda3eb7ee/kernels_data-0.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d78d8440f9d84be7ffb0162bf9bd2cfc968557a6beecfb41ad5199f7c1f66a91", size = 1328310, upload-time = "2026-06-03T08:42:37.888Z" }, - { url = "https://files.pythonhosted.org/packages/97/74/e3044910eaaaf6704349c5c755956ee251293ee997a0b212bb18734e2d2e/kernels_data-0.15.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:058ee10c66dffdc4e2c69638d38c8ee2473d86b5368eafef71691520e3ec9f3b", size = 1372982, upload-time = "2026-06-03T08:42:41.605Z" }, - { url = "https://files.pythonhosted.org/packages/27/3b/7a06acb1f27676e7a44b1e176bb8587d3b74b3414eb0413525649cddb2a0/kernels_data-0.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4cc009c809bd00e80f137db1be19ace8dd07b085488f4a2a01e0a3a8acf9c9e8", size = 1377371, upload-time = "2026-06-03T08:42:45.784Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6a/4af487456903eaef45808be6480ae083a75361f475b95ba33a46be57c50a/kernels_data-0.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:681cbb6d79f0f2f82c63d81bf499a485fd4de55c37d6cdcfb6a762c6f2a7c516", size = 1399163, upload-time = "2026-06-03T08:42:50.287Z" }, - { url = "https://files.pythonhosted.org/packages/a6/1f/92a12c777cded9e367895fdfd3c671d094e52e22400973258fd17b2302a5/kernels_data-0.15.2-cp314-cp314t-win32.whl", hash = "sha256:d966f9eb8dbbdc1f6322c059ba5086db0d24f2db256ebfe19b0ce1e784343290", size = 828863, upload-time = "2026-06-03T08:43:00.351Z" }, - { url = "https://files.pythonhosted.org/packages/ac/36/6af8a121c417b7d908344047782374162d3759ec1231ebbb6e8da23ddfc7/kernels_data-0.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:118e2f863cf54449c41b89b4319811edc0bcb239df2881d5c1ea71b6963ccf08", size = 918183, upload-time = "2026-06-03T08:42:56.731Z" }, - { url = "https://files.pythonhosted.org/packages/0b/56/4f7c0ea48b549d9f909cd5402381b69dc819b365a3371c8a80e1751e923b/kernels_data-0.15.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:91e076598618509ac6890ff9d60f77b401475ef7139ec5da7633f631dc8539cf", size = 1066526, upload-time = "2026-06-03T08:42:35.07Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d5/12f9cf1ddb895185f848ce07ba5ec6a714a9501d2db24ac31bf2307a94c3/kernels_data-0.15.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:25beae2142db44e209c68d94f74836c8117815819659431e258fd2a54b7e97b1", size = 1026975, upload-time = "2026-06-03T08:42:30.346Z" }, - { url = "https://files.pythonhosted.org/packages/56/5b/e8ecd09c70bbc7d9a054f6186aa07174b6b64d5329c75f6b17cf462e8ca1/kernels_data-0.15.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b86058fa14927b07f21ee936adcf646df0a2e699491fb2370a36c5492eed6ac", size = 1157076, upload-time = "2026-06-03T08:42:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/b9/1c/c552510d3ab8e2626edf6fa56362fbb48f96cd180e43cdf4c8669e894535/kernels_data-0.15.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a994db23172ea47f675e2c70b48fa6a5978bd724f67a3aa985489a76ce19b41c", size = 1106023, upload-time = "2026-06-03T08:42:11.111Z" }, - { url = "https://files.pythonhosted.org/packages/f7/81/068c657ec609ec7c4be8c5f3897e11e62653d6775329eadc0bf9b6f6b034/kernels_data-0.15.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2ce752431c91d7d4cbdff7562637ee7f2fee4ba5fe56774bc8c3ebcfac2ac9d", size = 1405880, upload-time = "2026-06-03T08:42:14.97Z" }, - { url = "https://files.pythonhosted.org/packages/81/b1/6a6cbca4a12f98f4f3f5870f0726996673bd32649d7604ca82a965bcff9e/kernels_data-0.15.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe7766831cf0d730ac88ae1263938c336ac197e3be362e92dce5358ac8652930", size = 1218481, upload-time = "2026-06-03T08:42:18.835Z" }, - { url = "https://files.pythonhosted.org/packages/58/c4/68b443fcfcaf4b2f46a5c72b79a359c2927f88cdac3b6074905bf492b6bd/kernels_data-0.15.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28c9a087236d3aece45c52f06db35c6926fa574f5b92a8f727e85b9fe722c399", size = 1169185, upload-time = "2026-06-03T08:42:26.455Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5c/c64f93af9b67d1c5607e44f2c095c6d293721346ee673f3d873024529645/kernels_data-0.15.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8bee9024d9923d8c76b1d862679e485602d244a0aa6527c4fd2e3bec734e417", size = 1203878, upload-time = "2026-06-03T08:42:22.542Z" }, - { url = "https://files.pythonhosted.org/packages/cc/3d/09d957112b073a34d82d3e4e539dacf93204c2f63b25909d802b2070eac8/kernels_data-0.15.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a573b5dec8700a46a4b37917e46de47c9963ab6fc1c103ae597529e6d4ddf0be", size = 1333215, upload-time = "2026-06-03T08:42:39.012Z" }, - { url = "https://files.pythonhosted.org/packages/88/a2/8cc41a9352bd4d59729f5ffb857502da0de511783a2c39e16cb8e0cd2059/kernels_data-0.15.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ff33fc5fd656cf92c12d521b18f1eaf4c593c1fc29cce7cd07451bb784608c10", size = 1379837, upload-time = "2026-06-03T08:42:43.204Z" }, - { url = "https://files.pythonhosted.org/packages/90/8d/c39581fca66db563834f371c86b1736aaf9ab5513078b856dc1c8ea6a539/kernels_data-0.15.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:6cfb95db3217d9c36b4b5d2d47988d7e6d07a02863b9f99916db435eb40b95f0", size = 1384927, upload-time = "2026-06-03T08:42:47.026Z" }, - { url = "https://files.pythonhosted.org/packages/7e/fc/9e9e24fcb25c333bc02af548d65d40f63c6ad0406a3d4f7bbb3785be2d0f/kernels_data-0.15.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ddd627f85d05ff0b49cd88c7fcfe80f6a8b3b261bd5d38d20f4484e60c6110c2", size = 1408204, upload-time = "2026-06-03T08:42:51.456Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b0/a7a23299141189d9cc8b19b32daf469a36c239e5d81984f32c5545421ea8/kernels_data-0.15.2-cp38-abi3-win32.whl", hash = "sha256:540eaa3a114f4b2543cd35bd2ca68ba2206674b4c4653c21fe933fd54f266cb4", size = 833610, upload-time = "2026-06-03T08:43:02.084Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ac/46e882bc31b6235567e07fdde12403b7cc0eef4edd17309e07bebebf650e/kernels_data-0.15.2-cp38-abi3-win_amd64.whl", hash = "sha256:96a37e7cec2f2650905da03a18606b05f79bdf58571e7b6fcdc0795ec0b0f3f8", size = 922258, upload-time = "2026-06-03T08:42:57.985Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a5/9c9d883b40c84e85d6484955d84c1cf61cd5ba6ad63f02d427a84cf57717/kernels_data-0.15.2-cp38-abi3-win_arm64.whl", hash = "sha256:69aed5b4cfe345626a02f76e32e595b335176bffbe5c4742bdcff58d1cc556d0", size = 863789, upload-time = "2026-06-03T08:42:53.382Z" }, -] - -[[package]] -name = "kornia" -version = "0.8.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "kornia-rs" }, - { name = "packaging" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1a/80/460e9dad0c1b68f3e83f198f3983638d62f910a88542b492290010d25f50/kornia-0.8.3.tar.gz", hash = "sha256:c06887374eaf39fb614a77ca054383e6cc2092cb7225e45437111b4984d0f866", size = 727384, upload-time = "2026-05-19T20:23:35.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/05/0c1cc486e87d2a5ad6649eb96a8ccaa7c6002d8d73d676c13e2102f286c1/kornia-0.8.3-py3-none-any.whl", hash = "sha256:0b15f5d359aeafd7ff54ea631ed1943a3eb295c4a6dae3f745ddeada25e33289", size = 1189381, upload-time = "2026-05-19T20:23:33.209Z" }, -] - -[[package]] -name = "kornia-rs" -version = "0.1.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/0f/cd6985790031ad2f0d4fcdfdb9763a177bebb970edddc6fe29f9e6afd902/kornia_rs-0.1.14.tar.gz", hash = "sha256:7584f654a9db2b41bee05c9aaf865608b665e2f7195096372e001b6f220de1d2", size = 2556658, upload-time = "2026-05-19T07:45:06.366Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/e3/e57480f38e395262616afd8efe91d0a1f7a4b875b52c9890e31aa85a057c/kornia_rs-0.1.14-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:76faf5389b1ea53452fc08561622ccad8ce81c8ff1857c4742be6ae4e82bf078", size = 3555313, upload-time = "2026-05-19T07:46:01.714Z" }, - { url = "https://files.pythonhosted.org/packages/e4/4f/2935c5186cce45bfa85b587883d627627427bceb61d0b0e0aa0267339910/kornia_rs-0.1.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0025db9854f3a34c66123c2646d52e71a534678d9343f3c897192136b2c3ddaf", size = 3349262, upload-time = "2026-05-19T07:45:47.689Z" }, - { url = "https://files.pythonhosted.org/packages/d1/f5/dc5e8f69130de7ed8d59500789bc0cf1658ada8d5360164e416622cb47f1/kornia_rs-0.1.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747b26a3ce0cad76aa1047ed65f95dcd649286a2d5417d8ad93f03bb1909238d", size = 3381293, upload-time = "2026-05-19T07:45:10.552Z" }, - { url = "https://files.pythonhosted.org/packages/72/7f/01c9456a09a3a5731bf986724f6f6ff70d627ac8072cf298d842ec204692/kornia_rs-0.1.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:396f84661fcf260885c3f9db717caf6904eafd44857dca17be09a835bd7da8d9", size = 3695565, upload-time = "2026-05-19T07:45:30.01Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6f/01e0e2cf90c47ecf26656263cdabe491a1b206c94c0c30a1dd7e4d13ed29/kornia_rs-0.1.14-cp312-cp312-win_amd64.whl", hash = "sha256:ac4bbd0a8fd73b5058a39707c790fecec4c5204a42d1f5af17f1fa57cc83d406", size = 3367565, upload-time = "2026-05-19T07:46:16.682Z" }, - { url = "https://files.pythonhosted.org/packages/ca/16/c0a4e602b0ae106dcae9ee94d115f348c7b385f175831b54acf56d886a47/kornia_rs-0.1.14-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a703ec79a33b76115386dfef02fd36bed17715a1209fed858dd0c1adf7482421", size = 3554935, upload-time = "2026-05-19T07:46:03.239Z" }, - { url = "https://files.pythonhosted.org/packages/dc/fa/63b541cd864bdaab443cc402751f22d881e5219e409627b7cf3cbf6e737b/kornia_rs-0.1.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ea26534e04f937f2f4d445e12dcbf0c291c4afbb91b3d659b03c1841b0a445d7", size = 3349072, upload-time = "2026-05-19T07:45:49.185Z" }, - { url = "https://files.pythonhosted.org/packages/32/f2/ebb0584fe11a93b4f92fbd2638c0476999003e3501557c26ceef51a5b9da/kornia_rs-0.1.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f0312afdaf27fb4579d07fdf6b457b2c75e1323a4d3b1d5812a86fef0a2316e", size = 3381025, upload-time = "2026-05-19T07:45:12.785Z" }, - { url = "https://files.pythonhosted.org/packages/f2/09/3f78df732325132a3f8fceb0059c1e4736bb48e4fca8acea7d3de93ad15f/kornia_rs-0.1.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65ba9214fc10cca816b7f6653f59a2bb74f343dce163adceba10926480d7a2b6", size = 3694754, upload-time = "2026-05-19T07:45:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/32/76/c6f5d9dbbad23c36fa5ecbbf64bce253680080de928df85f21a9ec8dfd3e/kornia_rs-0.1.14-cp313-cp313-win_amd64.whl", hash = "sha256:4d3312002012fd0189e762b62b24d882e97e4ea9fe3a3834f01d7e17e911201c", size = 3366940, upload-time = "2026-05-19T07:46:18.703Z" }, - { url = "https://files.pythonhosted.org/packages/88/41/c88fc43775e77d2e8c134f996c8962fdc51987307b604f50a3d2298f4836/kornia_rs-0.1.14-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:45866a0691ecb491a6af3c779b25fd76dc65792710070d0673181a7f9dc38a08", size = 3543237, upload-time = "2026-05-19T07:46:04.733Z" }, - { url = "https://files.pythonhosted.org/packages/78/7f/ad46cdd7b24006914f8f50d907e0e64cdf2231f4dffce253f90785eda132/kornia_rs-0.1.14-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2b329376d01a03e5a76a381efaaafa6fe1e54a5932eace1de95760564643ca4d", size = 3339920, upload-time = "2026-05-19T07:45:50.824Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fb/62ab6d4b582eaf12ac991c1bdb94f2211bd7bbeb59c04108af9e9456904e/kornia_rs-0.1.14-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:603f56ffa0ffe2de50e5c3c4c606e5a37c98c0277a2ad752feac0e25920880f4", size = 3377444, upload-time = "2026-05-19T07:45:14.587Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a8/9f6dec9c5b78ff76dca0fd63c6f3c96b8d00e2a475fcfadd7eeee0c65729/kornia_rs-0.1.14-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496301c800afea6867220d0f02344f44a90b50c1da22d5511c25df0c0c2b4d75", size = 3686869, upload-time = "2026-05-19T07:45:33.734Z" }, - { url = "https://files.pythonhosted.org/packages/15/e7/ff8d97f922679c0f8f3aec6b27bab98c03c02feb2200543aed2e98b4b09d/kornia_rs-0.1.14-cp313-cp313t-win_amd64.whl", hash = "sha256:29cfb7b179ba0b98772bd459f6e74da67f93b290491a5c03deb9197955dfa684", size = 3359930, upload-time = "2026-05-19T07:46:20.207Z" }, - { url = "https://files.pythonhosted.org/packages/d5/49/093a3d42d52c6d5b1850f0564b82f9801575b818515a4dfbc9f82d2a7459/kornia_rs-0.1.14-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:816dd1d1713b13f3b39831d20097cb2aa69c2863c9a98555b1b32df0e5b9e309", size = 3555445, upload-time = "2026-05-19T07:46:06.481Z" }, - { url = "https://files.pythonhosted.org/packages/04/df/344d1c7e36c2cca38227b29693554c8dd1112e5b03155c5fae5fd9122df6/kornia_rs-0.1.14-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:27b23edda1f847ee4532ae2f008b16da535b947e2cb261be1865f7faff6c9fe7", size = 3349363, upload-time = "2026-05-19T07:45:52.348Z" }, - { url = "https://files.pythonhosted.org/packages/58/89/a90c7ebe73715ec5d326cf5f215f430f32fb5f208a0f7db7bc04f523d7e6/kornia_rs-0.1.14-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1f5798b209c5e0cd6ec2629aac5b70c2b7c6c628a432a1b6a7414aca5805f9d", size = 3381572, upload-time = "2026-05-19T07:45:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/ed/05/ffd6ae5b5cdddcbf9f7b7940d408c38911b8ab3911148b5b114522410ff1/kornia_rs-0.1.14-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a70df2ce65269de1f1e9c1fbe14e1fb2cdda6c3a39a31621b68a09cdba01d", size = 3695025, upload-time = "2026-05-19T07:45:35.916Z" }, - { url = "https://files.pythonhosted.org/packages/b0/96/56c15bd73c5ea7f242e6e03001b46db31c99fb7b287455dc36d1273f1b39/kornia_rs-0.1.14-cp314-cp314-win_amd64.whl", hash = "sha256:ff5ab2ede8eee7c05c6b55318ca96118785c40e9320e30c3fbb7f2b68b6fbe2b", size = 3366738, upload-time = "2026-05-19T07:46:21.693Z" }, - { url = "https://files.pythonhosted.org/packages/28/51/db8b3b72289a72c71a29a8d0ae683bd2f307c0b6f5d534744bdad0fd0183/kornia_rs-0.1.14-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7726f27690cf471e8df967d71ee6c937adce764a0de0fea02aeac216b71770fc", size = 3543838, upload-time = "2026-05-19T07:46:07.958Z" }, - { url = "https://files.pythonhosted.org/packages/78/45/adc8d6642c4cda15525b50d40fae79bf80db91a12237be824cbd67059726/kornia_rs-0.1.14-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7835328d5bf1565c42ce405db125811653a207c6e5dc16e937cf4527a04d8710", size = 3341040, upload-time = "2026-05-19T07:45:53.867Z" }, - { url = "https://files.pythonhosted.org/packages/b1/62/f3855e36213a8815ae933238b68f67c7435d7f76d36a1f08da3b8a6517a3/kornia_rs-0.1.14-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cb1a72ea7ce13a2971af16f28409c080560aa332431b3552c633197316e0869", size = 3377938, upload-time = "2026-05-19T07:45:19.273Z" }, - { url = "https://files.pythonhosted.org/packages/63/56/408f1fa7308c5ea8b3d7f5338d121580ef692f7e32858ee35f6969c158b6/kornia_rs-0.1.14-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dc1942acd2e6cbf28f1e056518db751264550f9aaa61760ce01ede266e42b61", size = 3687771, upload-time = "2026-05-19T07:45:37.952Z" }, - { url = "https://files.pythonhosted.org/packages/22/0b/bb6d1904f92a4f1d05edb4ac39513fd7a3babc48c889bd137f09d4fd5668/kornia_rs-0.1.14-cp314-cp314t-win_amd64.whl", hash = "sha256:26b13fbf0a22c133a1957defca8460faceeb22c7ce1ab37a6f4a658944682c58", size = 3360061, upload-time = "2026-05-19T07:46:23.27Z" }, -] - -[[package]] -name = "lazy-loader" -version = "0.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, -] - -[[package]] -name = "llvmlite" -version = "0.47.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz", hash = "sha256:62031ce968ec74e95092184d4b0e857e444f8fdff0b8f9213707699570c33ccc", size = 193614, upload-time = "2026-03-31T18:29:53.497Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/48/4b7fe0e34c169fa2f12532916133e0b219d2823b540733651b34fdac509a/llvmlite-0.47.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:306a265f408c259067257a732c8e159284334018b4083a9e35f67d19792b164f", size = 37232769, upload-time = "2026-03-31T18:28:43.735Z" }, - { url = "https://files.pythonhosted.org/packages/e6/4b/e3f2cd17822cf772a4a51a0a8080b0032e6d37b2dbe8cfb724eac4e31c52/llvmlite-0.47.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5853bf26160857c0c2573415ff4efe01c4c651e59e2c55c2a088740acfee51cd", size = 56275178, upload-time = "2026-03-31T18:28:48.342Z" }, - { url = "https://files.pythonhosted.org/packages/b6/55/a3b4a543185305a9bdf3d9759d53646ed96e55e7dfd43f53e7a421b8fbae/llvmlite-0.47.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:003bcf7fa579e14db59c1a1e113f93ab8a06b56a4be31c7f08264d1d4072d077", size = 55128632, upload-time = "2026-03-31T18:28:52.901Z" }, - { url = "https://files.pythonhosted.org/packages/2f/f5/d281ae0f79378a5a91f308ea9fdb9f9cc068fddd09629edc0725a5a8fde1/llvmlite-0.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:f3079f25bdc24cd9d27c4b2b5e68f5f60c4fdb7e8ad5ee2b9b006007558f9df7", size = 38138692, upload-time = "2026-03-31T18:28:57.147Z" }, - { url = "https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a3c6a735d4e1041808434f9d440faa3d78d9b4af2ee64d05a66f351883b6ceec", size = 37232771, upload-time = "2026-03-31T18:29:01.324Z" }, - { url = "https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2699a74321189e812d476a43d6d7f652f51811e7b5aad9d9bba842a1c7927acb", size = 56275178, upload-time = "2026-03-31T18:29:05.748Z" }, - { url = "https://files.pythonhosted.org/packages/d6/da/b32cafcb926fb0ce2aa25553bf32cb8764af31438f40e2481df08884c947/llvmlite-0.47.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c6951e2b29930227963e53ee152441f0e14be92e9d4231852102d986c761e40", size = 55128632, upload-time = "2026-03-31T18:29:11.235Z" }, - { url = "https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2e9adf8698d813a9a5efb2d4370caf344dbc1e145019851fee6a6f319ba760e", size = 38138695, upload-time = "2026-03-31T18:29:15.43Z" }, - { url = "https://files.pythonhosted.org/packages/1c/d4/33c8af00f0bf6f552d74f3a054f648af2c5bc6bece97972f3bfadce4f5ec/llvmlite-0.47.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:de966c626c35c9dff5ae7bf12db25637738d0df83fc370cf793bc94d43d92d14", size = 37232773, upload-time = "2026-03-31T18:29:19.453Z" }, - { url = "https://files.pythonhosted.org/packages/64/1d/a760e993e0c0ba6db38d46b9f48f6c7dceb8ac838824997fb9e25f97bc04/llvmlite-0.47.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ddbccff2aeaff8670368340a158abefc032fe9b3ccf7d9c496639263d00151aa", size = 56275176, upload-time = "2026-03-31T18:29:24.149Z" }, - { url = "https://files.pythonhosted.org/packages/84/3b/e679bc3b29127182a7f4aa2d2e9e5bea42adb93fb840484147d59c236299/llvmlite-0.47.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a7b778a2e144fc64468fb9bf509ac1226c9813a00b4d7afea5d988c4e22fca", size = 55128631, upload-time = "2026-03-31T18:29:29.536Z" }, - { url = "https://files.pythonhosted.org/packages/be/f7/19e2a09c62809c9e63bbd14ce71fb92c6ff7b7b3045741bb00c781efc3c9/llvmlite-0.47.0-cp314-cp314-win_amd64.whl", hash = "sha256:694e3c2cdc472ed2bd8bd4555ca002eec4310961dd58ef791d508f57b5cc4c94", size = 39153826, upload-time = "2026-03-31T18:29:33.681Z" }, - { url = "https://files.pythonhosted.org/packages/40/a1/581a8c707b5e80efdbbe1dd94527404d33fe50bceb71f39d5a7e11bd57b7/llvmlite-0.47.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:92ec8a169a20b473c1c54d4695e371bde36489fc1efa3688e11e99beba0abf9c", size = 37232772, upload-time = "2026-03-31T18:29:37.952Z" }, - { url = "https://files.pythonhosted.org/packages/11/03/16090dd6f74ba2b8b922276047f15962fbeea0a75d5601607edb301ba945/llvmlite-0.47.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa1cbd800edd3b20bc141521f7fd45a6185a5b84109aa6855134e81397ffe72b", size = 56275178, upload-time = "2026-03-31T18:29:42.58Z" }, - { url = "https://files.pythonhosted.org/packages/f5/cb/0abf1dd4c5286a95ffe0c1d8c67aec06b515894a0dd2ac97f5e27b82ab0b/llvmlite-0.47.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6725179b89f03b17dabe236ff3422cb8291b4c1bf40af152826dfd34e350ae8", size = 55128632, upload-time = "2026-03-31T18:29:46.939Z" }, - { url = "https://files.pythonhosted.org/packages/4f/79/d3bbab197e86e0ff4f9c07122895b66a3e0d024247fcff7f12c473cb36d9/llvmlite-0.47.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6842cf6f707ec4be3d985a385ad03f72b2d724439e118fcbe99b2929964f0453", size = 39153839, upload-time = "2026-03-31T18:29:51.004Z" }, -] - -[[package]] -name = "markdown-it-py" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mdurl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, -] - -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, -] - -[[package]] -name = "mdurl" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, -] - -[[package]] -name = "modiff" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "accelerate" }, - { name = "aiofiles" }, - { name = "aiohttp" }, - { name = "aiohttp-cors" }, - { name = "bitsandbytes", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "diffusers" }, - { name = "ftfy" }, - { name = "imageio" }, - { name = "imageio-ffmpeg" }, - { name = "kornia" }, - { name = "nanoid" }, - { name = "peft" }, - { name = "protobuf" }, - { name = "scipy" }, - { name = "sentencepiece" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "torchsde" }, - { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torchvision", version = "0.27.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "transformers" }, -] - -[package.optional-dependencies] -apple-silicon = [ - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, - { name = "torchvision", version = "0.27.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, -] -background-removal = [ - { name = "transparent-background" }, -] -cuda = [ - { name = "bitsandbytes", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "xformers", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -non-commercial = [ - { name = "rembg", extra = ["gpu"] }, -] -nunchaku = [ - { name = "nunchaku", version = "1.0.1.dev20250924+torch2.8", source = { url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1dev20250924/nunchaku-1.0.1.dev20250924+torch2.8-cp312-cp312-linux_x86_64.whl" }, marker = "sys_platform == 'linux'" }, - { name = "nunchaku", version = "1.0.1.dev20250924+torch2.8", source = { url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1dev20250924/nunchaku-1.0.1.dev20250924+torch2.8-cp312-cp312-win_amd64.whl" }, marker = "sys_platform == 'win32'" }, -] -quantization = [ - { name = "dfloat11", extra = ["cuda12"], marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "gguf" }, - { name = "kernels" }, - { name = "optimum-quanto" }, - { name = "torchao" }, -] -spandrel = [ - { name = "spandrel" }, -] - -[package.metadata] -requires-dist = [ - { name = "accelerate", specifier = ">=1.4.0" }, - { name = "aiofiles", specifier = ">=25.1.0" }, - { name = "aiohttp", specifier = ">=3.11.12" }, - { name = "aiohttp-cors", specifier = ">=0.7.0" }, - { name = "bitsandbytes", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = ">=0.46.1" }, - { name = "bitsandbytes", marker = "(sys_platform == 'linux' and extra == 'cuda') or (sys_platform == 'win32' and extra == 'cuda')", specifier = ">=0.46.1" }, - { name = "dfloat11", extras = ["cuda12"], marker = "(sys_platform == 'linux' and extra == 'quantization') or (sys_platform == 'win32' and extra == 'quantization')" }, - { name = "diffusers", git = "https://github.com/huggingface/diffusers" }, - { name = "ftfy", specifier = ">=6.3.1" }, - { name = "gguf", marker = "extra == 'quantization'" }, - { name = "imageio", specifier = ">=2.37.2" }, - { name = "imageio-ffmpeg", specifier = ">=0.6.0" }, - { name = "kernels", marker = "extra == 'quantization'" }, - { name = "kornia", specifier = ">=0.8.1" }, - { name = "nanoid", specifier = ">=2.0.0" }, - { name = "nunchaku", marker = "sys_platform == 'linux' and extra == 'nunchaku'", url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1dev20250924/nunchaku-1.0.1.dev20250924+torch2.8-cp312-cp312-linux_x86_64.whl" }, - { name = "nunchaku", marker = "sys_platform == 'win32' and extra == 'nunchaku'", url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1dev20250924/nunchaku-1.0.1.dev20250924+torch2.8-cp312-cp312-win_amd64.whl" }, - { name = "optimum-quanto", marker = "extra == 'quantization'" }, - { name = "peft", specifier = ">=0.17.0" }, - { name = "protobuf", specifier = ">=6.31.1" }, - { name = "rembg", extras = ["gpu"], marker = "extra == 'non-commercial'" }, - { name = "scipy", specifier = ">=1.15.2" }, - { name = "sentencepiece", specifier = ">=0.2.0" }, - { name = "spandrel", marker = "extra == 'spandrel'" }, - { name = "torch", marker = "sys_platform == 'darwin' and extra == 'apple-silicon'", specifier = ">=2.6.0" }, - { name = "torch", marker = "sys_platform != 'linux' and sys_platform != 'win32'", specifier = ">=2.6.0" }, - { name = "torch", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = ">=2.6.0", index = "https://download.pytorch.org/whl/cu128" }, - { name = "torchao", marker = "extra == 'quantization'" }, - { name = "torchsde", specifier = ">=0.2.6" }, - { name = "torchvision", marker = "sys_platform == 'darwin' and extra == 'apple-silicon'", specifier = ">=0.21.0" }, - { name = "torchvision", marker = "sys_platform != 'linux' and sys_platform != 'win32'", specifier = ">=0.21.0" }, - { name = "torchvision", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = ">=0.21.0", index = "https://download.pytorch.org/whl/cu128" }, - { name = "transformers", specifier = ">=4.49.0" }, - { name = "transparent-background", marker = "extra == 'background-removal'" }, - { name = "xformers", marker = "(sys_platform == 'linux' and extra == 'cuda') or (sys_platform == 'win32' and extra == 'cuda')" }, -] -provides-extras = ["apple-silicon", "cuda", "nunchaku", "spandrel", "background-removal", "quantization", "non-commercial"] - -[[package]] -name = "mpmath" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, -] - -[[package]] -name = "multidict" -version = "6.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, - { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, - { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, - { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, - { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, - { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, - { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, - { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, - { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, - { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, - { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, - { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, - { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, - { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, - { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, - { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, - { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, - { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, - { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, - { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, - { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, - { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, - { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, - { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, - { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, - { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, - { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, - { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, - { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, - { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, - { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, - { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, - { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, - { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, - { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, - { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, - { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, - { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, - { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, - { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, - { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, - { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, - { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, - { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, - { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, - { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, - { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, - { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, - { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, - { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, - { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, - { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, - { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, - { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, - { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, - { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, - { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, - { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, - { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, - { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, - { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, - { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, -] - -[[package]] -name = "nanoid" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/0250bf5935d88e214df469d35eccc0f6ff7e9db046fc8a9aeb4b2a192775/nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68", size = 3290, upload-time = "2018-11-20T14:45:51.578Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/0d/8630f13998638dc01e187fadd2e5c6d42d127d08aeb4943d231664d6e539/nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb", size = 5844, upload-time = "2018-11-20T14:45:50.165Z" }, -] - -[[package]] -name = "networkx" -version = "3.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, -] - -[[package]] -name = "ninja" -version = "1.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/73/79a0b22fc731989c708068427579e840a6cf4e937fe7ae5c5d0b7356ac22/ninja-1.13.0.tar.gz", hash = "sha256:4a40ce995ded54d9dc24f8ea37ff3bf62ad192b547f6c7126e7e25045e76f978", size = 242558, upload-time = "2025-08-11T15:10:19.421Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/74/d02409ed2aa865e051b7edda22ad416a39d81a84980f544f8de717cab133/ninja-1.13.0-py3-none-macosx_10_9_universal2.whl", hash = "sha256:fa2a8bfc62e31b08f83127d1613d10821775a0eb334197154c4d6067b7068ff1", size = 310125, upload-time = "2025-08-11T15:09:50.971Z" }, - { url = "https://files.pythonhosted.org/packages/8e/de/6e1cd6b84b412ac1ef327b76f0641aeb5dcc01e9d3f9eee0286d0c34fd93/ninja-1.13.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3d00c692fb717fd511abeb44b8c5d00340c36938c12d6538ba989fe764e79630", size = 177467, upload-time = "2025-08-11T15:09:52.767Z" }, - { url = "https://files.pythonhosted.org/packages/c8/83/49320fb6e58ae3c079381e333575fdbcf1cca3506ee160a2dcce775046fa/ninja-1.13.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:be7f478ff9f96a128b599a964fc60a6a87b9fa332ee1bd44fa243ac88d50291c", size = 187834, upload-time = "2025-08-11T15:09:54.115Z" }, - { url = "https://files.pythonhosted.org/packages/56/c7/ba22748fb59f7f896b609cd3e568d28a0a367a6d953c24c461fe04fc4433/ninja-1.13.0-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:60056592cf495e9a6a4bea3cd178903056ecb0943e4de45a2ea825edb6dc8d3e", size = 202736, upload-time = "2025-08-11T15:09:55.745Z" }, - { url = "https://files.pythonhosted.org/packages/79/22/d1de07632b78ac8e6b785f41fa9aad7a978ec8c0a1bf15772def36d77aac/ninja-1.13.0-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1c97223cdda0417f414bf864cfb73b72d8777e57ebb279c5f6de368de0062988", size = 179034, upload-time = "2025-08-11T15:09:57.394Z" }, - { url = "https://files.pythonhosted.org/packages/ed/de/0e6edf44d6a04dabd0318a519125ed0415ce437ad5a1ec9b9be03d9048cf/ninja-1.13.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fb46acf6b93b8dd0322adc3a4945452a4e774b75b91293bafcc7b7f8e6517dfa", size = 180716, upload-time = "2025-08-11T15:09:58.696Z" }, - { url = "https://files.pythonhosted.org/packages/54/28/938b562f9057aaa4d6bfbeaa05e81899a47aebb3ba6751e36c027a7f5ff7/ninja-1.13.0-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4be9c1b082d244b1ad7ef41eb8ab088aae8c109a9f3f0b3e56a252d3e00f42c1", size = 146843, upload-time = "2025-08-11T15:10:00.046Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fb/d06a3838de4f8ab866e44ee52a797b5491df823901c54943b2adb0389fbb/ninja-1.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6739d3352073341ad284246f81339a384eec091d9851a886dfa5b00a6d48b3e2", size = 154402, upload-time = "2025-08-11T15:10:01.657Z" }, - { url = "https://files.pythonhosted.org/packages/31/bf/0d7808af695ceddc763cf251b84a9892cd7f51622dc8b4c89d5012779f06/ninja-1.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11be2d22027bde06f14c343f01d31446747dbb51e72d00decca2eb99be911e2f", size = 552388, upload-time = "2025-08-11T15:10:03.349Z" }, - { url = "https://files.pythonhosted.org/packages/9d/70/c99d0c2c809f992752453cce312848abb3b1607e56d4cd1b6cded317351a/ninja-1.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:aa45b4037b313c2f698bc13306239b8b93b4680eb47e287773156ac9e9304714", size = 472501, upload-time = "2025-08-11T15:10:04.735Z" }, - { url = "https://files.pythonhosted.org/packages/9f/43/c217b1153f0e499652f5e0766da8523ce3480f0a951039c7af115e224d55/ninja-1.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f8e1e8a1a30835eeb51db05cf5a67151ad37542f5a4af2a438e9490915e5b72", size = 638280, upload-time = "2025-08-11T15:10:06.512Z" }, - { url = "https://files.pythonhosted.org/packages/8c/45/9151bba2c8d0ae2b6260f71696330590de5850e5574b7b5694dce6023e20/ninja-1.13.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:3d7d7779d12cb20c6d054c61b702139fd23a7a964ec8f2c823f1ab1b084150db", size = 642420, upload-time = "2025-08-11T15:10:08.35Z" }, - { url = "https://files.pythonhosted.org/packages/3c/fb/95752eb635bb8ad27d101d71bef15bc63049de23f299e312878fc21cb2da/ninja-1.13.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:d741a5e6754e0bda767e3274a0f0deeef4807f1fec6c0d7921a0244018926ae5", size = 585106, upload-time = "2025-08-11T15:10:09.818Z" }, - { url = "https://files.pythonhosted.org/packages/c1/31/aa56a1a286703800c0cbe39fb4e82811c277772dc8cd084f442dd8e2938a/ninja-1.13.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:e8bad11f8a00b64137e9b315b137d8bb6cbf3086fbdc43bf1f90fd33324d2e96", size = 707138, upload-time = "2025-08-11T15:10:11.366Z" }, - { url = "https://files.pythonhosted.org/packages/34/6f/5f5a54a1041af945130abdb2b8529cbef0cdcbbf9bcf3f4195378319d29a/ninja-1.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b4f2a072db3c0f944c32793e91532d8948d20d9ab83da9c0c7c15b5768072200", size = 581758, upload-time = "2025-08-11T15:10:13.295Z" }, - { url = "https://files.pythonhosted.org/packages/95/97/51359c77527d45943fe7a94d00a3843b81162e6c4244b3579fe8fc54cb9c/ninja-1.13.0-py3-none-win32.whl", hash = "sha256:8cfbb80b4a53456ae8a39f90ae3d7a2129f45ea164f43fadfa15dc38c4aef1c9", size = 267201, upload-time = "2025-08-11T15:10:15.158Z" }, - { url = "https://files.pythonhosted.org/packages/29/45/c0adfbfb0b5895aa18cec400c535b4f7ff3e52536e0403602fc1a23f7de9/ninja-1.13.0-py3-none-win_amd64.whl", hash = "sha256:fb8ee8719f8af47fed145cced4a85f0755dd55d45b2bddaf7431fa89803c5f3e", size = 309975, upload-time = "2025-08-11T15:10:16.697Z" }, - { url = "https://files.pythonhosted.org/packages/df/93/a7b983643d1253bb223234b5b226e69de6cda02b76cdca7770f684b795f5/ninja-1.13.0-py3-none-win_arm64.whl", hash = "sha256:3c0b40b1f0bba764644385319028650087b4c1b18cdfa6f45cb39a3669b81aa9", size = 290806, upload-time = "2025-08-11T15:10:18.018Z" }, -] - -[[package]] -name = "numba" -version = "0.65.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "llvmlite" }, - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f6/c5/db2ac3685833d626c0dcae6bd2330cd68433e1fd248d15f70998160d3ad7/numba-0.65.1.tar.gz", hash = "sha256:19357146c32fe9ed25059ab915e8465fb13951cf6b0aace3826b76886373ab23", size = 2765600, upload-time = "2026-04-24T02:02:56.551Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/bc/76f8f8c5cf9adee47fdb7bbb03be8900f76f902d451d7477cf12b845e1de/numba-0.65.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ac3f1e77c352dd0ea9712732c2d8f9ca507717435eec5b5013bf138ac33c4a08", size = 2681371, upload-time = "2026-04-24T02:02:26.105Z" }, - { url = "https://files.pythonhosted.org/packages/69/47/a415af0283e4db0398104c6d1c11c9861a98dc67a7aa442a7769ed5d6196/numba-0.65.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:52bc6f3ceb8fcaff9b2ae26b4c6b1e9fee39db8d355534c0fe4f39a901246b84", size = 3802467, upload-time = "2026-04-24T02:02:27.712Z" }, - { url = "https://files.pythonhosted.org/packages/46/36/246f73ec99cfeab2f2cb2ce7d4218766cc36a2da418901223f4f4da9c813/numba-0.65.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90ca10b3463bae0bd70589726fe3c77d01d6b5fc86bee54bcdf9fb6b47c28977", size = 3502628, upload-time = "2026-04-24T02:02:29.763Z" }, - { url = "https://files.pythonhosted.org/packages/db/9e/3c679b2ee078425b9e99a91e44f8d132a6830d8ccce5227bc5e9181aeed8/numba-0.65.1-cp312-cp312-win_amd64.whl", hash = "sha256:5971c632be2a2351500431f46213821dba8d02b18a9f7d02fd36bd2743e41a6a", size = 2750611, upload-time = "2026-04-24T02:02:31.477Z" }, - { url = "https://files.pythonhosted.org/packages/79/37/14a4579049c1eb673afd0de0cb4842982acd55b9ce2643e763db858bcea0/numba-0.65.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1735c15c1134a5108b4d6a5c77fc0947924ea066a738dc09a52008c13df9cad3", size = 2681344, upload-time = "2026-04-24T02:02:33.65Z" }, - { url = "https://files.pythonhosted.org/packages/a0/22/b8d873f6466b20aa563fc9b33acd48dec89a07803ddaa2f1c8ca1cd33126/numba-0.65.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c09f49117ef255e1f1c6dad0c7a1ed39868243862a73be5706793241a3755f1b", size = 3810619, upload-time = "2026-04-24T02:02:36.041Z" }, - { url = "https://files.pythonhosted.org/packages/62/08/e16a8b5d9a018962ebb5c66be662317cde32b9f5dab08441f90bed5522fb/numba-0.65.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:594a8680b3fadac99e97e489b1fd89007177e5336713745c3b769528c635a464", size = 3509783, upload-time = "2026-04-24T02:02:38.245Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a5/03c970d57f4c1741354837353ce39fb5206952ae1dba8922d29c86f64805/numba-0.65.1-cp313-cp313-win_amd64.whl", hash = "sha256:85be74c0d036842699a30058f82fb88fc5ffdc59f7615cab5792ea92914c9b62", size = 2750534, upload-time = "2026-04-24T02:02:39.903Z" }, - { url = "https://files.pythonhosted.org/packages/4f/2e/8aed9b726d9ba5f11ad287645fd479e88278db3060a25cb1225d730eb2b7/numba-0.65.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:33f5eb68eb1c843511615d14663ce60258525d6a4c65ab040e2c2b0c4cf17450", size = 2681554, upload-time = "2026-04-24T02:02:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/87/96/f3eb235fafa82a34e2ab5dd7dc9ffff998ebf5f0bbc23fa56a96aeb44da6/numba-0.65.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71e73029bf53a62cc6afcf96be4bd942290d8b4c55f0a454fb536158115790f7", size = 3779602, upload-time = "2026-04-24T02:02:43.726Z" }, - { url = "https://files.pythonhosted.org/packages/09/90/b0f09b48752d23640b8284f22aa597737e8adaddc7fbfacc4708b7f73a4c/numba-0.65.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a07635e0be926b9bdbffb09137c230fb13f6ec0e564914ba937cee12ce3eb35", size = 3479532, upload-time = "2026-04-24T02:02:45.427Z" }, - { url = "https://files.pythonhosted.org/packages/56/46/3f7fc04fb853559e74b210e0b62c19974ec844cefec611f9e535f4da3761/numba-0.65.1-cp314-cp314-win_amd64.whl", hash = "sha256:2a20fcdabdefbdacf88d85caf70c3b18c4bcb7ebb8f82e6a19486383dd26ab63", size = 2752637, upload-time = "2026-04-24T02:02:47.664Z" }, - { url = "https://files.pythonhosted.org/packages/81/7b/c1a341a9067367778f4152a5f01061cf281fb09582c92c510ec4918cabf6/numba-0.65.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:548dd4b3a4508d5062768d1514b2cd7b015f9a25ec7af651c50dee243965e652", size = 2684600, upload-time = "2026-04-24T02:02:49.653Z" }, - { url = "https://files.pythonhosted.org/packages/03/36/98ddbcf3e4f04a6dd07e1c67249955920579ba4af6bb6868e3088f4ed282/numba-0.65.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:78abc28feff2c2ff8307fff3975b6438352759c9acb797ecd6b1fb6e7e39e31d", size = 3817198, upload-time = "2026-04-24T02:02:51.266Z" }, - { url = "https://files.pythonhosted.org/packages/a3/83/0dad21057ece5a835599f5d24099b091703995e23dbbf894f259e91c010b/numba-0.65.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7676cb389555805f9b9a1840cbcd1ea6c8bd5376ab6918e3a29c5ea1dbda20", size = 3533862, upload-time = "2026-04-24T02:02:52.987Z" }, - { url = "https://files.pythonhosted.org/packages/32/36/8be7118ffd4c8440881046eac3d0982cc5ab42909508cf5d67024d62a2e4/numba-0.65.1-cp314-cp314t-win_amd64.whl", hash = "sha256:20609346e3bd75204950dcbbfe383a8d7dbf4902f442aedbf00f97fef4aa8f38", size = 2758237, upload-time = "2026-04-24T02:02:54.612Z" }, -] - -[[package]] -name = "numpy" -version = "2.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, - { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, - { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, - { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, - { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, - { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, - { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, - { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, - { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, - { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, - { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, - { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, - { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, - { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, - { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, - { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, - { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, - { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, - { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, - { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, - { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, - { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, - { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, - { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, - { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, - { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, - { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, - { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, - { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, - { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, - { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, - { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, -] - -[[package]] -name = "nunchaku" -version = "1.0.1.dev20250924+torch2.8" -source = { url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1dev20250924/nunchaku-1.0.1.dev20250924+torch2.8-cp312-cp312-linux_x86_64.whl" } -resolution-markers = [ - "sys_platform == 'linux'", -] -dependencies = [ - { name = "accelerate", marker = "sys_platform == 'linux'" }, - { name = "diffusers", marker = "sys_platform == 'linux'" }, - { name = "einops", marker = "sys_platform == 'linux'" }, - { name = "huggingface-hub", marker = "sys_platform == 'linux'" }, - { name = "peft", marker = "sys_platform == 'linux'" }, - { name = "protobuf", marker = "sys_platform == 'linux'" }, - { name = "sentencepiece", marker = "sys_platform == 'linux'" }, - { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux'" }, - { name = "transformers", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1dev20250924/nunchaku-1.0.1.dev20250924+torch2.8-cp312-cp312-linux_x86_64.whl", hash = "sha256:3205dcece54918d88414b0cf9d4ab087b589049974378519dd230d12cd2c6433" }, -] - -[package.metadata] -requires-dist = [ - { name = "accelerate", specifier = ">=1.9" }, - { name = "breathe", marker = "extra == 'docs'" }, - { name = "controlnet-aux", marker = "extra == 'ci'", specifier = "==0.0.10" }, - { name = "controlnet-aux", marker = "extra == 'demo'" }, - { name = "controlnet-aux", marker = "extra == 'dev'" }, - { name = "controlnet-aux", marker = "extra == 'full'" }, - { name = "datasets", marker = "extra == 'ci'", specifier = "==3.6" }, - { name = "datasets", marker = "extra == 'dev'", specifier = "<4" }, - { name = "diffusers", specifier = ">=0.35.1" }, - { name = "diffusers", marker = "extra == 'ci'", git = "https://github.com/huggingface/diffusers?rev=a72bc0c" }, - { name = "einops" }, - { name = "facexlib", marker = "extra == 'ci'", specifier = "==0.3" }, - { name = "facexlib", marker = "extra == 'dev'" }, - { name = "facexlib", marker = "extra == 'full'" }, - { name = "furo", marker = "extra == 'docs'" }, - { name = "gradio", marker = "extra == 'demo'", specifier = "==5.39" }, - { name = "graphviz", marker = "extra == 'docs'" }, - { name = "huggingface-hub", specifier = ">=0.34" }, - { name = "image-gen-aux", marker = "extra == 'ci'", git = "https://github.com/asomoza/image_gen_aux.git" }, - { name = "image-gen-aux", marker = "extra == 'demo'", git = "https://github.com/asomoza/image_gen_aux.git" }, - { name = "image-gen-aux", marker = "extra == 'dev'", git = "https://github.com/asomoza/image_gen_aux.git" }, - { name = "image-gen-aux", marker = "extra == 'full'", git = "https://github.com/asomoza/image_gen_aux.git" }, - { name = "insightface", marker = "extra == 'ci'", specifier = "==0.7.3" }, - { name = "insightface", marker = "extra == 'dev'" }, - { name = "insightface", marker = "extra == 'full'" }, - { name = "ipykernel", marker = "extra == 'docs'" }, - { name = "jupyter", marker = "extra == 'docs'" }, - { name = "myst-parser", marker = "extra == 'docs'" }, - { name = "nbsphinx", marker = "extra == 'docs'" }, - { name = "onnxruntime", marker = "extra == 'ci'", specifier = "==1.22.1" }, - { name = "onnxruntime", marker = "extra == 'dev'" }, - { name = "onnxruntime", marker = "extra == 'full'" }, - { name = "opencv-python", marker = "extra == 'ci'", specifier = "==4.11.0.86" }, - { name = "opencv-python", marker = "extra == 'dev'" }, - { name = "opencv-python", marker = "extra == 'full'" }, - { name = "peft", specifier = ">=0.17" }, - { name = "pre-commit", marker = "extra == 'dev'" }, - { name = "protobuf" }, - { name = "pytest", marker = "extra == 'ci'", specifier = "==8.4.2" }, - { name = "pytest", marker = "extra == 'dev'" }, - { name = "pytest-rerunfailures", marker = "extra == 'ci'", specifier = "==16.0.1" }, - { name = "pytest-rerunfailures", marker = "extra == 'dev'" }, - { name = "sentencepiece" }, - { name = "spaces", marker = "extra == 'demo'" }, - { name = "sphinx", marker = "extra == 'docs'" }, - { name = "sphinx-book-theme", marker = "extra == 'docs'" }, - { name = "sphinx-copybutton", marker = "extra == 'docs'" }, - { name = "sphinx-rtd-theme", marker = "extra == 'docs'" }, - { name = "sphinx-tabs", marker = "extra == 'docs'" }, - { name = "sphinxawesome-theme", marker = "extra == 'docs'" }, - { name = "sphinxcontrib-mermaid", marker = "extra == 'docs'" }, - { name = "sphinxext-rediraffe", marker = "extra == 'docs'" }, - { name = "timm", marker = "extra == 'ci'", specifier = "==1.0.19" }, - { name = "timm", marker = "extra == 'dev'" }, - { name = "timm", marker = "extra == 'full'" }, - { name = "torchmetrics", marker = "extra == 'ci'", specifier = "==1.8" }, - { name = "torchmetrics", marker = "extra == 'dev'" }, - { name = "torchvision", specifier = ">=0.20" }, - { name = "transformers", specifier = ">=4.53.3" }, -] -provides-extras = ["ci", "demo", "dev", "docs", "full"] - -[[package]] -name = "nunchaku" -version = "1.0.1.dev20250924+torch2.8" -source = { url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1dev20250924/nunchaku-1.0.1.dev20250924+torch2.8-cp312-cp312-win_amd64.whl" } -resolution-markers = [ - "sys_platform == 'win32'", -] -dependencies = [ - { name = "accelerate", marker = "sys_platform == 'win32'" }, - { name = "diffusers", marker = "sys_platform == 'win32'" }, - { name = "einops", marker = "sys_platform == 'win32'" }, - { name = "huggingface-hub", marker = "sys_platform == 'win32'" }, - { name = "peft", marker = "sys_platform == 'win32'" }, - { name = "protobuf", marker = "sys_platform == 'win32'" }, - { name = "sentencepiece", marker = "sys_platform == 'win32'" }, - { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'win32'" }, - { name = "transformers", marker = "sys_platform == 'win32'" }, -] -wheels = [ - { url = "https://github.com/nunchaku-tech/nunchaku/releases/download/v1.0.1dev20250924/nunchaku-1.0.1.dev20250924+torch2.8-cp312-cp312-win_amd64.whl", hash = "sha256:2466286cd1d2c0c7ccfacdd876d1cfacacd0797c90611e9734fe18d6e58eb159" }, -] - -[package.metadata] -requires-dist = [ - { name = "accelerate", specifier = ">=1.9" }, - { name = "breathe", marker = "extra == 'docs'" }, - { name = "controlnet-aux", marker = "extra == 'ci'", specifier = "==0.0.10" }, - { name = "controlnet-aux", marker = "extra == 'demo'" }, - { name = "controlnet-aux", marker = "extra == 'dev'" }, - { name = "controlnet-aux", marker = "extra == 'full'" }, - { name = "datasets", marker = "extra == 'ci'", specifier = "==3.6" }, - { name = "datasets", marker = "extra == 'dev'", specifier = "<4" }, - { name = "diffusers", specifier = ">=0.35.1" }, - { name = "diffusers", marker = "extra == 'ci'", git = "https://github.com/huggingface/diffusers?rev=a72bc0c" }, - { name = "einops" }, - { name = "facexlib", marker = "extra == 'ci'", specifier = "==0.3" }, - { name = "facexlib", marker = "extra == 'dev'" }, - { name = "facexlib", marker = "extra == 'full'" }, - { name = "furo", marker = "extra == 'docs'" }, - { name = "gradio", marker = "extra == 'demo'", specifier = "==5.39" }, - { name = "graphviz", marker = "extra == 'docs'" }, - { name = "huggingface-hub", specifier = ">=0.34" }, - { name = "image-gen-aux", marker = "extra == 'ci'", git = "https://github.com/asomoza/image_gen_aux.git" }, - { name = "image-gen-aux", marker = "extra == 'demo'", git = "https://github.com/asomoza/image_gen_aux.git" }, - { name = "image-gen-aux", marker = "extra == 'dev'", git = "https://github.com/asomoza/image_gen_aux.git" }, - { name = "image-gen-aux", marker = "extra == 'full'", git = "https://github.com/asomoza/image_gen_aux.git" }, - { name = "insightface", marker = "extra == 'ci'", specifier = "==0.7.3" }, - { name = "insightface", marker = "extra == 'dev'" }, - { name = "insightface", marker = "extra == 'full'" }, - { name = "ipykernel", marker = "extra == 'docs'" }, - { name = "jupyter", marker = "extra == 'docs'" }, - { name = "myst-parser", marker = "extra == 'docs'" }, - { name = "nbsphinx", marker = "extra == 'docs'" }, - { name = "onnxruntime", marker = "extra == 'ci'", specifier = "==1.22.1" }, - { name = "onnxruntime", marker = "extra == 'dev'" }, - { name = "onnxruntime", marker = "extra == 'full'" }, - { name = "opencv-python", marker = "extra == 'ci'", specifier = "==4.11.0.86" }, - { name = "opencv-python", marker = "extra == 'dev'" }, - { name = "opencv-python", marker = "extra == 'full'" }, - { name = "peft", specifier = ">=0.17" }, - { name = "pre-commit", marker = "extra == 'dev'" }, - { name = "protobuf" }, - { name = "pytest", marker = "extra == 'ci'", specifier = "==8.4.2" }, - { name = "pytest", marker = "extra == 'dev'" }, - { name = "pytest-rerunfailures", marker = "extra == 'ci'", specifier = "==16.0.1" }, - { name = "pytest-rerunfailures", marker = "extra == 'dev'" }, - { name = "sentencepiece" }, - { name = "spaces", marker = "extra == 'demo'" }, - { name = "sphinx", marker = "extra == 'docs'" }, - { name = "sphinx-book-theme", marker = "extra == 'docs'" }, - { name = "sphinx-copybutton", marker = "extra == 'docs'" }, - { name = "sphinx-rtd-theme", marker = "extra == 'docs'" }, - { name = "sphinx-tabs", marker = "extra == 'docs'" }, - { name = "sphinxawesome-theme", marker = "extra == 'docs'" }, - { name = "sphinxcontrib-mermaid", marker = "extra == 'docs'" }, - { name = "sphinxext-rediraffe", marker = "extra == 'docs'" }, - { name = "timm", marker = "extra == 'ci'", specifier = "==1.0.19" }, - { name = "timm", marker = "extra == 'dev'" }, - { name = "timm", marker = "extra == 'full'" }, - { name = "torchmetrics", marker = "extra == 'ci'", specifier = "==1.8" }, - { name = "torchmetrics", marker = "extra == 'dev'" }, - { name = "torchvision", specifier = ">=0.20" }, - { name = "transformers", specifier = ">=4.53.3" }, -] -provides-extras = ["ci", "demo", "dev", "docs", "full"] - -[[package]] -name = "nvidia-cublas-cu12" -version = "12.8.4.1" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/99/db44d685f0e257ff0e213ade1964fc459b4a690a73293220e98feb3307cf/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b86f6dd8935884615a0683b663891d43781b819ac4f2ba2b0c9604676af346d0", size = 590537124, upload-time = "2025-03-07T01:43:53.556Z" }, - { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, -] - -[[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.8.90" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/1f/b3bd73445e5cb342727fd24fe1f7b748f690b460acadc27ea22f904502c8/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4412396548808ddfed3f17a467b104ba7751e6b58678a4b840675c56d21cf7ed", size = 9533318, upload-time = "2025-03-07T01:40:10.421Z" }, - { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, -] - -[[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.8.93" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d1/e50d0acaab360482034b84b6e27ee83c6738f7d32182b987f9c7a4e32962/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc1fec1e1637854b4c0a65fb9a8346b51dd9ee69e61ebaccc82058441f15bce8", size = 43106076, upload-time = "2025-03-07T01:41:59.817Z" }, -] - -[[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.8.90" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/75/f865a3b236e4647605ea34cc450900854ba123834a5f1598e160b9530c3a/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:52bf7bbee900262ffefe5e9d5a2a69a30d97e2bc5bb6cc866688caa976966e3d", size = 965265, upload-time = "2025-03-07T01:39:43.533Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, -] - -[[package]] -name = "nvidia-cudnn-cu12" -version = "9.19.0.56" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/09/b8/277c51962ee46fa3e5b203ac5f76107c650f781d6891e681e28e6f3e9fe6/nvidia_cudnn_cu12-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:08caaf27fe556aca82a3ee3b5aa49a77e7de0cfcb7ff4e5c29da426387a8267e", size = 656910700, upload-time = "2026-02-03T20:40:25.508Z" }, - { url = "https://files.pythonhosted.org/packages/c5/41/65225d42fba06fb3dd3972485ea258e7dd07a40d6e01c95da6766ad87354/nvidia_cudnn_cu12-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ac6ad90a075bb33a94f2b4cf4622eac13dd4dc65cf6dd9c7572a318516a36625", size = 657906812, upload-time = "2026-02-03T20:44:12.638Z" }, -] - -[[package]] -name = "nvidia-cufft-cu12" -version = "11.3.3.83" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/bc/7771846d3a0272026c416fbb7e5f4c1f146d6d80704534d0b187dd6f4800/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:848ef7224d6305cdb2a4df928759dca7b1201874787083b6e7550dd6765ce69a", size = 193109211, upload-time = "2025-03-07T01:44:56.873Z" }, - { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, -] - -[[package]] -name = "nvidia-cufile-cu12" -version = "1.13.1.3" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f5/5607710447a6fe9fd9b3283956fceeee8a06cda1d2f56ce31371f595db2a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:4beb6d4cce47c1a0f1013d72e02b0994730359e17801d395bdcbf20cfb3bb00a", size = 1120705, upload-time = "2025-03-07T01:45:41.434Z" }, -] - -[[package]] -name = "nvidia-curand-cu12" -version = "10.3.9.90" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/5e/92aa15eca622a388b80fbf8375d4760738df6285b1e92c43d37390a33a9a/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:dfab99248034673b779bc6decafdc3404a8a6f502462201f2f31f11354204acd", size = 63625754, upload-time = "2025-03-07T01:46:10.735Z" }, - { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, -] - -[[package]] -name = "nvidia-cusolver-cu12" -version = "11.7.3.90" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/32/f7cd6ce8a7690544d084ea21c26e910a97e077c9b7f07bf5de623ee19981/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:db9ed69dbef9715071232caa9b69c52ac7de3a95773c2db65bdba85916e4e5c0", size = 267229841, upload-time = "2025-03-07T01:46:54.356Z" }, - { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, -] - -[[package]] -name = "nvidia-cusparse-cu12" -version = "12.5.8.93" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/f7/cd777c4109681367721b00a106f491e0d0d15cfa1fd59672ce580ce42a97/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b6c161cb130be1a07a27ea6923df8141f3c295852f4b260c65f18f3e0a091dc", size = 288117129, upload-time = "2025-03-07T01:47:40.407Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, -] - -[[package]] -name = "nvidia-cusparselt-cu12" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/b9/598f6ff36faaece4b3c50d26f50e38661499ff34346f00e057760b35cc9d/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8878dce784d0fac90131b6817b607e803c36e629ba34dc5b433471382196b6a5", size = 283835557, upload-time = "2025-02-26T00:16:54.265Z" }, - { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, -] - -[[package]] -name = "nvidia-nccl-cu12" -version = "2.28.9" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/c4/120d2dfd92dff2c776d68f361ff8705fdea2ca64e20b612fab0fd3f581ac/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:50a36e01c4a090b9f9c47d92cec54964de6b9fcb3362d0e19b8ffc6323c21b60", size = 296766525, upload-time = "2025-11-18T05:49:16.094Z" }, - { url = "https://files.pythonhosted.org/packages/4a/4e/44dbb46b3d1b0ec61afda8e84837870f2f9ace33c564317d59b70bc19d3e/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:485776daa8447da5da39681af455aa3b2c2586ddcf4af8772495e7c532c7e5ab", size = 296782137, upload-time = "2025-11-18T05:49:34.248Z" }, -] - -[[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.8.93" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, - { url = "https://files.pythonhosted.org/packages/2a/a2/8cee5da30d13430e87bf99bb33455d2724d0a4a9cb5d7926d80ccb96d008/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:adccd7161ace7261e01bb91e44e88da350895c270d23f744f0820c818b7229e7", size = 38386204, upload-time = "2025-03-07T01:49:43.612Z" }, -] - -[[package]] -name = "nvidia-nvshmem-cu12" -version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/6a/03aa43cc9bd3ad91553a88b5f6fb25ed6a3752ae86ce2180221962bc2aa5/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b48363fc6964dede448029434c6abed6c5e37f823cb43c3bcde7ecfc0457e15", size = 138936938, upload-time = "2025-09-06T00:32:05.589Z" }, - { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, -] - -[[package]] -name = "nvidia-nvtx-cu12" -version = "12.8.90" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/c0/1b303feea90d296f6176f32a2a70b5ef230f9bdeb3a72bddb0dc922dc137/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d7ad891da111ebafbf7e015d34879f7112832fc239ff0d7d776b6cb685274615", size = 91161, upload-time = "2025-03-07T01:42:23.922Z" }, - { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, -] - -[[package]] -name = "onnxruntime-gpu" -version = "1.26.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "flatbuffers" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "protobuf" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/fd/59bee7cffaa435da44fefdeb63e29c61de4dbfa4b279852f59cd02c042ae/onnxruntime_gpu-1.26.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c01119ed4d9449d60367fa8ccffcd02bd3fe736754284e4b198d131f54edad6", size = 276971796, upload-time = "2026-05-08T19:15:46.192Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e4/9b378a5466ea0bed65e5beb8e09254973c580a6522810a38afbcc45e5105/onnxruntime_gpu-1.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:5f49c44689894650990e4c8a857d2edafc276fbd79bba57ceb224bd18d25d491", size = 226548963, upload-time = "2026-05-08T19:09:34.925Z" }, - { url = "https://files.pythonhosted.org/packages/dd/97/fe8979f44b9275654b42f7bb556e30789b71a1b22998c83b540df2b1b774/onnxruntime_gpu-1.26.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfda2fad535595bfc3e570eb588092717711dcb2957656d814695e0c9ceb1508", size = 276974871, upload-time = "2026-05-08T19:15:58.052Z" }, - { url = "https://files.pythonhosted.org/packages/67/3f/59f1777a394625ecc9a85636de57dc47c25dbb5f888da050f1463955a0ce/onnxruntime_gpu-1.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:6ab9f9c741d2e239b2e321ab0d389c04329d4ab7f11e3b92dd3aa7db1c59dee4", size = 226548083, upload-time = "2026-05-08T19:09:44.408Z" }, - { url = "https://files.pythonhosted.org/packages/89/96/360328e3c463f7ea08e853c4239c397e83363dd0204de71a710dc1a544bd/onnxruntime_gpu-1.26.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcf6f347cad9f88a9a625c2b352cf9de927528aedb627ebbc089a201f1990b94", size = 276992052, upload-time = "2026-05-08T19:16:09.892Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c8/aa2dc0e79bba577f37d5448bcb32fea79977e07506684d8138c19a0f1077/onnxruntime_gpu-1.26.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e6e4fb1ec9ae1cf456534d9115f106ab2a1ae96fa513b4ed0f4795302b4a2c6", size = 276978254, upload-time = "2026-05-08T19:16:22.096Z" }, - { url = "https://files.pythonhosted.org/packages/41/e7/923298431e669567d7ccc2a4c898b6534a47641a051569fd97165fe6d9b8/onnxruntime_gpu-1.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e592439b0183d303c2374517b5b392599a3d50b2dc9de949b9b15731ac921c9", size = 229142768, upload-time = "2026-05-08T19:09:54.589Z" }, - { url = "https://files.pythonhosted.org/packages/97/91/93ffe5431d154989f5e04864a25a97eea480997d771232bcbbc538188241/onnxruntime_gpu-1.26.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56dc7b73954ff4bdc71f5b8ab306b6f61be5d007881b6ef423a609e2b9cd088b", size = 276991545, upload-time = "2026-05-08T19:16:33.347Z" }, -] - -[[package]] -name = "opencv-python" -version = "4.13.0.92" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/6f/5a28fef4c4a382be06afe3938c64cc168223016fa520c5abaf37e8862aa5/opencv_python-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:caf60c071ec391ba51ed00a4a920f996d0b64e3e46068aac1f646b5de0326a19", size = 46247052, upload-time = "2026-02-05T07:01:25.046Z" }, - { url = "https://files.pythonhosted.org/packages/08/ac/6c98c44c650b8114a0fb901691351cfb3956d502e8e9b5cd27f4ee7fbf2f/opencv_python-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:5868a8c028a0b37561579bfb8ac1875babdc69546d236249fff296a8c010ccf9", size = 32568781, upload-time = "2026-02-05T07:01:41.379Z" }, - { url = "https://files.pythonhosted.org/packages/3e/51/82fed528b45173bf629fa44effb76dff8bc9f4eeaee759038362dfa60237/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bc2596e68f972ca452d80f444bc404e08807d021fbba40df26b61b18e01838a", size = 47685527, upload-time = "2026-02-05T06:59:11.24Z" }, - { url = "https://files.pythonhosted.org/packages/db/07/90b34a8e2cf9c50fe8ed25cac9011cde0676b4d9d9c973751ac7616223a2/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:402033cddf9d294693094de5ef532339f14ce821da3ad7df7c9f6e8316da32cf", size = 70460872, upload-time = "2026-02-05T06:59:19.162Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/7a9cc719b3eaf4377b9c2e3edeb7ed3a81de41f96421510c0a169ca3cfd4/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bccaabf9eb7f897ca61880ce2869dcd9b25b72129c28478e7f2a5e8dee945616", size = 46708208, upload-time = "2026-02-05T06:59:15.419Z" }, - { url = "https://files.pythonhosted.org/packages/fd/55/b3b49a1b97aabcfbbd6c7326df9cb0b6fa0c0aefa8e89d500939e04aa229/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:620d602b8f7d8b8dab5f4b99c6eb353e78d3fb8b0f53db1bd258bb1aa001c1d5", size = 72927042, upload-time = "2026-02-05T06:59:23.389Z" }, - { url = "https://files.pythonhosted.org/packages/fb/17/de5458312bcb07ddf434d7bfcb24bb52c59635ad58c6e7c751b48949b009/opencv_python-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:372fe164a3148ac1ca51e5f3ad0541a4a276452273f503441d718fab9c5e5f59", size = 30932638, upload-time = "2026-02-05T07:02:14.98Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a5/1be1516390333ff9be3a9cb648c9f33df79d5096e5884b5df71a588af463/opencv_python-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:423d934c9fafb91aad38edf26efb46da91ffbc05f3f59c4b0c72e699720706f5", size = 40212062, upload-time = "2026-02-05T07:02:12.724Z" }, -] - -[[package]] -name = "opencv-python-headless" -version = "4.13.0.92" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/42/2310883be3b8826ac58c3f2787b9358a2d46923d61f88fedf930bc59c60c/opencv_python_headless-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:1a7d040ac656c11b8c38677cc8cccdc149f98535089dbe5b081e80a4e5903209", size = 46247192, upload-time = "2026-02-05T07:01:35.187Z" }, - { url = "https://files.pythonhosted.org/packages/2d/1e/6f9e38005a6f7f22af785df42a43139d0e20f169eb5787ce8be37ee7fcc9/opencv_python_headless-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:3e0a6f0a37994ec6ce5f59e936be21d5d6384a4556f2d2da9c2f9c5dc948394c", size = 32568914, upload-time = "2026-02-05T07:01:51.989Z" }, - { url = "https://files.pythonhosted.org/packages/21/76/9417a6aef9def70e467a5bf560579f816148a4c658b7d525581b356eda9e/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c8cfc8e87ed452b5cecb9419473ee5560a989859fe1d10d1ce11ae87b09a2cb", size = 33703709, upload-time = "2026-02-05T10:24:46.469Z" }, - { url = "https://files.pythonhosted.org/packages/92/ce/bd17ff5772938267fd49716e94ca24f616ff4cb1ff4c6be13085108037be/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0525a3d2c0b46c611e2130b5fdebc94cf404845d8fa64d2f3a3b679572a5bd22", size = 56016764, upload-time = "2026-02-05T10:26:48.904Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b4/b7bcbf7c874665825a8c8e1097e93ea25d1f1d210a3e20d4451d01da30aa/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb60e36b237b1ebd40a912da5384b348df8ed534f6f644d8e0b4f103e272ba7d", size = 35010236, upload-time = "2026-02-05T10:28:11.031Z" }, - { url = "https://files.pythonhosted.org/packages/4b/33/b5db29a6c00eb8f50708110d8d453747ca125c8b805bc437b289dbdcc057/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0bd48544f77c68b2941392fcdf9bcd2b9cdf00e98cb8c29b2455d194763cf99e", size = 60391106, upload-time = "2026-02-05T10:30:14.236Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c3/52cfea47cd33e53e8c0fbd6e7c800b457245c1fda7d61660b4ffe9596a7f/opencv_python_headless-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:a7cf08e5b191f4ebb530791acc0825a7986e0d0dee2a3c491184bd8599848a4b", size = 30812232, upload-time = "2026-02-05T07:02:29.594Z" }, - { url = "https://files.pythonhosted.org/packages/4a/90/b338326131ccb2aaa3c2c85d00f41822c0050139a4bfe723cfd95455bd2d/opencv_python_headless-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:77a82fe35ddcec0f62c15f2ba8a12ecc2ed4207c17b0902c7a3151ae29f37fb6", size = 40070414, upload-time = "2026-02-05T07:02:26.448Z" }, -] - -[[package]] -name = "optimum-quanto" -version = "0.2.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, - { name = "ninja" }, - { name = "numpy" }, - { name = "safetensors" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3e/df/03ae85090b33d81f06b4dd7a43b16cef6ee5f1d36d8fdcce864964895c70/optimum_quanto-0.2.7.tar.gz", hash = "sha256:91b5c2dc8a9100297dc7924a93747fb77ab010784b5e1f6d0208976ba054dade", size = 361601, upload-time = "2025-03-06T08:07:51.578Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/33/4ad914b0ae7e46296fe00d76d084be351fef69816b3498ed32a178471c8a/optimum_quanto-0.2.7-py3-none-any.whl", hash = "sha256:1369b1d9a4a197f88c0d1c67e8d950694e5b86ce4c9f3878e178d5be35339f61", size = 165285, upload-time = "2025-03-06T08:07:32.913Z" }, -] - -[[package]] -name = "packaging" -version = "26.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, -] - -[[package]] -name = "peft" -version = "0.19.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "accelerate" }, - { name = "huggingface-hub" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "psutil" }, - { name = "pyyaml" }, - { name = "safetensors" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "tqdm" }, - { name = "transformers" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/cf/037f1e3d5186496c05513a6754639e2dab3038a05f384284d49a9bd06a2d/peft-0.19.1.tar.gz", hash = "sha256:0d97542fe96dcdaa20d3b81c06f26f988618f416a73544ab23c3618ccb674a40", size = 763738, upload-time = "2026-04-16T15:46:45.105Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/b6/f54d676ed93cc2dd2234c3b172ea9c8c3d7d29361e66b1b23dec57a67465/peft-0.19.1-py3-none-any.whl", hash = "sha256:2113f72a81621b5913ef28f9022204c742df111890c5f49d812716a4a301e356", size = 680692, upload-time = "2026-04-16T15:46:42.886Z" }, -] - -[[package]] -name = "pillow" -version = "12.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, - { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, - { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, - { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, - { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, - { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, -] - -[[package]] -name = "pooch" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "platformdirs" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/43/85ef45e8b36c6a48546af7b266592dc32d7f67837a6514d111bced6d7d75/pooch-1.9.0.tar.gz", hash = "sha256:de46729579b9857ffd3e741987a2f6d5e0e03219892c167c6578c0091fb511ed", size = 61788, upload-time = "2026-01-30T19:15:09.649Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl", hash = "sha256:f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b", size = 67175, upload-time = "2026-01-30T19:15:08.36Z" }, -] - -[[package]] -name = "propcache" -version = "0.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, - { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, - { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, - { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, - { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, - { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, - { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, - { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, - { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, - { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, - { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, - { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, - { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, - { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, - { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, - { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, - { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, - { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, - { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, - { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, - { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, - { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, - { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, - { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, - { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, - { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, - { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, - { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, - { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, - { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, - { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, - { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, - { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, - { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, - { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, - { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, - { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, - { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, - { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, - { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, - { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, - { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, - { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, - { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, - { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, - { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, - { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, - { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, - { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, - { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, - { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, - { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, - { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, - { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, - { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, - { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, - { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, - { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, - { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, - { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, - { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, - { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, - { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, - { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, - { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, - { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, -] - -[[package]] -name = "protobuf" -version = "7.35.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/60/fd/5b1491d9e4b586d621c54f4c36b888714164b6875f8d6afa3f9072906a51/protobuf-7.35.0.tar.gz", hash = "sha256:a2efd84605f41e559f1881b0912b44099d0a2ac9bf46b3474823f10fb393b0e6", size = 458677, upload-time = "2026-05-19T23:02:29.197Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/ee/93d06e358a4aa32280b00e722d3ea0a1f25fc3cc5778d80581c9cca2c10e/protobuf-7.35.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:66be6c513931c794fa92c080ffee41671390da3d79da219cf9c0c0907f035dda", size = 433225, upload-time = "2026-05-19T23:02:19.884Z" }, - { url = "https://files.pythonhosted.org/packages/8b/39/1c76c2da93f3c507e958e0aecee2391cc44d4625de6c728bbc555195b5a8/protobuf-7.35.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:fcbe42a4ac09d3ec9c987ddfcd956afd0b15f1ff613bd8371bde9405ffd5c8e5", size = 328847, upload-time = "2026-05-19T23:02:22.3Z" }, - { url = "https://files.pythonhosted.org/packages/91/1a/39f7ce90a238c1a987a4d81ec26379e02ca0aff367de68e4a1fa474215b9/protobuf-7.35.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:4cbf5cc286130e06a6c9bbefac442431173906dfcc979712183d4adcc01b37ee", size = 344030, upload-time = "2026-05-19T23:02:23.591Z" }, - { url = "https://files.pythonhosted.org/packages/70/5b/6baf9008817964454055ff3fe65f1de0b5f1e26c80c82f7fb108b7cd4ea3/protobuf-7.35.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:6c0f98f10c8a05ea30f8993dfef2de093d27b490fdae78bb60c8343795d55011", size = 327130, upload-time = "2026-05-19T23:02:24.637Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e5/e46adb0badc388bfb84877a5f9f026aff63f60e611016cf64dbe77e05446/protobuf-7.35.0-cp310-abi3-win32.whl", hash = "sha256:4c4617b83ade0e279d1d2bfe04025a1adb87f9ed657de038620dc0ff959357f6", size = 428946, upload-time = "2026-05-19T23:02:25.741Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ab/547fbd9e16d879dd13c167478f8ae0a83a428008ca07a5e06acdc23ad473/protobuf-7.35.0-cp310-abi3-win_amd64.whl", hash = "sha256:f05bcadf9a2a6b8dda047007075135fb7d08c73d9177aabc067e1be46881a201", size = 439996, upload-time = "2026-05-19T23:02:26.808Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ef/50433d346c56657a70d27f156c7b349ac59a068b01de4eb796e747eecc43/protobuf-7.35.0-py3-none-any.whl", hash = "sha256:c13f325cf242bad135c350629eeb5d54b24228eb472fb3e2e9ebbd4c5dc20ca0", size = 171659, upload-time = "2026-05-19T23:02:27.842Z" }, -] - -[[package]] -name = "psutil" -version = "7.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, - { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, - { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, -] - -[[package]] -name = "pydantic" -version = "2.13.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.46.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, - { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, - { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, - { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, - { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, - { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, - { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, - { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, - { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, - { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, - { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, - { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, - { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, - { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, - { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, - { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, - { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, - { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, - { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, - { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, - { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, - { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, - { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, - { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, - { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, - { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, - { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, - { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, - { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, - { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, - { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, - { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, - { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, - { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, - { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, - { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, - { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, - { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, - { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, - { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, - { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, - { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, - { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, -] - -[[package]] -name = "pygments" -version = "2.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, -] - -[[package]] -name = "pymatting" -version = "1.1.15" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numba" }, - { name = "numpy" }, - { name = "pillow" }, - { name = "scipy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9a/f5/83955aa915ea5e04cecb32612d419e8341604d0b898c2ebe4277adbc4c6b/pymatting-1.1.15.tar.gz", hash = "sha256:67cbadd68d04696357461ad1861bcb3c2adc9ec5fcd38d524db606addabe745a", size = 44424, upload-time = "2026-01-26T09:27:22.395Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/59/87a27f2539b0a9436853484e80f6a3ef96c30caa0316dce85f1f29d9e953/pymatting-1.1.15-py3-none-any.whl", hash = "sha256:1bd7f04651f1e02b390b88b84cf97c7f4c871ad8568945e4303746bf3ab48ecc", size = 54862, upload-time = "2026-01-26T09:27:20.856Z" }, -] - -[[package]] -name = "pysocks" -version = "1.7.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, -] - -[[package]] -name = "referencing" -version = "0.37.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, -] - -[[package]] -name = "regex" -version = "2026.5.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/9b/6550044bc44e17c84d312c031c2ec42fbdb6a4ec4e29093be3a172d08772/regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", size = 490451, upload-time = "2026-05-09T23:12:34.72Z" }, - { url = "https://files.pythonhosted.org/packages/1e/95/fc7ba4303b5a0f92446a12ee6778ef2c6c799233f5060042a31bf390cfe9/regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", size = 292112, upload-time = "2026-05-09T23:12:36.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/4b/ee27938d1b2c443e89a9a10e00d2d19aa5ee300cd3d61140644e93bb083e/regex-2026.5.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225", size = 289599, upload-time = "2026-05-09T23:12:38.089Z" }, - { url = "https://files.pythonhosted.org/packages/d8/dd/ba103dc19614e25f3880800ca67ce093d6e21b325d72b8383c7bf906e9fa/regex-2026.5.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6441cc660d76107934a09c22167200839a0e89604a6297f78a974e66e931d2c0", size = 796732, upload-time = "2026-05-09T23:12:40.062Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e7/f035b4fd858b050b0080bf302968dc0f59ba34e391872d54936758e6844e/regex-2026.5.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91328f1c23d47595ca3ef0a7557fa129c5a23404b775c770697d2f35b33e0107", size = 865440, upload-time = "2026-05-09T23:12:42.059Z" }, - { url = "https://files.pythonhosted.org/packages/0a/51/8cd301ecc899aea28124357f729f4272f44de7806fc7ca02490bfbe253e8/regex-2026.5.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:93a7860539414dddaefba2b40f8771765ae17949d4c7182b876ce429e11a8309", size = 912329, upload-time = "2026-05-09T23:12:44.373Z" }, - { url = "https://files.pythonhosted.org/packages/cc/1e/3fbe2fa1e8cebd62f3bb7d3321cff1640aca2e240b51d9bd624aad949260/regex-2026.5.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd2810d22146b6d838acc5ec15602cb6b47920aa4e33015df3868eedfd20bab8", size = 801239, upload-time = "2026-05-09T23:12:46.268Z" }, - { url = "https://files.pythonhosted.org/packages/17/2f/6f6008682bf2cf98040a0d3153a8e557b6ab728d7713d045cee4ce544ab8/regex-2026.5.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daff2bdbaf1d23e52fdff7c0b7bc2048b68f978df6a4d107ac981f94caef2e66", size = 777054, upload-time = "2026-05-09T23:12:48.051Z" }, - { url = "https://files.pythonhosted.org/packages/19/2b/eee0d20a6842ba04df4b8847a920b57ef56853f14ef85405473e586b605a/regex-2026.5.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4eeb011098fcb77af513dcef521a3dbecbf8849b1e38940759d293b7a93f5026", size = 785098, upload-time = "2026-05-09T23:12:49.851Z" }, - { url = "https://files.pythonhosted.org/packages/4a/98/6fc1e6410feefb92159edaed5041992bfe390e8d26c721865434acbca558/regex-2026.5.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ea9c8ecfa1b73c73b626534d6626e5340d429630943672b8480724f44e84b962", size = 860095, upload-time = "2026-05-09T23:12:51.666Z" }, - { url = "https://files.pythonhosted.org/packages/18/a3/bd855e0f2cb1a978ecf6fa6bb69632dd9c3f6ea3b81cde62fde14c9daec7/regex-2026.5.9-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cd2846168eb9ee3c513902bc8225409cb1caab31d04728b145171fa1625d9621", size = 765762, upload-time = "2026-05-09T23:12:53.413Z" }, - { url = "https://files.pythonhosted.org/packages/dc/66/0ae8c092e60b14c79d24f8e0b7f0aea5bfbffdcab00b5483d13404d3c3a5/regex-2026.5.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39617fb0cde9c0e6306dc70e3bfc096f3da793219879f7ae7aa341a69fbdcf6d", size = 852100, upload-time = "2026-05-09T23:12:55.256Z" }, - { url = "https://files.pythonhosted.org/packages/21/de/8dfde60fc1b21c946a893ba273403b72617edb261370cb1087099a83f088/regex-2026.5.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd03c4f0e33280d15cae17159b899245d6b7c53d21def19b263b39655061f5ce", size = 789479, upload-time = "2026-05-09T23:12:57.573Z" }, - { url = "https://files.pythonhosted.org/packages/c3/1c/bdcc98f9a4af4fdd166c74941174619ccff4726d3ce32faa8e9a2ecd38dd/regex-2026.5.9-cp312-cp312-win32.whl", hash = "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e", size = 266699, upload-time = "2026-05-09T23:12:59.14Z" }, - { url = "https://files.pythonhosted.org/packages/78/87/240d36864f9e48ace85f72e79ced97ceb7f27ce87739a947dcb834b4e6bc/regex-2026.5.9-cp312-cp312-win_amd64.whl", hash = "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e", size = 277783, upload-time = "2026-05-09T23:13:00.789Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b5/7b30f312b0669dff5beebe5b0989dc2d1a312b1a44fab852199c387a5b96/regex-2026.5.9-cp312-cp312-win_arm64.whl", hash = "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070", size = 270513, upload-time = "2026-05-09T23:13:02.426Z" }, - { url = "https://files.pythonhosted.org/packages/aa/da/797e91ecec6f84135da778ddce78c20e0af5d2a15c26f87a81bc3eadb6db/regex-2026.5.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d626b84406444b165fc0ba981604edea39f0588ff1f92baa23fe50799ea9afdb", size = 490303, upload-time = "2026-05-09T23:13:04.382Z" }, - { url = "https://files.pythonhosted.org/packages/44/da/bf30abaaa737b58f4a4b8c4a03659e02fd92092c822e0197ed9e0daab917/regex-2026.5.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f", size = 292019, upload-time = "2026-05-09T23:13:06.022Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e7/d0eaf5713828417b9e5648cf81fa9bacd4961f6ab98c380c2034f8716e35/regex-2026.5.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a8820737949116ffff55fe18f9fc644530063ba6ebfcb8314239416e78f1347c", size = 289468, upload-time = "2026-05-09T23:13:08.214Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9b/b3fdd62b003baa1a9b593cd8c8699c9651c2e80cc21a5c715707983c42d7/regex-2026.5.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0fbdbac82cb3e4450d0ccde7d7a35607f4cb2dd9fba4b8b69bfaf8c9fa6aed", size = 796749, upload-time = "2026-05-09T23:13:10.573Z" }, - { url = "https://files.pythonhosted.org/packages/d4/30/66ab84588765f5b4b271a9ca09ef7ce2b87caa95176ec3d2ad65d7bc4902/regex-2026.5.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57e8915c7986aa33d25e4d3629cef711cd2863f2961b10409f0c04cb8b7d9020", size = 865445, upload-time = "2026-05-09T23:13:12.523Z" }, - { url = "https://files.pythonhosted.org/packages/1a/89/f05169e8588aac365f35ffc7f3bc3184f095ef4cfded7cfaa3c7fd5dbd89/regex-2026.5.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508f56a89ba9cb26e4168cbc37dbd60a28d82430a9e18ad1d25fe0883c314ca2", size = 912322, upload-time = "2026-05-09T23:13:14.281Z" }, - { url = "https://files.pythonhosted.org/packages/30/e1/c93444052cf41581f3c884ab3fb5823daf0992f11cd4388d4275ca610558/regex-2026.5.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d189041f15691cfa2b6c4290448ec221244d225b3f5fe9e7771b34ffcdf6e2", size = 801269, upload-time = "2026-05-09T23:13:16.569Z" }, - { url = "https://files.pythonhosted.org/packages/50/fe/0cf96b882f540e62e8b9956599798203d599c44cf4c77917ca27400ff69b/regex-2026.5.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e82db382b44d0111b22601c509c89f64434816c9e0eef9d1989cda8cc6ff1c04", size = 777085, upload-time = "2026-05-09T23:13:18.675Z" }, - { url = "https://files.pythonhosted.org/packages/23/5c/d78d4924e7fc875557b9e9b768423925fdfaac5549d06da7810019a9bd26/regex-2026.5.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2acfb48634f64996b57f90f39afa692ff362162722581921fe92239a59960f3c", size = 785153, upload-time = "2026-05-09T23:13:20.525Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e0/5214774090e7b4524dcea3e3c4aa74141d43043f8beb49c1599db1c8b53a/regex-2026.5.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d29eebfc9525db68cad3c97eedd7f754fa265aa5cd0cf4f863b2421e1b48fc9f", size = 860164, upload-time = "2026-05-09T23:13:22.263Z" }, - { url = "https://files.pythonhosted.org/packages/6e/e1/4a57a83350319b1271f0d7a249b8672513ed928b237a741631270de6caea/regex-2026.5.9-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:debb893095e944091c16e641a6e33c1b0f4cb61ab945ec5afbf53ce7068834d8", size = 765731, upload-time = "2026-05-09T23:13:24.277Z" }, - { url = "https://files.pythonhosted.org/packages/12/f4/499e74a20c156fc75836ee04a72a38d1a063978f600937f9760467beb1b0/regex-2026.5.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d659eee77986549c9ea45b861c7567e44d6287c3dc9a4565478853f7b9fe2ff6", size = 852062, upload-time = "2026-05-09T23:13:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/5b/92/7eebc0d0a01e78629695f342ba17e0deaff8fb45e79cc0d7b98287da6e3e/regex-2026.5.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2efa205e6d98b24d1f3ab395c11aa15cdf10935bca283d0285e0499c284fba21", size = 789577, upload-time = "2026-05-09T23:13:27.814Z" }, - { url = "https://files.pythonhosted.org/packages/05/a4/018e71f7d2ad48c1ebe6d3ae0026f9b7cb4802fd15c7cc02fdf724355102/regex-2026.5.9-cp313-cp313-win32.whl", hash = "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127", size = 266691, upload-time = "2026-05-09T23:13:29.549Z" }, - { url = "https://files.pythonhosted.org/packages/e6/1d/861a93719fb9ee7dbfc3761b3797b7a3e112a5d42c6129459d2d741be9b5/regex-2026.5.9-cp313-cp313-win_amd64.whl", hash = "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca", size = 277747, upload-time = "2026-05-09T23:13:31.859Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c6/0a2436ae4da1ba76e51cb98943c6838a9a721faa40ebe2dce07694ae34e3/regex-2026.5.9-cp313-cp313-win_arm64.whl", hash = "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6", size = 270500, upload-time = "2026-05-09T23:13:33.525Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e9/d21346f7b60ed58789371358ed66b09d00f832e1bd7c06e55d9da5679882/regex-2026.5.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:01f28d868834624c934b8d2e0aa1c8341337e37831f4a012f18a5afcba4cbaf3", size = 494172, upload-time = "2026-05-09T23:13:35.935Z" }, - { url = "https://files.pythonhosted.org/packages/c4/43/fd1177a2032037c681baecdb3422ee4e1424aec4e4f470ef47793d325274/regex-2026.5.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6", size = 293952, upload-time = "2026-05-09T23:13:38.307Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7d/9fbf919768368d3f8a4f6c692cf2aa61e482b2b81ec6a298ace4cbf02480/regex-2026.5.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b96350aa424e79d4fd6b567b344dcbe2b2d6bfc48dfe7717587e1fa6d43da6ff", size = 292314, upload-time = "2026-05-09T23:13:40.353Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6c/e41bfeecb589716843e7c4df09ba46ff2a42961457afece19059d85caeef/regex-2026.5.9-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f3af7a4903c5c04a11a196a5aa75cdd7dd3f8508132f9fb3259d9f5908e3b88", size = 811681, upload-time = "2026-05-09T23:13:42.543Z" }, - { url = "https://files.pythonhosted.org/packages/87/83/a5c1c525fba0aa656e88ad0face0b1829788ef4c2fb6b26df58aa1151b84/regex-2026.5.9-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e87577720152d2caae19fe2baaf1f8d5ca12091e9e229f03915c37d1e4b9178", size = 871135, upload-time = "2026-05-09T23:13:44.326Z" }, - { url = "https://files.pythonhosted.org/packages/18/d4/80882e799e440dd878b0979cbebf8fa4d54624a332c83037c7a701649e3f/regex-2026.5.9-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8b9b9d294cfea3cd19c718ade7cc93492b2c4991abd9a68d0b3477ae6d8e100", size = 917265, upload-time = "2026-05-09T23:13:47.295Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ff/8db60211e2286e396aad7dc7725356c502bff0901ea05bd6cdc2e1a042b9/regex-2026.5.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:728d8bfd28a8845c8b6bc5dc7ce010453d206396786c0765c2740cb65f37791e", size = 816311, upload-time = "2026-05-09T23:13:49.885Z" }, - { url = "https://files.pythonhosted.org/packages/4c/47/742ef579c61730f8d268e5cf1f9ce0e37e2ea041ad0f5644724f2378e463/regex-2026.5.9-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e30b874d341fac767d7df5a0870540541c2c054b80cfaac116e8d367a8a7ff2", size = 785498, upload-time = "2026-05-09T23:13:52.25Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ab/cb0999802dcb0fb95b1ab005e8d4163d8afdd67efc2cb6b6630ac13f8cb1/regex-2026.5.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fd190e88a895a8901325fad284a3f74ea52b1da8525b76cc811fa9b1edf0ce2b", size = 801348, upload-time = "2026-05-09T23:13:54.127Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/8ca59a24c55bc34d166eefaf3717bd77772f329fdbf984d86581e0a3571c/regex-2026.5.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8e76e8161ad00694cfce6767d5dea860c6391ac5b83e5c3a39661e696f11fc7e", size = 866493, upload-time = "2026-05-09T23:13:56.067Z" }, - { url = "https://files.pythonhosted.org/packages/8d/3d/30f2ae62cef3278bb5bb821f467277a55fb73f01032cf85997e15e8289a8/regex-2026.5.9-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ddda5340e6c01a293027dd46232fa79eaff1b48058ce7a98f572b6445b088041", size = 772811, upload-time = "2026-05-09T23:13:57.867Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ae/7d2089bcd78ad0c0161bc684339df50032acb438a7bd3305e7ddb1193cec/regex-2026.5.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:205109e96b3cf5adf8f4cd62bedde9487feb282b9497a3535451e5a24cd706a0", size = 856584, upload-time = "2026-05-09T23:13:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/a9/29/92ff47f75990131ea4f24ba17819e5a9d141e10819807e09addd73409af6/regex-2026.5.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbe4579b9f08036aa7d101d1835437a20783574ac66327e6b29b4018a138081", size = 803453, upload-time = "2026-05-09T23:14:01.978Z" }, - { url = "https://files.pythonhosted.org/packages/04/99/eff29f1037dcab36702c9ee5d6858cf1ce2336ea8ea2987f64245b99ea5e/regex-2026.5.9-cp313-cp313t-win32.whl", hash = "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5", size = 269951, upload-time = "2026-05-09T23:14:03.661Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9d/8870b8981d27b22cda77bb26a5ac7ebfa9c7d9e0dea195a834a82380e748/regex-2026.5.9-cp313-cp313t-win_amd64.whl", hash = "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4", size = 281240, upload-time = "2026-05-09T23:14:05.56Z" }, - { url = "https://files.pythonhosted.org/packages/72/b1/3379415e8f135c13ac551353397cc4fe97b4978f3cac73c5fcbcded548b8/regex-2026.5.9-cp313-cp313t-win_arm64.whl", hash = "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de", size = 272383, upload-time = "2026-05-09T23:14:07.843Z" }, - { url = "https://files.pythonhosted.org/packages/13/3e/9c3cd292d8808b3645a2ce517e200179b6d0e903f176300bd8b542e14de5/regex-2026.5.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a", size = 490376, upload-time = "2026-05-09T23:14:09.64Z" }, - { url = "https://files.pythonhosted.org/packages/60/70/d43ee8a2ca0a8b68d167f21658b85520ac0574617c7f320367c5047f7556/regex-2026.5.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4", size = 291964, upload-time = "2026-05-09T23:14:11.424Z" }, - { url = "https://files.pythonhosted.org/packages/21/91/9d50b433828d8e74196904e168a43abf1e6e88b2a15d47ed742456720c37/regex-2026.5.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c", size = 289682, upload-time = "2026-05-09T23:14:13.123Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/b835e3cafbb9d977736912436259ff551d60919f7d7b3d37d46659c63564/regex-2026.5.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9", size = 796996, upload-time = "2026-05-09T23:14:14.923Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a6/9f992d00019166b9de01c546dd4549bc679f2a68df11b877740b0760b7c2/regex-2026.5.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af", size = 866089, upload-time = "2026-05-09T23:14:17.757Z" }, - { url = "https://files.pythonhosted.org/packages/e0/08/4d32af657e049b19cb62b02e46e38fe1518797bfb2203ee93a510b21b0dc/regex-2026.5.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0", size = 911530, upload-time = "2026-05-09T23:14:20.353Z" }, - { url = "https://files.pythonhosted.org/packages/d9/27/2af43dd1dc201d1fecefda64a45f4ad0995855b92724f795a777b402ee69/regex-2026.5.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4", size = 800643, upload-time = "2026-05-09T23:14:22.265Z" }, - { url = "https://files.pythonhosted.org/packages/a4/dd/23a249047013b5321d4a60c4d2437462086f601b061776a525e5fba2a59f/regex-2026.5.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf", size = 777223, upload-time = "2026-05-09T23:14:24.179Z" }, - { url = "https://files.pythonhosted.org/packages/94/6a/e85ed9538cd19586d0465076a4578a12e093ce776d15f3f8ce92733a8dd6/regex-2026.5.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346", size = 785760, upload-time = "2026-05-09T23:14:26.065Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c4/f25473209438638e947c55f9156fd8f236f74169229028cc99116380868e/regex-2026.5.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676", size = 860891, upload-time = "2026-05-09T23:14:28.17Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f7/f4f86e3c74419c37370e91f150ae0c2ef7d34b2e0e4cdd5da046a02e4022/regex-2026.5.9-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14", size = 765891, upload-time = "2026-05-09T23:14:30.06Z" }, - { url = "https://files.pythonhosted.org/packages/26/70/704d8e13765939146b1cd0ef4e2feb71d7929727d2290f026eed10095955/regex-2026.5.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd", size = 851380, upload-time = "2026-05-09T23:14:32.123Z" }, - { url = "https://files.pythonhosted.org/packages/26/29/1a13582a8460038edc38e49f64ceb0dd7c60f5caba77571f4bf6601965d9/regex-2026.5.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e", size = 789350, upload-time = "2026-05-09T23:14:34.799Z" }, - { url = "https://files.pythonhosted.org/packages/73/56/3dcafe34fc72e271d62ad9a291801e88a1457bb251c132f15fcc2e5aad1a/regex-2026.5.9-cp314-cp314-win32.whl", hash = "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad", size = 272130, upload-time = "2026-05-09T23:14:36.729Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9c/02eebf0be95efe416c664db7fb8b6b05b7a0b06a7544f2884f2558b0526f/regex-2026.5.9-cp314-cp314-win_amd64.whl", hash = "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763", size = 280999, upload-time = "2026-05-09T23:14:39.126Z" }, - { url = "https://files.pythonhosted.org/packages/70/5a/1dd1abee76cb7a846a0bcf42fdc87e5720c3c33c24f3e37814310a513d9f/regex-2026.5.9-cp314-cp314-win_arm64.whl", hash = "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372", size = 273500, upload-time = "2026-05-09T23:14:41.059Z" }, - { url = "https://files.pythonhosted.org/packages/86/c1/c5f619b0057a7965cb78ec559c1d7a45ce8c99a35bea95483d64959a93d9/regex-2026.5.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499", size = 494269, upload-time = "2026-05-09T23:14:42.869Z" }, - { url = "https://files.pythonhosted.org/packages/05/2c/5d01f1aee33de4bbe60c8452945bfc8477ca7c5ae4450f6bfe711036cb36/regex-2026.5.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1", size = 293954, upload-time = "2026-05-09T23:14:44.822Z" }, - { url = "https://files.pythonhosted.org/packages/7a/fe/e8988b2ae2108c6ef71bd4aa8d87fbe257976dd0810e826cd75f701c68b6/regex-2026.5.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d", size = 292405, upload-time = "2026-05-09T23:14:47.211Z" }, - { url = "https://files.pythonhosted.org/packages/79/34/d2b0937faa7859263f7f0a3c6b103a1296306be6952dc173d0154e9a2f49/regex-2026.5.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c", size = 811855, upload-time = "2026-05-09T23:14:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/80/fe/daf53a47457a8486db66c66c01ceb9c2303eecee3f87197f1e77eb1a736d/regex-2026.5.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5", size = 871189, upload-time = "2026-05-09T23:14:51.555Z" }, - { url = "https://files.pythonhosted.org/packages/1c/75/058fc4470cbfbf57d800aff1a0022b929a3f9fa553ee10a0cdf2070eb31f/regex-2026.5.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20", size = 917485, upload-time = "2026-05-09T23:14:53.633Z" }, - { url = "https://files.pythonhosted.org/packages/88/e7/179cfda3a28bc843b5c6cfe7f79f23489c791ed95f151083803660878432/regex-2026.5.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0", size = 816369, upload-time = "2026-05-09T23:14:56.198Z" }, - { url = "https://files.pythonhosted.org/packages/41/90/6f0cc422071688266d344fca8462d787cba0a2c144acb25721f9a61ec265/regex-2026.5.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d", size = 785869, upload-time = "2026-05-09T23:14:58.602Z" }, - { url = "https://files.pythonhosted.org/packages/02/67/a31f1760f09c27b251ef39e9beb541f462cf977381d067faa764c2c0e393/regex-2026.5.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b", size = 801427, upload-time = "2026-05-09T23:15:00.642Z" }, - { url = "https://files.pythonhosted.org/packages/e3/c4/1a80654597b6bc1e1ea0494824c31200e8a956abe290afae9b19a166a148/regex-2026.5.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a", size = 866482, upload-time = "2026-05-09T23:15:03.384Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/960724e06482c08466ff5611e242e86f80062949cdf6b4b9cc317b9dd93d/regex-2026.5.9-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415", size = 773022, upload-time = "2026-05-09T23:15:05.625Z" }, - { url = "https://files.pythonhosted.org/packages/50/a8/a9979c3e7918280e93159ebcab5ef1a65116dd4f3bd6091be0eae4a126e8/regex-2026.5.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2", size = 856642, upload-time = "2026-05-09T23:15:07.966Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d4/a9b732f2f0072c0ab12227483abb24fffcb9f73f8a2b203df0a6d0434735/regex-2026.5.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41", size = 803552, upload-time = "2026-05-09T23:15:10.215Z" }, - { url = "https://files.pythonhosted.org/packages/d5/fe/1b3113817447a1d4155e4ac76d2e072f42c0bcba2f43fa8a0e756ea2cd91/regex-2026.5.9-cp314-cp314t-win32.whl", hash = "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58", size = 275746, upload-time = "2026-05-09T23:15:12.609Z" }, - { url = "https://files.pythonhosted.org/packages/92/73/93d42045302636c91f2e5ef588b65b84b01428f28ec77de256b1dfdfbe5c/regex-2026.5.9-cp314-cp314t-win_amd64.whl", hash = "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77", size = 285685, upload-time = "2026-05-09T23:15:15.086Z" }, - { url = "https://files.pythonhosted.org/packages/da/80/35b4c33c804a165a7f55289afda3ea9e3eb6d15800341a2d66455c0f1f30/regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", size = 275713, upload-time = "2026-05-09T23:15:16.98Z" }, -] - -[[package]] -name = "rembg" -version = "2.0.76" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema" }, - { name = "numpy" }, - { name = "pillow" }, - { name = "pooch" }, - { name = "pymatting" }, - { name = "scikit-image" }, - { name = "scipy" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/26/54/516bb406aa1dd95de5d0d5bcce889ce292c37da5a0f0fc9b52790add403a/rembg-2.0.76.tar.gz", hash = "sha256:b55ba8a39a53961918506b36b7241adcf737f8e3ab452638d5f50927e16cdc7e", size = 30423, upload-time = "2026-06-03T13:27:16.423Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/41/711e1c5763b5acc653b53a5f4c60b9ffa85b6298c4525f5e90844af7236e/rembg-2.0.76-py3-none-any.whl", hash = "sha256:c98ed085de93f4e1e984f8939afd361fa13d0e4922ed14b3ef77670438a76db3", size = 45230, upload-time = "2026-06-03T13:27:17.393Z" }, -] - -[package.optional-dependencies] -gpu = [ - { name = "onnxruntime-gpu", marker = "sys_platform != 'darwin'" }, -] - -[[package]] -name = "requests" -version = "2.34.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, -] - -[package.optional-dependencies] -socks = [ - { name = "pysocks" }, -] - -[[package]] -name = "rich" -version = "15.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, -] - -[[package]] -name = "rpds-py" -version = "2026.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, - { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, - { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, - { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, - { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, - { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, - { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, - { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, - { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, - { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, - { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, - { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, - { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, - { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, - { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, - { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, - { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, - { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, - { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, - { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, - { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, - { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, - { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, - { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, - { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, - { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, - { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, - { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, - { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, - { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, - { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, - { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, - { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, - { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, - { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, - { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, - { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, - { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, - { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, - { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, - { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, - { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, - { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, - { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, - { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, - { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, - { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, - { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, - { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, - { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, - { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, - { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, - { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, - { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, - { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, - { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, - { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, - { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, - { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, - { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, - { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, - { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, - { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, - { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, - { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, - { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, - { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, - { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, - { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, - { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, - { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, - { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, - { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, - { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, - { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, - { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, - { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, -] - -[[package]] -name = "safetensors" -version = "0.8.0rc1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/a1/d46f642820c00443e86343b9548647a82c78f8818857652084852757f805/safetensors-0.8.0rc1.tar.gz", hash = "sha256:a4bacbcd2ab9efe4eb5f1ea44afc9ac5f3b40e103ebde146e370e885ba46f2fc", size = 325883, upload-time = "2026-06-01T09:54:53.914Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/6c/3bf8f6b52f47d15a1d922378893a4b6b7885847ea5ad84b2b4a18ea10f85/safetensors-0.8.0rc1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:7e57730ae523085fda4a80eef74ad40c6d67af60b38498d9995cc9fcb639103b", size = 473516, upload-time = "2026-06-01T09:54:47.735Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5d/2aa9139997bf1681778b96188522480c862af0b4efd978eaae5171296105/safetensors-0.8.0rc1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ba66ebb7eaa5914ff41cd2b2cd7ccd22c844854be2a5179289e748c70ffa90a4", size = 484485, upload-time = "2026-06-01T09:54:46.523Z" }, - { url = "https://files.pythonhosted.org/packages/01/07/c78d1bb09f83885467d4472b14598efc933bd37e0de443f18d23758b0ff3/safetensors-0.8.0rc1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a72817e309ed17a6b805168bca500af711c8bf50cccf7bf790ad247788c676c", size = 503240, upload-time = "2026-06-01T09:54:37.393Z" }, - { url = "https://files.pythonhosted.org/packages/a8/1a/5ef0b186d4edda2ecdd1b4f4b384c03afa64ce17b4582c33329c32bad9ca/safetensors-0.8.0rc1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c945f1ec6fc5a04abc174e79bce69c4613ae536c5276a3812a2a89b62ae009d7", size = 511782, upload-time = "2026-06-01T09:54:38.903Z" }, - { url = "https://files.pythonhosted.org/packages/ac/30/699638f35a524de781ba16cc5c54f31430fd04387960b1d7dc0d5d5fa305/safetensors-0.8.0rc1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba0397739b71400eab5d1f492d68484595cf8b5d13dbfef274e3bcf518348bd8", size = 633524, upload-time = "2026-06-01T09:54:40.169Z" }, - { url = "https://files.pythonhosted.org/packages/4f/bc/12ecd32fec076e836c25e57ac991e09d26fd448d361228057d3d5b3e4d1b/safetensors-0.8.0rc1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f61b7d2c1babc6271d778138788c57c8a95898be6ad3b559971fce20ec1c9c23", size = 545342, upload-time = "2026-06-01T09:54:42.585Z" }, - { url = "https://files.pythonhosted.org/packages/0c/8f/23e0eca29cbaba9f89917e6ca2840f6a80bbcdf659a0e2cd59d311549b7b/safetensors-0.8.0rc1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53f59971f435fb5c23bb7bfa3c00e6cdbb26486d41f3aaf04c6d9e2d91bc8e4c", size = 516088, upload-time = "2026-06-01T09:54:45.356Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/490c76d05f9dd7041bdf29d72b43084ed1498dadebaca5955491e488f2be/safetensors-0.8.0rc1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:b070ba9428e6b2b3b820152b1e1583931cfbd692cda595442d5fbc8b5a46e824", size = 513718, upload-time = "2026-06-01T09:54:41.477Z" }, - { url = "https://files.pythonhosted.org/packages/34/e5/6ccbe61ac09b831b65b3d44239758bebe857a4c4d072a731472f2cd7e131/safetensors-0.8.0rc1-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:69f73c2ec4f76e89deaefe485fec0c6fc42c04a70e318aaefd235da8edacccb1", size = 560058, upload-time = "2026-06-01T09:54:43.954Z" }, - { url = "https://files.pythonhosted.org/packages/73/37/1c2cd01fcf86703433bd2df7371f6d864449d31bfc0289d2575d9e4b001c/safetensors-0.8.0rc1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87aad206a0bb02fa3ddaeb2feb1c6f7f39a87bea1976750ba28a857fbfcd6b31", size = 678511, upload-time = "2026-06-01T09:54:48.861Z" }, - { url = "https://files.pythonhosted.org/packages/99/55/15c17603d8b575253431a574af4ffff447550a9ad6023f2ea643dad7af47/safetensors-0.8.0rc1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6ea00be4f7066ba834064fd7b104ba4b10d2e42cd915c9ece31f0e2b42e6b6d2", size = 786778, upload-time = "2026-06-01T09:54:50.231Z" }, - { url = "https://files.pythonhosted.org/packages/64/a2/d049bddaf097c260204f3169184d7507e256a0c7e0e800eec96eba23688d/safetensors-0.8.0rc1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:2445135cda6018a095ed951dec94a2c393f929ee3b7f7a9ca4630db854be6b89", size = 765767, upload-time = "2026-06-01T09:54:51.518Z" }, - { url = "https://files.pythonhosted.org/packages/9b/03/53ecffb459f5c58b8712e9fde57e8e2a011274eb7ec1f8de3db4641039a2/safetensors-0.8.0rc1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cf949f6a37287572de1c47294b36131bf49528436724eec2f96015f75a3d0bc8", size = 722435, upload-time = "2026-06-01T09:54:52.767Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e8/05503308f6b70d0f68b5a70bb24a4022f5630b68ddec4c114e7c0e24dd68/safetensors-0.8.0rc1-cp310-abi3-win32.whl", hash = "sha256:4633355aaa0da80e789cb7c014c462b6dabe0ecc5f5bb56738fca01511b99975", size = 342496, upload-time = "2026-06-01T09:54:57.119Z" }, - { url = "https://files.pythonhosted.org/packages/f9/23/1c28c20641d222633bddc03bde0b4f719fb8430cf016969683e332fa06e1/safetensors-0.8.0rc1-cp310-abi3-win_amd64.whl", hash = "sha256:d62fad383627979d80b640174c679f3304b3a21614c4d8e71f76910aecac9c8d", size = 355530, upload-time = "2026-06-01T09:54:56.129Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ad/f8620b79a66144de4e9d018ff135b90abeeb11556947737f9657fb27f335/safetensors-0.8.0rc1-cp310-abi3-win_arm64.whl", hash = "sha256:2b8ce46f7f16376eaf7527ee1dc830a32f74542767c0779f446f76d0be6b5da8", size = 340439, upload-time = "2026-06-01T09:54:54.948Z" }, -] - -[[package]] -name = "scikit-image" -version = "0.26.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "imageio" }, - { name = "lazy-loader" }, - { name = "networkx" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "scipy" }, - { name = "tifffile" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" }, - { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" }, - { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" }, - { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" }, - { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" }, - { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" }, - { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" }, - { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" }, - { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" }, - { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" }, - { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" }, - { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" }, - { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" }, - { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" }, - { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" }, - { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" }, - { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" }, - { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" }, - { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" }, - { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810, upload-time = "2025-12-20T17:11:47.357Z" }, - { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717, upload-time = "2025-12-20T17:11:49.483Z" }, - { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520, upload-time = "2025-12-20T17:11:51.58Z" }, - { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340, upload-time = "2025-12-20T17:11:53.708Z" }, - { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839, upload-time = "2025-12-20T17:11:55.89Z" }, - { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021, upload-time = "2025-12-20T17:11:58.014Z" }, - { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490, upload-time = "2025-12-20T17:12:00.013Z" }, - { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782, upload-time = "2025-12-20T17:12:01.983Z" }, - { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060, upload-time = "2025-12-20T17:12:03.886Z" }, - { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628, upload-time = "2025-12-20T17:12:05.921Z" }, - { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369, upload-time = "2025-12-20T17:12:07.912Z" }, - { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431, upload-time = "2025-12-20T17:12:10.255Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362, upload-time = "2025-12-20T17:12:12.793Z" }, - { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151, upload-time = "2025-12-20T17:12:14.909Z" }, - { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484, upload-time = "2025-12-20T17:12:17.046Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501, upload-time = "2025-12-20T17:12:19.339Z" }, -] - -[[package]] -name = "scipy" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, - { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, - { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, - { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, - { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, - { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, - { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, - { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, - { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, - { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, - { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, - { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, - { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, - { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, - { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, - { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, - { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, - { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, - { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, - { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, - { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, -] - -[[package]] -name = "sentencepiece" -version = "0.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/15/2e7a025fc62d764b151ae6d0f2a92f8081755ebe8d4a64099accc6f77ba6/sentencepiece-0.2.1.tar.gz", hash = "sha256:8138cec27c2f2282f4a34d9a016e3374cd40e5c6e9cb335063db66a0a3b71fad", size = 3228515, upload-time = "2025-08-12T07:00:51.718Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/be/32ce495aa1d0e0c323dcb1ba87096037358edee539cac5baf8755a6bd396/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57cae326c8727de58c85977b175af132a7138d84c764635d7e71bbee7e774133", size = 1943152, upload-time = "2025-08-12T06:59:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/88/7e/ff23008899a58678e98c6ff592bf4d368eee5a71af96d0df6b38a039dd4f/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:56dd39a3c4d6493db3cdca7e8cc68c6b633f0d4195495cbadfcf5af8a22d05a6", size = 1325651, upload-time = "2025-08-12T06:59:41.536Z" }, - { url = "https://files.pythonhosted.org/packages/19/84/42eb3ce4796777a1b5d3699dfd4dca85113e68b637f194a6c8d786f16a04/sentencepiece-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9381351182ff9888cc80e41c632e7e274b106f450de33d67a9e8f6043da6f76", size = 1253645, upload-time = "2025-08-12T06:59:42.903Z" }, - { url = "https://files.pythonhosted.org/packages/89/fa/d3d5ebcba3cb9e6d3775a096251860c41a6bc53a1b9461151df83fe93255/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99f955df238021bf11f0fc37cdb54fd5e5b5f7fd30ecc3d93fb48b6815437167", size = 1316273, upload-time = "2025-08-12T06:59:44.476Z" }, - { url = "https://files.pythonhosted.org/packages/04/88/14f2f4a2b922d8b39be45bf63d79e6cd3a9b2f248b2fcb98a69b12af12f5/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdfecef430d985f1c2bcbfff3defd1d95dae876fbd0173376012d2d7d24044b", size = 1387881, upload-time = "2025-08-12T06:59:46.09Z" }, - { url = "https://files.pythonhosted.org/packages/fd/b8/903e5ccb77b4ef140605d5d71b4f9e0ad95d456d6184688073ed11712809/sentencepiece-0.2.1-cp312-cp312-win32.whl", hash = "sha256:a483fd29a34c3e34c39ac5556b0a90942bec253d260235729e50976f5dba1068", size = 999540, upload-time = "2025-08-12T06:59:48.023Z" }, - { url = "https://files.pythonhosted.org/packages/2d/81/92df5673c067148c2545b1bfe49adfd775bcc3a169a047f5a0e6575ddaca/sentencepiece-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4cdc7c36234fda305e85c32949c5211faaf8dd886096c7cea289ddc12a2d02de", size = 1054671, upload-time = "2025-08-12T06:59:49.895Z" }, - { url = "https://files.pythonhosted.org/packages/fe/02/c5e3bc518655d714622bec87d83db9cdba1cd0619a4a04e2109751c4f47f/sentencepiece-0.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:daeb5e9e9fcad012324807856113708614d534f596d5008638eb9b40112cd9e4", size = 1033923, upload-time = "2025-08-12T06:59:51.952Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4a/85fbe1706d4d04a7e826b53f327c4b80f849cf1c7b7c5e31a20a97d8f28b/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dcd8161eee7b41aae57ded06272905dbd680a0a04b91edd0f64790c796b2f706", size = 1943150, upload-time = "2025-08-12T06:59:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/c2/83/4cfb393e287509fc2155480b9d184706ef8d9fa8cbf5505d02a5792bf220/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c6c8f42949f419ff8c7e9960dbadcfbc982d7b5efc2f6748210d3dd53a7de062", size = 1325651, upload-time = "2025-08-12T06:59:55.073Z" }, - { url = "https://files.pythonhosted.org/packages/8d/de/5a007fb53b1ab0aafc69d11a5a3dd72a289d5a3e78dcf2c3a3d9b14ffe93/sentencepiece-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:097f3394e99456e9e4efba1737c3749d7e23563dd1588ce71a3d007f25475fff", size = 1253641, upload-time = "2025-08-12T06:59:56.562Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d2/f552be5928105588f4f4d66ee37dd4c61460d8097e62d0e2e0eec41bc61d/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7b670879c370d350557edabadbad1f6561a9e6968126e6debca4029e5547820", size = 1316271, upload-time = "2025-08-12T06:59:58.109Z" }, - { url = "https://files.pythonhosted.org/packages/96/df/0cfe748ace5485be740fed9476dee7877f109da32ed0d280312c94ec259f/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7f0fd2f2693309e6628aeeb2e2faf6edd221134dfccac3308ca0de01f8dab47", size = 1387882, upload-time = "2025-08-12T07:00:00.701Z" }, - { url = "https://files.pythonhosted.org/packages/ac/dd/f7774d42a881ced8e1739f393ab1e82ece39fc9abd4779e28050c2e975b5/sentencepiece-0.2.1-cp313-cp313-win32.whl", hash = "sha256:92b3816aa2339355fda2c8c4e021a5de92180b00aaccaf5e2808972e77a4b22f", size = 999541, upload-time = "2025-08-12T07:00:02.709Z" }, - { url = "https://files.pythonhosted.org/packages/dd/e9/932b9eae6fd7019548321eee1ab8d5e3b3d1294df9d9a0c9ac517c7b636d/sentencepiece-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:10ed3dab2044c47f7a2e7b4969b0c430420cdd45735d78c8f853191fa0e3148b", size = 1054669, upload-time = "2025-08-12T07:00:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/c9/3a/76488a00ea7d6931689cda28726a1447d66bf1a4837943489314593d5596/sentencepiece-0.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac650534e2251083c5f75dde4ff28896ce7c8904133dc8fef42780f4d5588fcd", size = 1033922, upload-time = "2025-08-12T07:00:06.496Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b6/08fe2ce819e02ccb0296f4843e3f195764ce9829cbda61b7513f29b95718/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8dd4b477a7b069648d19363aad0cab9bad2f4e83b2d179be668efa672500dc94", size = 1946052, upload-time = "2025-08-12T07:00:08.136Z" }, - { url = "https://files.pythonhosted.org/packages/ab/d9/1ea0e740591ff4c6fc2b6eb1d7510d02f3fb885093f19b2f3abd1363b402/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c0f672da370cc490e4c59d89e12289778310a0e71d176c541e4834759e1ae07", size = 1327408, upload-time = "2025-08-12T07:00:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/99/7e/1fb26e8a21613f6200e1ab88824d5d203714162cf2883248b517deb500b7/sentencepiece-0.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad8493bea8432dae8d6830365352350f3b4144415a1d09c4c8cb8d30cf3b6c3c", size = 1254857, upload-time = "2025-08-12T07:00:11.021Z" }, - { url = "https://files.pythonhosted.org/packages/bc/85/c72fd1f3c7a6010544d6ae07f8ddb38b5e2a7e33bd4318f87266c0bbafbf/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b81a24733726e3678d2db63619acc5a8dccd074f7aa7a54ecd5ca33ca6d2d596", size = 1315722, upload-time = "2025-08-12T07:00:12.989Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e8/661e5bd82a8aa641fd6c1020bd0e890ef73230a2b7215ddf9c8cd8e941c2/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a81799d0a68d618e89063fb423c3001a034c893069135ffe51fee439ae474d6", size = 1387452, upload-time = "2025-08-12T07:00:15.088Z" }, - { url = "https://files.pythonhosted.org/packages/99/5e/ae66c361023a470afcbc1fbb8da722c72ea678a2fcd9a18f1a12598c7501/sentencepiece-0.2.1-cp313-cp313t-win32.whl", hash = "sha256:89a3ea015517c42c0341d0d962f3e6aaf2cf10d71b1932d475c44ba48d00aa2b", size = 1002501, upload-time = "2025-08-12T07:00:16.966Z" }, - { url = "https://files.pythonhosted.org/packages/c1/03/d332828c4ff764e16c1b56c2c8f9a33488bbe796b53fb6b9c4205ddbf167/sentencepiece-0.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:33f068c9382dc2e7c228eedfd8163b52baa86bb92f50d0488bf2b7da7032e484", size = 1057555, upload-time = "2025-08-12T07:00:18.573Z" }, - { url = "https://files.pythonhosted.org/packages/88/14/5aee0bf0864df9bd82bd59e7711362908e4935e3f9cdc1f57246b5d5c9b9/sentencepiece-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:b3616ad246f360e52c85781e47682d31abfb6554c779e42b65333d4b5f44ecc0", size = 1036042, upload-time = "2025-08-12T07:00:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/24/9c/89eb8b2052f720a612478baf11c8227dcf1dc28cd4ea4c0c19506b5af2a2/sentencepiece-0.2.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5d0350b686c320068702116276cfb26c066dc7e65cfef173980b11bb4d606719", size = 1943147, upload-time = "2025-08-12T07:00:21.809Z" }, - { url = "https://files.pythonhosted.org/packages/82/0b/a1432bc87f97c2ace36386ca23e8bd3b91fb40581b5e6148d24b24186419/sentencepiece-0.2.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c7f54a31cde6fa5cb030370566f68152a742f433f8d2be458463d06c208aef33", size = 1325624, upload-time = "2025-08-12T07:00:23.289Z" }, - { url = "https://files.pythonhosted.org/packages/ea/99/bbe054ebb5a5039457c590e0a4156ed073fb0fe9ce4f7523404dd5b37463/sentencepiece-0.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c83b85ab2d6576607f31df77ff86f28182be4a8de6d175d2c33ca609925f5da1", size = 1253670, upload-time = "2025-08-12T07:00:24.69Z" }, - { url = "https://files.pythonhosted.org/packages/19/ad/d5c7075f701bd97971d7c2ac2904f227566f51ef0838dfbdfdccb58cd212/sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1855f57db07b51fb51ed6c9c452f570624d2b169b36f0f79ef71a6e6c618cd8b", size = 1316247, upload-time = "2025-08-12T07:00:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/fb/03/35fbe5f3d9a7435eebd0b473e09584bd3cc354ce118b960445b060d33781/sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01e6912125cb45d3792f530a4d38f8e21bf884d6b4d4ade1b2de5cf7a8d2a52b", size = 1387894, upload-time = "2025-08-12T07:00:28.339Z" }, - { url = "https://files.pythonhosted.org/packages/dc/aa/956ef729aafb6c8f9c443104c9636489093bb5c61d6b90fc27aa1a865574/sentencepiece-0.2.1-cp314-cp314-win32.whl", hash = "sha256:c415c9de1447e0a74ae3fdb2e52f967cb544113a3a5ce3a194df185cbc1f962f", size = 1096698, upload-time = "2025-08-12T07:00:29.764Z" }, - { url = "https://files.pythonhosted.org/packages/b8/cb/fe400d8836952cc535c81a0ce47dc6875160e5fedb71d2d9ff0e9894c2a6/sentencepiece-0.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:881b2e44b14fc19feade3cbed314be37de639fc415375cefaa5bc81a4be137fd", size = 1155115, upload-time = "2025-08-12T07:00:32.865Z" }, - { url = "https://files.pythonhosted.org/packages/32/89/047921cf70f36c7b6b6390876b2399b3633ab73b8d0cb857e5a964238941/sentencepiece-0.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:2005242a16d2dc3ac5fe18aa7667549134d37854823df4c4db244752453b78a8", size = 1133890, upload-time = "2025-08-12T07:00:34.763Z" }, - { url = "https://files.pythonhosted.org/packages/a1/11/5b414b9fae6255b5fb1e22e2ed3dc3a72d3a694e5703910e640ac78346bb/sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a19adcec27c524cb7069a1c741060add95f942d1cbf7ad0d104dffa0a7d28a2b", size = 1946081, upload-time = "2025-08-12T07:00:36.97Z" }, - { url = "https://files.pythonhosted.org/packages/77/eb/7a5682bb25824db8545f8e5662e7f3e32d72a508fdce086029d89695106b/sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e37e4b4c4a11662b5db521def4e44d4d30ae69a1743241412a93ae40fdcab4bb", size = 1327406, upload-time = "2025-08-12T07:00:38.669Z" }, - { url = "https://files.pythonhosted.org/packages/03/b0/811dae8fb9f2784e138785d481469788f2e0d0c109c5737372454415f55f/sentencepiece-0.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:477c81505db072b3ab627e7eab972ea1025331bd3a92bacbf798df2b75ea86ec", size = 1254846, upload-time = "2025-08-12T07:00:40.611Z" }, - { url = "https://files.pythonhosted.org/packages/ef/23/195b2e7ec85ebb6a547969f60b723c7aca5a75800ece6cc3f41da872d14e/sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:010f025a544ef770bb395091d57cb94deb9652d8972e0d09f71d85d5a0816c8c", size = 1315721, upload-time = "2025-08-12T07:00:42.914Z" }, - { url = "https://files.pythonhosted.org/packages/7e/aa/553dbe4178b5f23eb28e59393dddd64186178b56b81d9b8d5c3ff1c28395/sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:733e59ff1794d26db706cd41fc2d7ca5f6c64a820709cb801dc0ea31780d64ab", size = 1387458, upload-time = "2025-08-12T07:00:44.56Z" }, - { url = "https://files.pythonhosted.org/packages/66/7c/08ff0012507297a4dd74a5420fdc0eb9e3e80f4e88cab1538d7f28db303d/sentencepiece-0.2.1-cp314-cp314t-win32.whl", hash = "sha256:d3233770f78e637dc8b1fda2cd7c3b99ec77e7505041934188a4e7fe751de3b0", size = 1099765, upload-time = "2025-08-12T07:00:46.058Z" }, - { url = "https://files.pythonhosted.org/packages/91/d5/2a69e1ce15881beb9ddfc7e3f998322f5cedcd5e4d244cb74dade9441663/sentencepiece-0.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e4366c97b68218fd30ea72d70c525e6e78a6c0a88650f57ac4c43c63b234a9d", size = 1157807, upload-time = "2025-08-12T07:00:47.673Z" }, - { url = "https://files.pythonhosted.org/packages/f3/16/54f611fcfc2d1c46cbe3ec4169780b2cfa7cf63708ef2b71611136db7513/sentencepiece-0.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:105e36e75cbac1292642045458e8da677b2342dcd33df503e640f0b457cb6751", size = 1136264, upload-time = "2025-08-12T07:00:49.485Z" }, -] - -[[package]] -name = "setuptools" -version = "81.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, -] - -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, -] - -[[package]] -name = "simsimd" -version = "6.5.16" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/8c/070a179eb509b689509dacbd0bc81aa2e36614aff2c8aa6dc6c440886206/simsimd-6.5.16.tar.gz", hash = "sha256:0a005c6e2dacec83f235a747f7dbecca46b5d4d1e183ecc1929ca556ee7d7564", size = 187216, upload-time = "2026-03-07T14:36:23.191Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/b8/53f89ca12a3526b86c4221de68497d2b1f4c3f7f6b47d8c153ef14c67d15/simsimd-6.5.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f8a207a23bc9060a46b234ec304a712f1cbb0a240d18b484bad5cabf0d01746", size = 105152, upload-time = "2026-03-07T14:34:52.203Z" }, - { url = "https://files.pythonhosted.org/packages/56/4f/0fa014163c6b846182f6355ebfc24f79e86ced7a2cce0ca95ba711f19e04/simsimd-6.5.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51c6b0ad0078f8c6b4d3ae4ec256bcf861c2bf5909d4567440b86f9ad7f94fd3", size = 94599, upload-time = "2026-03-07T14:34:53.564Z" }, - { url = "https://files.pythonhosted.org/packages/fb/3c/35266c8d128ea42706d9436b54994039e2659fb37ed28f1c62e123a86631/simsimd-6.5.16-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13b8af340ad5cc1311cae6f8d778aef80bff1922260dee1a17ca60878eaac466", size = 385042, upload-time = "2026-03-07T14:34:54.873Z" }, - { url = "https://files.pythonhosted.org/packages/3c/28/7ae846998728326759eab771afd83ad721b6c10e9cef7da2b5ca9bdd4a7b/simsimd-6.5.16-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12ae4f5f2ade1152d2d3a0094f56fae636204d40595b385ea9b304410647a353", size = 583515, upload-time = "2026-03-07T14:34:56.578Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b9/3a5717c988b6093a5fb15484754f7ffe5451a7559f3c1d5f2b3183199441/simsimd-6.5.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97bcda199d4be8f4372af6b781e96e7e8cd1838ce256a83deef75ac660dcd464", size = 421418, upload-time = "2026-03-07T14:34:58.316Z" }, - { url = "https://files.pythonhosted.org/packages/bb/65/e218050eb89390c64ddc327f36da8e3b471483f11c0f3683c2bf891d2dab/simsimd-6.5.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a59ef1ab3d0f6d4f1dcac43e1b2db9b8e73c00e72714716e061bfd27dde2d652", size = 619558, upload-time = "2026-03-07T14:34:59.847Z" }, - { url = "https://files.pythonhosted.org/packages/45/89/a45ef421b70d557eac7d196b03e45ff9ff8c7c786b4e54dfb505c1efc0f1/simsimd-6.5.16-cp312-cp312-win_amd64.whl", hash = "sha256:e0ae95b0fe17c62532ecc66f03f6e9354641448249efabe6332eed0f5819150d", size = 87454, upload-time = "2026-03-07T14:35:01.778Z" }, - { url = "https://files.pythonhosted.org/packages/1d/68/620c859f8737990371f79a45e3dc7135374635011c89b9e403cadd746639/simsimd-6.5.16-cp312-cp312-win_arm64.whl", hash = "sha256:fcfcc79473141f42b1db05037cb626e196ed20cffa7f768d4cad34b2a1239965", size = 62912, upload-time = "2026-03-07T14:35:03.133Z" }, - { url = "https://files.pythonhosted.org/packages/1b/f2/e1dedb4b3644c76467c84ffb57fc6e7784f46f312c34be9d6b52144e3d90/simsimd-6.5.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0af914ab13741744ea1bd3521e719226633f2ab082dc5b07790c61685d88558", size = 105157, upload-time = "2026-03-07T14:35:04.455Z" }, - { url = "https://files.pythonhosted.org/packages/46/21/a52af2040ad608cc236583ada58b0bfa5ffbfdc83b1d3565f4793f28cade/simsimd-6.5.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:683f758d0261b3d8790f8c9fc63fdc64b7af4db66b59ba7a31556a755cb38df7", size = 94604, upload-time = "2026-03-07T14:35:05.982Z" }, - { url = "https://files.pythonhosted.org/packages/e1/39/c6c7f66368204f0aa544aa074fa84b42a4146cf9e4bc79c3896c155d9abc/simsimd-6.5.16-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc1e29d8fed1c2b89338062fa17283b78181c84d2b024cc9bf7ed75402810bfc", size = 385102, upload-time = "2026-03-07T14:35:07.32Z" }, - { url = "https://files.pythonhosted.org/packages/2d/f2/6d84388c6e0f0637321149bc84bbbfa54a12f65f29bc6a007dd1403bf6f7/simsimd-6.5.16-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec7e92323c820935475bc9ec84938eecc9d9bc625055ff057a6d0dcfffb7eb2a", size = 583601, upload-time = "2026-03-07T14:35:08.799Z" }, - { url = "https://files.pythonhosted.org/packages/14/53/26bf42b6f8ec1f5680d91e95e276e49662bc1b8e0522c4861a0c3349b7ba/simsimd-6.5.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5a4be386421726204f70e9f8601dc8818fc2df0032ef6dcd218cdf224a9fce18", size = 421445, upload-time = "2026-03-07T14:35:10.298Z" }, - { url = "https://files.pythonhosted.org/packages/67/05/31b5247c0e17cd82482fd1724881d49ce442ad6affb2776efae8f9cc4835/simsimd-6.5.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe922886957645e041618fddf242a89f5f7ded0c4bee13dc6537f749ccf75ba2", size = 619612, upload-time = "2026-03-07T14:35:12.109Z" }, - { url = "https://files.pythonhosted.org/packages/5d/54/dbc23d585a57c9b0e71ab10705c4121ce91a807df374d433dd86fb438caa/simsimd-6.5.16-cp313-cp313-win_amd64.whl", hash = "sha256:fe7a0fa49b09651cc1721f5928fa68665f4957c492937241bbdd6ed040dc4a5d", size = 87460, upload-time = "2026-03-07T14:35:13.948Z" }, - { url = "https://files.pythonhosted.org/packages/a3/3c/62a41c182ab6f7abfbfe8941fa12d08b8235b4498e988e5d1f29ac21504f/simsimd-6.5.16-cp313-cp313-win_arm64.whl", hash = "sha256:3fc01992b9d3be84d4826c0d9f8a894668ad931285c09f74bdbe61a5400c9f4d", size = 62922, upload-time = "2026-03-07T14:35:15.252Z" }, - { url = "https://files.pythonhosted.org/packages/df/df/6a1b62074968bbd2976611ac9f89fa60bde2c0c3171f1eb303314bd2bb40/simsimd-6.5.16-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:22624893c86cb9f07968a7e471ed81b2e59f68ba4941cea69ee7418b5cc6fe8e", size = 105335, upload-time = "2026-03-07T14:35:16.839Z" }, - { url = "https://files.pythonhosted.org/packages/09/4f/43bf19becc155e5efdd31dc220c1bd34f866172739a7a081a8bfa2cae840/simsimd-6.5.16-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10d8b32ecee86a86fe30abb35a7c47c1d76756838355bc4377b73bdc69d16ed4", size = 94782, upload-time = "2026-03-07T14:35:18.233Z" }, - { url = "https://files.pythonhosted.org/packages/27/91/c31085edffdc81343f81b937fb2930cd0e105cfab1b9b97845c45b3621c3/simsimd-6.5.16-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b5a632299ee145fa2eab53906922d1596ee63f5a182e3741cde9b18745afe68", size = 387117, upload-time = "2026-03-07T14:35:19.573Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a6/faaf1633cf9d3fc5ebe46d2f145f42257accc6bd25420d722702b6b5adfb/simsimd-6.5.16-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:40a7e14e02acebd0cdadc88c3eeb262c6cbff550a10d4bce2c7771756cf68658", size = 275340, upload-time = "2026-03-07T15:13:14.926Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c8/3c3fa982272ab7a5943ceacc04fd64a38d408fce2cd45e7890eb932e92d1/simsimd-6.5.16-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4b878a28a338c30768cb401f4fbb79bd5b911d95ca024717077f1c57746ad78", size = 297257, upload-time = "2026-03-07T15:13:17.076Z" }, - { url = "https://files.pythonhosted.org/packages/46/8c/81e83b57992f1ae1bb3fa3d55cd1c4a5bd5dafcec6bd44273eb59c8f8f79/simsimd-6.5.16-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:639bb66dbb15da8727267dc7b7fbf7cc59c18ccef901dd83cdff4f12651f0244", size = 286880, upload-time = "2026-03-07T15:13:19.428Z" }, - { url = "https://files.pythonhosted.org/packages/5e/96/de52bf9ffff59c71b9bc672d7a539c431d81a17d909c4ee734f7731b51d2/simsimd-6.5.16-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:999acb24a43c619af6217b513536ae28bfe23c8fa170a4120a3cca7fdd22acff", size = 585133, upload-time = "2026-03-07T14:35:21.53Z" }, - { url = "https://files.pythonhosted.org/packages/84/e8/190aead5370bc3e0bd0f5fbd938a27cac4678dd903e81ff16acae7d7c6e4/simsimd-6.5.16-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8524c7fd12f7ef9b97e824c65db4e89919b7cc8d530780119b3417ce8643a3c2", size = 422963, upload-time = "2026-03-07T14:35:23.137Z" }, - { url = "https://files.pythonhosted.org/packages/c0/26/d6ecb102a16f01ea22e98bbf8da37b9a8cb4fb38459b939367afb401f1c4/simsimd-6.5.16-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:973460e647b3f769e714caa40b64f56dcf95a4afca98cdd19e2c3c1c9527e438", size = 320199, upload-time = "2026-03-07T15:13:21.823Z" }, - { url = "https://files.pythonhosted.org/packages/07/08/920d1619df54ed2c377dbfb10e0a561e27731091995ba1093b600ed3f00c/simsimd-6.5.16-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:141437e4d727872ab50fe3b19098816aee23b8c3519ee04c9831ef0326e444e1", size = 340041, upload-time = "2026-03-07T15:13:23.91Z" }, - { url = "https://files.pythonhosted.org/packages/0d/3e/995e875eca129b1acb35e4824f1f4fab30b8393da80d51883552e2edd60f/simsimd-6.5.16-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3daee137ffc2dd8bbe64b7f0f95ca2b2302b2985c35a6a7be61626052aa74e5d", size = 317465, upload-time = "2026-03-07T15:13:25.697Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ce/892865784240c167624bf55f835ff74d52e24c7d7f1b9aa79f77358397ac/simsimd-6.5.16-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:03f4d0a8aff48160e3b0acb44ac5525a39d26348db907d6d5ef516369b309973", size = 620749, upload-time = "2026-03-07T14:35:25.077Z" }, - { url = "https://files.pythonhosted.org/packages/54/0d/b74a391fefe7d349230b58b1d0fe6d401d1625553b5375a99608f9d228a9/simsimd-6.5.16-cp313-cp313t-win_amd64.whl", hash = "sha256:01ef2ff8cf99fc3a8e23fb2cadc06b6aa4df9b5e6d001b184d42cf403b1cdc16", size = 87630, upload-time = "2026-03-07T14:35:26.598Z" }, - { url = "https://files.pythonhosted.org/packages/eb/ac/004dc381de9ac6634c785d0284dba8d1f12018584ddd992c09d9f85454b9/simsimd-6.5.16-cp313-cp313t-win_arm64.whl", hash = "sha256:a152c559298bae402ed8205b604e5b0418a2ce8a61a6a87f14973e53b68d5f6a", size = 63126, upload-time = "2026-03-07T14:35:28.295Z" }, - { url = "https://files.pythonhosted.org/packages/6b/5a/b70d670c67ca3d0284b4a52e32d65eb9767df51c0ff5b968db6a2bdc406c/simsimd-6.5.16-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c70924ce14c7ed1663ff131f34bdf3987042f569b41a4ed756a1ad65109de760", size = 105215, upload-time = "2026-03-07T14:35:29.677Z" }, - { url = "https://files.pythonhosted.org/packages/cd/16/59a7d17719a49d453d35a21d2fc40bd7915f78046f82b3325f1f5629505a/simsimd-6.5.16-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cfa1237885074a8e8aba7c203d82e189b84760ffa946fb53e82ece762f40f36c", size = 94618, upload-time = "2026-03-07T14:35:31.395Z" }, - { url = "https://files.pythonhosted.org/packages/02/75/8cb99c018b1c68b5048e19df9d4552d5f41f0512f2e32fdd6a5e58a5b2d1/simsimd-6.5.16-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ecf8eb87e39a72e23126bf7ffa1a454830ec2daddd00ac89cef96aefce788a7", size = 385337, upload-time = "2026-03-07T14:35:32.863Z" }, - { url = "https://files.pythonhosted.org/packages/79/48/0fd0017b306422d950758e8077e00295d5d9dc2add4680c0aad437774128/simsimd-6.5.16-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0029256c39bafc3930884b47280628ff84a8eda3b7b55e64465f0e051df93cb8", size = 583769, upload-time = "2026-03-07T14:35:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/03/4e/803bffa17b5d52bd545b906f28d947630f271d6a4dc53324d5177464babe/simsimd-6.5.16-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9afa80898b89cdb65317ca6f36efedb3320a000205a82b70dd2ea82872482d08", size = 421581, upload-time = "2026-03-07T14:35:36.719Z" }, - { url = "https://files.pythonhosted.org/packages/59/59/93bbf9c1a6b554b4cf21b32f436fc0de082fe929c4c459d295292ee8bcce/simsimd-6.5.16-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fc6b72bf5a62afa66a9b51f6a01d751d8f217c9f7d4b1ea094e495c3dce87c33", size = 619710, upload-time = "2026-03-07T14:35:38.338Z" }, - { url = "https://files.pythonhosted.org/packages/04/4c/207158749eb6ad8576a1b3cd4e80b7f1e2a0fc59fb2b0730f8df43b3d4a9/simsimd-6.5.16-cp314-cp314-win_amd64.whl", hash = "sha256:96fdb750432ad6478177fb80612b3aea2da002dff613f1fddd19334da9b7f25e", size = 90117, upload-time = "2026-03-07T14:35:40.495Z" }, - { url = "https://files.pythonhosted.org/packages/66/67/38ab856761cc62fbb92b328350a6652f87b27ab2ca1d49fa934aaeca0d3c/simsimd-6.5.16-cp314-cp314-win_arm64.whl", hash = "sha256:2e3981bfa3f09fa9fac845037df7c3a684e0538ff297d3b2ccd26a2eed243f80", size = 64908, upload-time = "2026-03-07T14:35:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/62/49/df617f9e5605b48b75d921b5361c88475879b95a43dd3f2b77fb4659382a/simsimd-6.5.16-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:864a0497c8d4bdc6948bedb016836ba777d14a93300c3735c6e84444241cd66e", size = 105371, upload-time = "2026-03-07T14:35:43.552Z" }, - { url = "https://files.pythonhosted.org/packages/45/3f/e0b8064146919d40436503032f331fc92fbd3d8e5b29ca01c40a675432cf/simsimd-6.5.16-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:492b86704d942fa3ec627523ba7f40e87203e4222d498aa6fc880a865e13fa76", size = 94790, upload-time = "2026-03-07T14:35:44.914Z" }, - { url = "https://files.pythonhosted.org/packages/74/6f/b3811e96e6582e4f04793b688eb1f85e2a74722f00089dc5c7932023d523/simsimd-6.5.16-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c4e0e257e191c2e1ac94737901ec3771b076f7b9c032b620c0bfb747ecefcd9", size = 387243, upload-time = "2026-03-07T14:35:46.408Z" }, - { url = "https://files.pythonhosted.org/packages/47/5b/46b52cd8df732e73799adb91af16e2bc872e597349b01f456df3008d4dd7/simsimd-6.5.16-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ed0eec1d7d5124bc86256a8d7ac81b1c6363149e1f1cc957007418da04e8ed", size = 585270, upload-time = "2026-03-07T14:35:48.885Z" }, - { url = "https://files.pythonhosted.org/packages/9d/0b/92c7dc6b6478032cde9d65f997e8135f5e178c455b8585877b3a9f996bf7/simsimd-6.5.16-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b331c7c2222bc03139e0821c076103ea50f9fab5750571b4cd1e53c2ba3cb0d6", size = 423066, upload-time = "2026-03-07T14:35:50.63Z" }, - { url = "https://files.pythonhosted.org/packages/4d/03/ad761cc350e0f30cd52f798e39434ce68bd09a741e931f4458ddafd0d099/simsimd-6.5.16-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c51b74b8f9b096ddd98beea66e18751ad079c398600d8c877a5d228a1f23d20", size = 620824, upload-time = "2026-03-07T14:35:52.638Z" }, - { url = "https://files.pythonhosted.org/packages/bf/aa/b059b409ae311d4d5e936c07c506c62d5f547597933822fe8c54d32e276b/simsimd-6.5.16-cp314-cp314t-win_amd64.whl", hash = "sha256:4aedebecab2c776177c2db2cdd2f311892d9b1b71bcf66d889539ab1e22ad9a6", size = 90323, upload-time = "2026-03-07T14:35:54.438Z" }, - { url = "https://files.pythonhosted.org/packages/07/3a/2d0a48ef00dd495b5ded82a476ec4300ae3f67496cbd7c7fe2777de89a3c/simsimd-6.5.16-cp314-cp314t-win_arm64.whl", hash = "sha256:d63af5fbd32b0346ef949794451b6c1ec58a66139d3ca22177f93cf7c4be7877", size = 65109, upload-time = "2026-03-07T14:35:55.863Z" }, -] - -[[package]] -name = "soupsieve" -version = "2.8.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, -] - -[[package]] -name = "spandrel" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "einops" }, - { name = "numpy" }, - { name = "safetensors" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torchvision", version = "0.27.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/8f/ab4565c23dd67a036ab72101a830cebd7ca026b2fddf5771bbf6284f6228/spandrel-0.4.2.tar.gz", hash = "sha256:fefa4ea966c6a5b7721dcf24f3e2062a5a96a395c8bedcb570fb55971fdcbccb", size = 247544, upload-time = "2026-02-21T01:52:26.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/74/31/411ea965835534c43d4b98d451968354876e0e867ea1fd42669e4cca0732/spandrel-0.4.2-py3-none-any.whl", hash = "sha256:6c93e3ecbeb0e548fd2df45a605472b34c1614287c56b51bb33cdef7ae5235b5", size = 320811, upload-time = "2026-02-21T01:52:25.015Z" }, -] - -[[package]] -name = "stringzilla" -version = "4.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4d/c0/08c222e1c89871970121f8ead9764f63df925e32879f68bb479c61f7ad89/stringzilla-4.6.1.tar.gz", hash = "sha256:47e9b0b95337857146e0dd4309998af7acab6bdca1f85c069adc1c32bbd57cb0", size = 646515, upload-time = "2026-05-05T15:19:45.003Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/56/a2771ab8979a92db5dda7af3f2737f77f8e1242ab524619875eb4a853c19/stringzilla-4.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6c971a8a86bb4a2adae2a0e9a9afd1bd2dbc56b888d3179de09c43067f232701", size = 212269, upload-time = "2026-05-05T15:17:35.56Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b6/624af99c2c613a22b248eefc79fb272eecd00945f8e172dd520fd55b8d80/stringzilla-4.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921d5cf2ed53d1e4de89f07b7372053b110be7272c40923f4590b82dd9f74f95", size = 199363, upload-time = "2026-05-05T15:17:37.113Z" }, - { url = "https://files.pythonhosted.org/packages/87/21/9aa322a1618f2e63e26211f8c96d91b2eb850bd57faef4fff7dbd44e4778/stringzilla-4.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25454262cd73db2d9718498ea27d61f2cebed89c10c5e8e404049d36502934c5", size = 689869, upload-time = "2026-05-05T15:17:39.102Z" }, - { url = "https://files.pythonhosted.org/packages/6a/db/be3a3d58fc34512de73bc6b84ba0c423e1b5210f07e9c2874ecdd1b5bcaf/stringzilla-4.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:393ca09165194b08664ecd615205d26f2d40666ef971e5bdabb205e713ae2269", size = 657420, upload-time = "2026-05-05T15:17:41.304Z" }, - { url = "https://files.pythonhosted.org/packages/8c/1a/2298271b5b8f35a9fca57841b4f37e60c91e095110dc37691ba32a0d02e0/stringzilla-4.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f7d8bdbe20a61acfffbecf3cab9a741c382f8805cb68e513b78234a614548fb", size = 640708, upload-time = "2026-05-05T15:17:43.798Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3c/86c930bf4ef7ad4f89ac8b6768bd4afd7010c4d3b45db60a1711fc72e89c/stringzilla-4.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd75a62340b09cfedfddf9359bbfc20465c25f24e704331027b994199a34766e", size = 2053991, upload-time = "2026-05-05T15:17:45.879Z" }, - { url = "https://files.pythonhosted.org/packages/6a/f9/41ebe73ab07ab56096c5f76839c98074394f8d1266ad85b850444734cb6d/stringzilla-4.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d50158354618ed1b9bf18172be8ee491af30259e0d156394da9dd41d7400264", size = 644119, upload-time = "2026-05-05T15:17:47.93Z" }, - { url = "https://files.pythonhosted.org/packages/0a/e4/763a653ca1bdef70323c8f064ba09d4437ff52cd85e3ceab128d69b6aeba/stringzilla-4.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:39018a9b33995650fb0e5d144608024629c4e90cc9d47c90eaa4cc8cfdcbdc21", size = 653897, upload-time = "2026-05-05T15:17:50.145Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ff/f07d0896cfee42bae82a98068ff34254f354da872db58ec28a78b596e1e8/stringzilla-4.6.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0a37390c949fbd38a4cfb32c1482faab51322a7af34d2ad9afe71f34f2614c3c", size = 587145, upload-time = "2026-05-05T15:17:51.786Z" }, - { url = "https://files.pythonhosted.org/packages/7e/9d/82d3b696268bc07f3f71d2bb152dd60888047f7ea85cc6da92ca5b14d050/stringzilla-4.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcb8d9e8d5ca26d56c25f04d9d55a864814a8e29ef48987a28e778c5144a3605", size = 626272, upload-time = "2026-05-05T15:17:53.819Z" }, - { url = "https://files.pythonhosted.org/packages/76/ea/667dc6cc4f77514c6286a8ba302c371ad00fc1d36e7b39f41431f4cb564e/stringzilla-4.6.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a1218590cb4d644549e51c17e020cc5b95bd7129f31025c2dd1e234adb2b5817", size = 619119, upload-time = "2026-05-05T15:17:55.915Z" }, - { url = "https://files.pythonhosted.org/packages/2c/cb/fc4648dcaee966bc7f7ee9b6298fe8c758ac8672f76aa58f145ad153c537/stringzilla-4.6.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e351b69d77d45a96293ee065c62376531d58163dd2b74c8db2f53cafbb970896", size = 613387, upload-time = "2026-05-05T15:17:58.017Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8d/057d939d09ef261bdcb5273c00e6a9380d7fe2e6937eef68a2be14aad189/stringzilla-4.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0ddb8e3ab71812a37f34ee15d45688021ae0e971cd337a55a8ebf04a70b883", size = 1909891, upload-time = "2026-05-05T15:17:59.796Z" }, - { url = "https://files.pythonhosted.org/packages/b6/45/8897eff5a8c3e155a2c26639a1c3ba49d5d571b18e5c46abd60642a2ef4f/stringzilla-4.6.1-cp312-cp312-win32.whl", hash = "sha256:4c31cdfcfc4ce304ffb970a353f563d8e668d9819d6c73f0c39c162e7175f83e", size = 114789, upload-time = "2026-05-05T15:18:01.61Z" }, - { url = "https://files.pythonhosted.org/packages/cd/1e/54dd3db9c48bff0de3bd085be57c6efeef0489a8e47e84fdc00f311f4cf5/stringzilla-4.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9beb6cbd5d2de22816d7e3e5503dea2bb47723c0868c9cd4144b0304112b2d8", size = 162550, upload-time = "2026-05-05T15:18:03.177Z" }, - { url = "https://files.pythonhosted.org/packages/af/35/548fc3cb3131b5c6ebba6ede0c40951f358c6f2df71453a9d9843b358f49/stringzilla-4.6.1-cp312-cp312-win_arm64.whl", hash = "sha256:f928181e55f49ff14a4c47c651ff853e5dbaf21d1e5ede1f98fe53a94261346f", size = 123320, upload-time = "2026-05-05T15:18:04.95Z" }, - { url = "https://files.pythonhosted.org/packages/64/37/13e3d79ebe2425769617b95510b5d2a4e1128bc6f2392ceba452b4bf8e45/stringzilla-4.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:30468b0c5953947bda2bba2234f0c03e724c93c89e28f816ab0f694f63fd15d0", size = 212271, upload-time = "2026-05-05T15:18:06.791Z" }, - { url = "https://files.pythonhosted.org/packages/0f/66/a641718b57adc22ba9dffa9c3eedb8bc7719726edcb09b385d2d79b24f75/stringzilla-4.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9f5d8f0475d85fa5999b3c474f5c8f7fb1eaea8b38599b7c119cf4fc3ef42757", size = 199368, upload-time = "2026-05-05T15:18:08.662Z" }, - { url = "https://files.pythonhosted.org/packages/90/d8/4f1154c70e1da8ff14d10df55e5c0e64602567051e0e9d9f1e0172a158bb/stringzilla-4.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:810d86b24865832ee61641080d59f6de148f95b300dbf5a94cf45145b33c897b", size = 689900, upload-time = "2026-05-05T15:18:10.253Z" }, - { url = "https://files.pythonhosted.org/packages/4c/53/1b017ffc46cde8f64055ac91b18a0b37b708f13e79ba8cd51da337e030f8/stringzilla-4.6.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cf49c4f06e309a2e0b99b01037ad6595a546cdcc32206f15c8b796dc666adb03", size = 657403, upload-time = "2026-05-05T15:18:12.014Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d4/06a86053caa38834ec91c90d1823928a03a9f9080a37dbf087a5cad98c77/stringzilla-4.6.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4a924be9e4ca165da0c88017ac33caa888d7b33c2ec9c5cf3cc17e9bf090faff", size = 640633, upload-time = "2026-05-05T15:18:13.86Z" }, - { url = "https://files.pythonhosted.org/packages/e3/04/86ee8bde71c1898ce2bcf1cbc3f2b80ec749852b9031ffbf3eb7e8802aa1/stringzilla-4.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e288fd253872c6a098d4478b2653a05ec0c6be51b1618a45f25dd78527950a96", size = 2054016, upload-time = "2026-05-05T15:18:15.755Z" }, - { url = "https://files.pythonhosted.org/packages/b1/90/cedbc0615d5d8a8b448b954c9d4818768ac5c45e114afee11e5918982a35/stringzilla-4.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58327cc6e53951f2f3df02f697ba2a429ab7a1a123cce58db708398ad24fadd2", size = 644091, upload-time = "2026-05-05T15:18:17.643Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b0/7e71463c0545f13c9e893391a1c7ba61cd4344240b1c972095bcef06f489/stringzilla-4.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c4248555165e1dc5c743932941070c2f523cdaf17b3938ed04dd9a74cc543e8", size = 653905, upload-time = "2026-05-05T15:18:19.372Z" }, - { url = "https://files.pythonhosted.org/packages/dd/e8/281d32b15adda19346edc0a722e6433e6fc3e9356cfcf9bfef512d9f2a78/stringzilla-4.6.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b23d3172830e5136691a9a13bb2f6bc391fff7d3a3399e02a690bcbfd0f5bbda", size = 587120, upload-time = "2026-05-05T15:18:21.145Z" }, - { url = "https://files.pythonhosted.org/packages/da/fb/33702a7365aba99b0bb7e6866fe35860142a8e9752d6de3de66250bc9f90/stringzilla-4.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1360b78634f495272a85704cacb2197ecce7f00b9b8be1c0d2d6e7c05ffc4efc", size = 626297, upload-time = "2026-05-05T15:18:23.126Z" }, - { url = "https://files.pythonhosted.org/packages/37/65/333f702b366a9595fea03de805379bb672fa3cafe52178697a09f430fc1a/stringzilla-4.6.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f40b8973efa7bc127d07a26c5bbd753197cd9d7b539810896bf8b98a9542f51b", size = 619149, upload-time = "2026-05-05T15:18:25.11Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a3/ccf774223db71021d0af56d385a17d7f78245d8d34ba5cadc6517fd0e58c/stringzilla-4.6.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:19d91a5a4e6a3c7827d89a3b8bfe5e691f90987adaff9418c63bd17380fe1c38", size = 613455, upload-time = "2026-05-05T15:18:26.895Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3b/c49a971f9bbec98deb3b388dd51a1ef458472e871223f86608bfdad9cc22/stringzilla-4.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0e162700650d25f6e9040fb9d196fec19337c072cad5b1df11fc4f612e7d86c7", size = 1909937, upload-time = "2026-05-05T15:18:28.822Z" }, - { url = "https://files.pythonhosted.org/packages/16/83/4b694084746a7e58604a46dc5a6911341b5ae332360492262a84a265d110/stringzilla-4.6.1-cp313-cp313-win32.whl", hash = "sha256:e8b2ab69ee28eb79f54754e9b85cad827580f83aab8d5929ac1eb140fe6e7ade", size = 114789, upload-time = "2026-05-05T15:18:30.634Z" }, - { url = "https://files.pythonhosted.org/packages/97/db/c917b8c9428f647bcd04433e66110cbcd63cee1075f735bb3c7ae6b2ba20/stringzilla-4.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:a9da1feb660085a00f8c8e781be85a4984e6a4504f7804af1b13fdf5563a41b7", size = 162555, upload-time = "2026-05-05T15:18:32.638Z" }, - { url = "https://files.pythonhosted.org/packages/e9/7c/350482e963262ff6cfb7d094fe5254c6a432c21ce971d83e98a23268a2ed/stringzilla-4.6.1-cp313-cp313-win_arm64.whl", hash = "sha256:c2cc745ce598af8cdd5067b923ba7631bbe5e77b9d552b7d5a29135566ea3bec", size = 123329, upload-time = "2026-05-05T15:18:34.336Z" }, -] - -[[package]] -name = "sympy" -version = "1.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mpmath" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, -] - -[[package]] -name = "tifffile" -version = "2026.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b7/38/5e2ecef5af2f4fd4a89bb8d6240de9458bab4d51a4cbd97aeb3a0cd618e2/tifffile-2026.6.1.tar.gz", hash = "sha256:626c892c0e899d959b9438e7c0e1491dc154a7fead1f1f37a991724a50eceba9", size = 429694, upload-time = "2026-05-31T23:57:12.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/59/208f71d70ddc6184f79b8c6d87d46eb7d7b12c19186a817dec9c9c3f3693/tifffile-2026.6.1-py3-none-any.whl", hash = "sha256:0d7382d2769b855b81ce358528e2b40c16d48aa39031746efa81215205332a8d", size = 267108, upload-time = "2026-05-31T23:57:10.597Z" }, -] - -[[package]] -name = "timm" -version = "1.0.27" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, - { name = "pyyaml" }, - { name = "safetensors" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torchvision", version = "0.27.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/54/ece85b0eef3700c90db8271a43669b05a0ebbe2edb1962329c34374a297e/timm-1.0.27.tar.gz", hash = "sha256:315dfe63186ca9fb7ff941268941231fd5be259f2b4bb4afa28560ae1015cb9a", size = 2439861, upload-time = "2026-05-08T19:38:36.844Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/2e/26bab7686ff4aed48f8f5f6c23e2aa37b7a37ddd9effe3aa61e908fd518f/timm-1.0.27-py3-none-any.whl", hash = "sha256:5ff07c9ddf53cbada88eab1c93ff175c64cab683b5a2fddf863bcee985926f89", size = 2589280, upload-time = "2026-05-08T19:38:35.034Z" }, -] - -[[package]] -name = "tokenizers" -version = "0.22.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, - { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, - { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, - { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, - { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, - { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, - { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, - { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, -] - -[[package]] -name = "tomlkit" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/db/03eaf4331631ef6b27d6e3c9b68c54dc6f0d63d87201fed600cc409307fd/tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3", size = 161875, upload-time = "2026-05-10T07:38:22.245Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/43/8bd850ee71a191bf072e31302c73a66be413fecdd98fdcd111ecbcce13ca/tomlkit-0.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738", size = 41328, upload-time = "2026-05-10T07:38:23.517Z" }, -] - -[[package]] -name = "torch" -version = "2.11.0+cu128" -source = { registry = "https://download.pytorch.org/whl/cu128" } -resolution-markers = [ - "sys_platform == 'linux'", - "sys_platform == 'win32'", -] -dependencies = [ - { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, - { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, - { name = "filelock", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "fsspec", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "jinja2", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "networkx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "nvidia-cudnn-cu12", marker = "sys_platform == 'linux'" }, - { name = "nvidia-cusparselt-cu12", marker = "sys_platform == 'linux'" }, - { name = "nvidia-nccl-cu12", marker = "sys_platform == 'linux'" }, - { name = "nvidia-nvshmem-cu12", marker = "sys_platform == 'linux'" }, - { name = "setuptools", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "sympy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "triton", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -wheels = [ - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9c8f38efee365cb9d334de8a83ce52fc7e5fc9e5a7b0853285efa1b69e00b0f2", upload-time = "2026-04-27T17:41:30Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d252cf975fb18c94a85336323ad425f473df56dab35a44b00399bd70c7a3b997", upload-time = "2026-04-27T17:42:06Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp312-cp312-win_amd64.whl", hash = "sha256:7c78215c3af4f62e63f2b2e360f1722fc719b0853c7ac22666483d9810613a4c", upload-time = "2026-04-27T17:43:49Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7db3580106bba044da5b8950f3fb8fe5f31999eaab3f6a3aa2ac5d202c3684d2", upload-time = "2026-04-27T17:45:35Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:db964b33c55035a72ab3e2162287af8f1cc276039c65d015740cc88c26dcedf7", upload-time = "2026-04-27T17:46:18Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313-win_amd64.whl", hash = "sha256:6f367e62fd81b75cdf23ca4b75ced834d2db2cf98d1588ac935bde345de9de23", upload-time = "2026-04-27T17:48:09Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd1cf1005c5fe419194ee294b7b584ba5ad0f2fb1778b3fe5a7b9c3f4617ddbc", upload-time = "2026-04-27T17:50:01Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:74b628dbc71603977b09f4e140792c6e997081a35ef3421555f3f6e201b81210", upload-time = "2026-04-27T17:50:42Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313t-win_amd64.whl", hash = "sha256:c2a5984deba8e001d166bf9cb83b8351f63a28b009e1a2fa0e4bbf08c90b259b", upload-time = "2026-04-27T17:52:32Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:baa52f7b8a53cab16587b10f1c27d1000ca033f97236878b685b75d5a1b92408", upload-time = "2026-04-27T17:54:24Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d389a850677f0d24dafae1573644034428d8d3b9c80b51d55ba62fed7e6c8777", upload-time = "2026-04-27T17:55:03Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314-win_amd64.whl", hash = "sha256:d6c21797ff75271b4fbdd905e2d703be4ecea5ea5bbdde4d1c201e9c71bc411d", upload-time = "2026-04-27T17:56:46Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:06849e9311dbb0617c97557d9c26c99a9e1c4f2ac9cb8e9b6d9b420d522acb91", upload-time = "2026-04-27T17:58:48Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:169a9987e1f84f0c5eee07544b3a34827a163ac9180e23abf0c3548f1335762c", upload-time = "2026-04-27T17:59:26Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314t-win_amd64.whl", hash = "sha256:d86c125d720c2c368c53bd1a4ef062916d91fa965c10448c74c78b5d039faf2d", upload-time = "2026-04-27T18:01:14Z" }, -] - -[[package]] -name = "torch" -version = "2.12.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "sys_platform != 'linux' and sys_platform != 'win32'", -] -dependencies = [ - { name = "filelock", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "fsspec", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "jinja2", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "networkx", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "setuptools", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "sympy", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "typing-extensions", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/bb/285d643f254731294c9b595a007eac39db4600a98682d7bca688f42ca164/torch-2.12.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b41339df93d491435e790ff8bcbae1c0ce777175889bfd1281d119862793e6a2", size = 88010197, upload-time = "2026-05-13T14:55:35.414Z" }, - { url = "https://files.pythonhosted.org/packages/86/ca/01896c80ba921676aa45886b2c5b8d774912de2a1f719de48169c6f755cd/torch-2.12.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:90dd587a5f61bfe1307148b581e2084fc5bc4a06e2b90a20e9a36b81087ff16b", size = 88009511, upload-time = "2026-05-13T14:54:47.411Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ad/e95e822f3538171e22640a7fbe839a1fdb666600bf6487025de2ff03b11a/torch-2.12.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:10ee1448a9f304d3b987eb4656f664ba6e4d7b410ca7a5a7c642199777a2cf88", size = 88319556, upload-time = "2026-05-13T14:54:05.574Z" }, - { url = "https://files.pythonhosted.org/packages/67/dc/ac069f8d6e8be701535921141055293b0d4819d3d7f224a4612cf157c7f9/torch-2.12.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7dfae4a519197dfa050e98d8e36378a0fb5899625a875c2b54445005a2e404e", size = 88027282, upload-time = "2026-05-13T14:53:05.258Z" }, - { url = "https://files.pythonhosted.org/packages/7b/78/2e12b37ce50a19a037d7bc62d652a5a8f27385a7b05859d6bc9204f20cfe/torch-2.12.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:b4556715c8572758625d62b6e0ae3b1f76c440221913a6fb5e100f321fb4fb02", size = 88320100, upload-time = "2026-05-13T14:51:39.955Z" }, -] - -[[package]] -name = "torchao" -version = "0.17.0" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/fe/a4036a8e80fa800c92dbcbf75f541cd4c106248b6b579db6dab1800f616a/torchao-0.17.0-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:87a418ce0ec064a821ceab83c921b501acef0ce9a6ccd1be358fcd16c3ae8c58", size = 3206172, upload-time = "2026-03-30T22:25:52.974Z" }, - { url = "https://files.pythonhosted.org/packages/c9/37/ef37ca885265e5f79a168616767dd416a3cea1cc3b28bb6b503ce4a5b652/torchao-0.17.0-py3-none-any.whl", hash = "sha256:02eba449036715b9ae784fbaa1a6f97994bb7b0421ce92d1d5d1c08e5bd6d349", size = 1200680, upload-time = "2026-03-30T22:25:54.457Z" }, -] - -[[package]] -name = "torchsde" -version = "0.2.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "scipy" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "trampoline" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/a5/ae18ee6de023b3a5462122a43a4c9812c11d275cc585a3d08bf24945c02a/torchsde-0.2.6.tar.gz", hash = "sha256:81d074d3504f9d190f1694fb526395afbe4608ee43a88adb1262a639e5b4778b", size = 48840, upload-time = "2023-09-26T21:52:20.614Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/1f/b67ebd7e19ffe259f05d3cf4547326725c3113d640c277030be3e9998d6f/torchsde-0.2.6-py3-none-any.whl", hash = "sha256:19bf7ff02eec7e8e46ba1cdb4aa0f9db1c51d492524a16975234b467f7fc463b", size = 61232, upload-time = "2023-09-26T21:52:19.274Z" }, -] - -[[package]] -name = "torchvision" -version = "0.26.0+cu128" -source = { registry = "https://download.pytorch.org/whl/cu128" } -resolution-markers = [ - "sys_platform == 'linux'", - "sys_platform == 'win32'", -] -dependencies = [ - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "pillow", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -wheels = [ - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:63e35234aed13b6edda37056f417b5c281249669db631e706811917af36b21d7", upload-time = "2026-04-09T23:21:35Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ccf26b4b659cfce6f2208cb8326071d51c70219a34856dfdf468d1e19af52c0d", upload-time = "2026-03-23T15:36:22Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp312-cp312-win_amd64.whl", hash = "sha256:8c0d1c4fbb2c9a4d5d41d0aaa87da20e525bcb2a154ce405725b0be59456804b", upload-time = "2026-04-09T23:21:36Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c4a9cacd521f2a4df0bcd9d8e96704771b928f478f1f3067e4085bb53a1da298", upload-time = "2026-04-09T23:21:37Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cb1f6184a7ba30fba40580e1a01a6604a86c55e79fdda187f40116ee680441ec", upload-time = "2026-03-23T15:36:22Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313-win_amd64.whl", hash = "sha256:0232cb219927a52d6c98ff202f32d1cdf4802c2195a85fc1f1a0c1b0b4983a4d", upload-time = "2026-04-09T23:21:38Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e594732552a8c2fee2ace9c6475c6c6904fc44ccca622ee6765a89a045416a44", upload-time = "2026-04-09T23:21:38Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6168abc019803ac9e97efce27eafd2fdb33db04dcc54a86039537729e5047b29", upload-time = "2026-03-23T15:36:23Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313t-win_amd64.whl", hash = "sha256:367d42ea703844ecdb516e9d5eb09929012a58705d2622cf4e9e3c37f278cb85", upload-time = "2026-04-09T23:21:39Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:b3865fa227661dd75b7b28c96d3d14e739bd08bf0614132758922fe0e7206f91", upload-time = "2026-04-09T23:21:39Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:aac647c9130f1f25f5c8f5bca3d95cfd96bdfac93ab54529690b088e64e4fa64", upload-time = "2026-03-23T15:36:23Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314-win_amd64.whl", hash = "sha256:6319e1ba49c6f62ac9902f73d0eab207b8a4dc6b4d3392fe9edd9903fff1be0a", upload-time = "2026-04-09T23:21:40Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:e2ee9e16ee4518292694537fcbd20d2d27044e381d92b864f637e82795796a84", upload-time = "2026-04-09T23:21:40Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:b5772c55bfda4377df8f1930d43c4e0231ef231b0228eade4b227c8d3ba6e34e", upload-time = "2026-03-23T15:36:23Z" }, - { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314t-win_amd64.whl", hash = "sha256:f160dc552a086244f7102c898f7be8ef46a41b36bce5ea80a4f2493cb30ca1fc", upload-time = "2026-04-09T23:21:41Z" }, -] - -[[package]] -name = "torchvision" -version = "0.27.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "sys_platform != 'linux' and sys_platform != 'win32'", -] -dependencies = [ - { name = "numpy", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "pillow", marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c8/5cd91932f7f3671b0743dc4ae1a4c16b1d0b45bf4087976277d325bda718/torchvision-0.27.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1a6dd742a150645126df9e0b2e449874c1d635897c773b322c2e067e98382dfe", size = 1758824, upload-time = "2026-05-13T14:57:15.227Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ae/36547812e6e047c1d80bcacd1b17a340612b08a6e876e0aabf3d0b9228b0/torchvision-0.27.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:41d6dae73e1af09fa82ded597ae57f2a2314285acde54b25890a8f8e51b999d7", size = 1758826, upload-time = "2026-05-13T14:57:05.262Z" }, - { url = "https://files.pythonhosted.org/packages/fa/23/95dfa40431360f42ca949bf861434bed51164adfa8fb9801e05bf3194f50/torchvision-0.27.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:c5121f1b9ab09a7f73e837871deb8321551f7eaeb19d87aa00de9191968eae44", size = 1845008, upload-time = "2026-05-13T14:57:03.768Z" }, - { url = "https://files.pythonhosted.org/packages/92/22/c0633677b3b3f3e69554a21ac087bf705f829c40cd5e3783507b8c006681/torchvision-0.27.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:c1fac0fc2a7adf29481fc1938a0e7845c57ba1147a986784109c4d98f434ea8c", size = 1758818, upload-time = "2026-05-13T14:56:54.988Z" }, - { url = "https://files.pythonhosted.org/packages/8d/7e/e78c48662a8d551606efdbe11c6b9c1d6d2391b92cd0e4591b9e6a2412b8/torchvision-0.27.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:2c4099a15150143b9b034730b404a56d572efe0b79489b4c765d929cb4eac7f3", size = 1758828, upload-time = "2026-05-13T14:56:52.293Z" }, -] - -[[package]] -name = "tqdm" -version = "4.67.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, -] - -[[package]] -name = "trampoline" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/54/d2805324fb746d8da86d3844bee4f55c0cfd6c136de61b713772d44c5bea/trampoline-0.1.2-py3-none-any.whl", hash = "sha256:36cc9a4ff9811843d177fc0e0740efbd7da39eadfe6e50c9e2937cbc06d899d9", size = 5173, upload-time = "2018-08-18T01:00:41.215Z" }, -] - -[[package]] -name = "transformers" -version = "5.10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "regex" }, - { name = "safetensors" }, - { name = "tokenizers" }, - { name = "tqdm" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/65/d3/5b7c2f1a52ff0e57355efdc21554aab7e4602f6592ab4582a34c988ab956/transformers-5.10.1.tar.gz", hash = "sha256:31112d1dcdfcf9934242acbba891f44e2279ff74b9b8ba4595640e0e04195a3a", size = 8798372, upload-time = "2026-06-03T15:37:03.289Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/8c/3119596c7fcd9b8b8d924d5b5ba1bfdfafd6d8738bdd81ca09c60b4a38b3/transformers-5.10.1-py3-none-any.whl", hash = "sha256:ccb919ea1b77338b44d0d45d23f7472081906b1bb6ed8e5f5cf4d692d1da03d4", size = 11003770, upload-time = "2026-06-03T15:37:00.433Z" }, -] - -[[package]] -name = "transparent-background" -version = "1.3.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "albucore" }, - { name = "albumentations" }, - { name = "easydict" }, - { name = "gdown" }, - { name = "kornia" }, - { name = "opencv-python" }, - { name = "pymatting" }, - { name = "pyyaml" }, - { name = "timm" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torchvision", version = "0.27.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, - { name = "tqdm" }, - { name = "wget" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/74/3f/26fe49d0aa8d4968ce8f6510a97c0d39651d8971a4908a50f8a8a17489ba/transparent_background-1.3.4.tar.gz", hash = "sha256:28014ece0ae5b7760f7c12231840db3f5ad1b493968d0d7dbce4a079e206dfa6", size = 36755, upload-time = "2025-05-14T11:30:10.892Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/3c/ebb4280082bd6f7569454b48fafeb9575d1a5b910bec9e261042b0589273/transparent_background-1.3.4-py3-none-any.whl", hash = "sha256:aa823962e124ae06ea16eb722c18d00cafdda2eda729e9d84d1ab038f62a1d32", size = 33390, upload-time = "2025-05-14T11:30:09.018Z" }, -] - -[[package]] -name = "triton" -version = "3.6.0" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, - { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, - { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, - { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, - { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, - { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, -] - -[[package]] -name = "typer" -version = "0.25.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, -] - -[[package]] -name = "urllib3" -version = "2.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, -] - -[[package]] -name = "wcwidth" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0", size = 182132, upload-time = "2026-05-02T16:04:12.653Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" }, -] - -[[package]] -name = "wget" -version = "3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/6a/62e288da7bcda82b935ff0c6cfe542970f04e29c756b0e147251b2fb251f/wget-3.2.zip", hash = "sha256:35e630eca2aa50ce998b9b1a127bb26b30dfee573702782aa982f875e3f16061", size = 10857, upload-time = "2015-10-22T15:26:37.51Z" } - -[[package]] -name = "xformers" -version = "0.0.35" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, - { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/5a/6e27734bd793adc44d0b8d294e67cfacf4ec590572c1aef51d683fc7a791/xformers-0.0.35.tar.gz", hash = "sha256:f7fc183a58e4bf0e2ae339a18fb1b1d4a37854c0f2545b4f360fef001646ab76", size = 4258182, upload-time = "2026-02-20T20:33:05.417Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/85/6d71f9b16f2ac647877e66ed4af723b3fbd477806ab8b8a89d39a362b85f/xformers-0.0.35-py39-none-manylinux_2_28_x86_64.whl", hash = "sha256:ccc73c7db9890224ab05f5fb60e2034f9e6c8672a10be0cf00e95cbbae3eda7c", size = 3264751, upload-time = "2026-02-20T20:33:02.444Z" }, - { url = "https://files.pythonhosted.org/packages/49/0b/88c39c128a05d5b553a67cb9c4c3fc32eefb91f836f838befab9e78f8364/xformers-0.0.35-py39-none-win_amd64.whl", hash = "sha256:57381ce3cbb79b593e6b62cb20a937885345fad2796de2aa6fbb66c033601179", size = 2638618, upload-time = "2026-02-20T20:33:04.104Z" }, -] - -[[package]] -name = "yarl" -version = "1.24.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, - { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, - { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, - { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, - { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, - { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, - { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, - { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, - { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, - { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, - { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, - { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, - { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, - { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" }, - { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" }, - { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" }, - { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" }, - { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" }, - { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" }, - { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" }, - { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" }, - { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" }, - { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" }, - { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" }, - { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" }, - { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" }, - { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" }, - { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" }, - { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" }, - { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" }, - { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" }, - { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" }, - { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" }, - { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" }, - { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" }, - { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" }, - { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" }, - { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" }, - { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" }, - { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" }, - { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" }, - { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" }, - { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" }, - { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" }, - { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" }, - { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" }, - { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" }, - { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" }, - { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" }, - { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" }, - { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" }, - { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, -] - -[[package]] -name = "zipp" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, -] diff --git a/web/THIRD_PARTY_LICENSES.txt b/web/THIRD_PARTY_LICENSES.txt new file mode 100644 index 0000000..2f39fa6 --- /dev/null +++ b/web/THIRD_PARTY_LICENSES.txt @@ -0,0 +1,6920 @@ +MoDiff web client - production dependency licenses + +Generated deterministically from package-lock.json and the installed production dependency closure. +Do not edit this file directly; run `npm run licenses:generate`. + +================================================================================ +MoDiff project-specific third-party notices +================================================================================ + +# Third-party notices + +## Mellon Client + +Substantial portions of the client source are adapted from +[`cubiq/Mellon-client`](https://github.com/cubiq/Mellon-client) at comparison +baseline +[`af0c5801f843453a1700733596e99fe6589b2e86`](https://github.com/cubiq/Mellon-client/tree/af0c5801f843453a1700733596e99fe6589b2e86), +Copyright 2024 Matteo Spinelli. Mellon Client is licensed under the Apache +License 2.0; the project `LICENSE` contains that license text. + +This revision is an evidence-based pre-import comparison baseline selected +from repository history and file similarity. It is not asserted to be a proven +Git ancestor or necessarily the exact snapshot used for MoDiff Client's +original import. MoDiff Client has modified the adapted files. See the +[source-provenance map](docs/source-provenance.md) for the affected path +families and the treatment of formats that cannot carry comments. + +## Font software + +MoDiff redistributes the following font software in source builds and/or the +bundled web client. These fonts are not licensed under MoDiff's Apache-2.0 +license. + +- **IBM Plex Mono**, supplied by `@fontsource/ibm-plex-mono` 5.2.7. + The package notice says: Copyright 2017 IBM Corp. All rights reserved. + The authoritative IBM Plex notice is: Copyright © 2017 IBM Corp. with + Reserved Font Name "Plex". +- **Source Sans Pro**, supplied by `@fontsource/source-sans-pro` 5.2.5. + The package notice says: Google Inc. The authoritative Source Sans notice + is: Copyright 2010-2024 Adobe (), with Reserved Font + Name "Source". All Rights Reserved. Source is a trademark of Adobe in the + United States and/or other countries. + +Both published packages declare the SIL Open Font License, Version 1.1. + +## SIL Open Font License, Version 1.1 + +Version 1.1 - 26 February 2007 + +### Preamble + +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The fonts, +including any derivative works, can be bundled, embedded, redistributed +and/or sold with any software provided that any reserved names are not used +by derivative works. The fonts and derivatives, however, cannot be released +under any other type of license. The requirement for fonts to remain under +this license does not apply to any document created using the fonts or their +derivatives. + +### Definitions + +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may include +source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or +substituting -- in part or in whole -- any of the components of the Original +Version, by changing formats or by porting the Font Software to a new +environment. + +"Author" refers to any designer, engineer, programmer, technical writer or +other person who contributed to the Font Software. + +### Permission and conditions + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the Font Software, to use, study, copy, merge, embed, modify, redistribute, +and sell modified and unmodified copies of the Font Software, subject to the +following conditions: + +1. Neither the Font Software nor any of its individual components, in + Original or Modified Versions, may be sold by itself. +2. Original or Modified Versions of the Font Software may be bundled, + redistributed and/or sold with any software, provided that each copy + contains the above copyright notice and this license. These can be + included either as stand-alone text files, human-readable headers or in + the appropriate machine-readable metadata fields within text or binary + files as long as those fields can be easily viewed by the user. +3. No Modified Version of the Font Software may use the Reserved Font Name(s) + unless explicit written permission is granted by the corresponding + Copyright Holder. This restriction only applies to the primary font name + as presented to the users. +4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font + Software shall not be used to promote, endorse or advertise any Modified + Version, except to acknowledge the contribution(s) of the Copyright + Holder(s) and the Author(s) or with their explicit written permission. +5. The Font Software, modified or unmodified, in part or in whole, must be + distributed entirely under this license, and must not be distributed + under any other license. The requirement for fonts to remain under this + license does not apply to any document created using the Font Software. + +### Termination + +This license becomes null and void if any of the above conditions are not +met. + +### Disclaimer + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, +INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE +THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +================================================================================ +@floating-ui/core@1.7.5 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2021-present Floating UI contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +@floating-ui/dom@1.7.6 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2021-present Floating UI contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +@floating-ui/react-dom@2.1.8 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2021-present Floating UI contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +@floating-ui/react@0.26.28 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2021-present Floating UI contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +@floating-ui/utils@0.2.11 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2021-present Floating UI contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +@fontsource/ibm-plex-mono@5.2.7 +Declared license: OFL-1.1 +================================================================================ + +--- LICENSE --- + +Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-ThinItalic.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-ExtraLight.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-ExtraLightItalic.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-Light.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-LightItalic.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-Regular.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-Italic.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-Medium.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-MediumItalic.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-SemiBold.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-SemiBoldItalic.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-Bold.ttf: Copyright 2017 IBM Corp. All rights reserved. IBMPlexMono-BoldItalic.ttf: Copyright 2017 IBM Corp. All rights reserved. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +================================================================================ +@fontsource/source-sans-pro@5.2.5 +Declared license: OFL-1.1 +================================================================================ + +--- LICENSE --- + +Google Inc. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +================================================================================ +@headlessui/react@2.2.10 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2020 Tailwind Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@internationalized/date@3.12.2 +Declared license: Apache-2.0 +================================================================================ + +--- LICENSE --- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Adobe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ +@internationalized/number@3.6.7 +Declared license: Apache-2.0 +================================================================================ + +--- LICENSE --- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Adobe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ +@internationalized/string@3.2.9 +Declared license: Apache-2.0 +================================================================================ + +--- LICENSE --- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Adobe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ +@jridgewell/gen-mapping@0.3.13 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +Copyright 2024 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@jridgewell/remapping@2.3.5 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +Copyright 2024 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@jridgewell/resolve-uri@3.1.2 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +Copyright 2019 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@jridgewell/sourcemap-codec@1.5.5 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +Copyright 2024 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@jridgewell/trace-mapping@0.3.31 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +Copyright 2024 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@oxc-project/types@0.139.0 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2024-present VoidZero Inc. & Contributors +Copyright (c) 2023 Boshen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@react-aria/focus@3.22.1 +Declared license: Apache-2.0 +================================================================================ + +--- LICENSE --- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Adobe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ +@react-aria/interactions@3.28.1 +Declared license: Apache-2.0 +================================================================================ + +--- LICENSE --- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Adobe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ +@react-types/shared@3.35.0 +Declared license: Apache-2.0 +================================================================================ + +--- LICENSE --- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Adobe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ +@rolldown/pluginutils@1.0.1 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2026-present, rolldown/plugins repository contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@swc/helpers@0.5.23 +Declared license: Apache-2.0 +================================================================================ + +--- LICENSE --- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2024 SWC contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +================================================================================ +@tailwindcss/node@4.3.1 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) Tailwind Labs, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@tailwindcss/oxide@4.3.1 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) Tailwind Labs, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@tailwindcss/vite@4.3.1 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) Tailwind Labs, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@tanstack/react-virtual@3.14.3 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2021-present Tanner Linsley + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@tanstack/virtual-core@3.17.1 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2021-present Tanner Linsley + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@types/d3-color@3.1.3 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +================================================================================ +@types/d3-drag@3.0.7 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +================================================================================ +@types/d3-interpolate@3.0.4 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +================================================================================ +@types/d3-selection@3.0.11 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +================================================================================ +@types/d3-transition@3.0.9 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +================================================================================ +@types/d3-zoom@3.0.8 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +================================================================================ +@types/node@24.12.4 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +================================================================================ +@types/react-dom@19.2.3 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +================================================================================ +@types/react@19.2.16 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + +================================================================================ +@xyflow/react@12.11.0 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2019-2025 webkid GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +@xyflow/system@0.0.77 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2019-2025 webkid GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +aria-hidden@1.2.6 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2017 Anton Korzunov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +classcat@5.0.5 +Declared license: MIT +================================================================================ + +--- LICENSE.md --- + +Copyright © Jorge Bucaran <> + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +clsx@2.1.1 +Declared license: MIT +================================================================================ + +--- license --- + +MIT License + +Copyright (c) Luke Edwards (lukeed.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +csstype@3.2.3 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +Copyright (c) 2017-2018 Fredrik Nicol + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +d3-color@3.1.0 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +Copyright 2010-2022 Mike Bostock + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +================================================================================ +d3-dispatch@3.0.1 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +Copyright 2010-2021 Mike Bostock + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +================================================================================ +d3-drag@3.0.0 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +Copyright 2010-2021 Mike Bostock + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +================================================================================ +d3-ease@3.0.1 +Declared license: BSD-3-Clause +================================================================================ + +--- LICENSE --- + +Copyright 2010-2021 Mike Bostock +Copyright 2001 Robert Penner +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the author nor the names of contributors may be used to + endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +================================================================================ +d3-interpolate@3.0.1 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +Copyright 2010-2021 Mike Bostock + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +================================================================================ +d3-selection@3.0.0 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +Copyright 2010-2021 Mike Bostock + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +================================================================================ +d3-timer@3.0.1 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +Copyright 2010-2021 Mike Bostock + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +================================================================================ +d3-transition@3.0.1 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +Copyright 2010-2021 Mike Bostock + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +================================================================================ +d3-zoom@3.0.0 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +Copyright 2010-2021 Mike Bostock + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + +================================================================================ +detect-libc@2.1.2 +Declared license: Apache-2.0 +================================================================================ + +--- LICENSE --- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ +enhanced-resolve@5.21.6 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +Copyright JS Foundation and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +fdir@6.5.0 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +Copyright 2023 Abdullah Atta + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +graceful-fs@4.2.11 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +The ISC License + +Copyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +================================================================================ +jiti@2.7.0 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) Pooya Parsa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +lightningcss@1.32.0 +Declared license: MPL-2.0 +================================================================================ + +--- LICENSE --- + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" +means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software. + +1.2. "Contributor Version" +means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" +means Covered Software of a particular Contributor. + +1.4. "Covered Software" +means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof. + +1.5. "Incompatible With Secondary Licenses" +means + +(a) that the initial Contributor has attached the notice described +in Exhibit B to the Covered Software; or + +(b) that the Covered Software was made available under the terms of +version 1.1 or earlier of the License, but not also under the +terms of a Secondary License. + +1.6. "Executable Form" +means any form of the work other than Source Code Form. + +1.7. "Larger Work" +means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software. + +1.8. "License" +means this document. + +1.9. "Licensable" +means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + +1.10. "Modifications" +means any of the following: + +(a) any file in Source Code Form that results from an addition to, +deletion from, or modification of the contents of Covered +Software; or + +(b) any new file in Source Code Form that contains any Covered +Software. + +1.11. "Patent Claims" of a Contributor +means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version. + +1.12. "Secondary License" +means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses. + +1.13. "Source Code Form" +means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") +means an individual or a legal entity exercising rights under this +License. For legal entities, "You" includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) +Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer +for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; +or + +(b) for infringements caused by: (i) Your and any other third party's +modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of +its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code +Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this +License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + +This Source Code Form is "Incompatible With Secondary Licenses", as +defined by the Mozilla Public License, v. 2.0. + +================================================================================ +lucide-react@1.20.0 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +magic-string@0.30.21 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +Copyright 2018 Rich Harris + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +nanoid@3.3.16 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +The MIT License (MIT) + +Copyright 2017 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +nanoid@5.1.16 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +The MIT License (MIT) + +Copyright 2017 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +picocolors@1.1.1 +Declared license: ISC +================================================================================ + +--- LICENSE --- + +ISC License + +Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +================================================================================ +picomatch@4.0.5 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +The MIT License (MIT) + +Copyright (c) 2017-present, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +================================================================================ +postcss@8.5.25 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +The MIT License (MIT) + +Copyright 2013 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +react-aria@3.49.0 +Declared license: Apache-2.0 +================================================================================ + +--- LICENSE --- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Adobe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ +react-dom@19.2.7 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +react-stately@3.47.0 +Declared license: Apache-2.0 +================================================================================ + +--- LICENSE --- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Adobe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================================================================ +react@19.2.7 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +rolldown@1.1.5 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2024-present VoidZero Inc. & Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +end of terms and conditions + +The licenses of externally maintained libraries from which parts of the Software is derived are listed [here](https://github.com/rolldown/rolldown/blob/main/THIRD-PARTY-LICENSE). + +================================================================================ +scheduler@0.27.0 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +source-map-js@1.2.1 +Declared license: BSD-3-Clause +================================================================================ + +--- LICENSE --- + +Copyright (c) 2009-2011, Mozilla Foundation and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the names of the Mozilla Foundation nor the names of project + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +================================================================================ +tabbable@6.4.0 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +The MIT License (MIT) + +Copyright (c) 2015 David Clark + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +tailwindcss@4.3.1 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) Tailwind Labs, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +tapable@2.3.3 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +The MIT License + +Copyright JS Foundation and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +================================================================================ +tinyglobby@0.2.17 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2024 Madeline Gurriarán + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +tslib@2.8.1 +Declared license: 0BSD +================================================================================ + +--- LICENSE.txt --- + +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + +================================================================================ +undici-types@7.16.0 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) Matteo Collina and Undici contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +use-sync-external-store@1.6.0 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +vite@8.1.4 +Declared license: MIT +================================================================================ + +--- LICENSE.md --- + +# Vite core license +Vite is released under the MIT license: + +MIT License + +Copyright (c) 2019-present, VoidZero Inc. and Vite contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +# Licenses of bundled dependencies +The published Vite artifact additionally contains code with the following licenses: +Apache-2.0, BSD-2-Clause, CC0-1.0, ISC, MIT + +# Bundled dependencies: +## @jridgewell/gen-mapping, @jridgewell/remapping, @jridgewell/sourcemap-codec, @jridgewell/trace-mapping +License: MIT +By: Justin Ridgewell +Repositories: https://github.com/jridgewell/sourcemaps, https://github.com/jridgewell/sourcemaps, https://github.com/jridgewell/sourcemaps, https://github.com/jridgewell/sourcemaps + +> Copyright 2024 Justin Ridgewell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## @jridgewell/resolve-uri +License: MIT +By: Justin Ridgewell +Repository: https://github.com/jridgewell/resolve-uri + +> Copyright 2019 Justin Ridgewell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## @polka/compression +License: MIT +Repository: https://github.com/lukeed/polka + +--------------------------------------- + +## @polka/url +License: MIT +By: Luke Edwards +Repository: https://github.com/lukeed/polka + +--------------------------------------- + +## @rollup/plugin-alias, @rollup/plugin-dynamic-import-vars, @rollup/pluginutils +License: MIT +By: Johannes Stein +Repository: https://github.com/rollup/plugins + +License: MIT +By: LarsDenBakker +Repository: https://github.com/rollup/plugins + +License: MIT +By: Rich Harris +Repository: https://github.com/rollup/plugins + +> The MIT License (MIT) +> +> Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## @vercel/detect-agent +License: Apache-2.0 +By: Vercel +Repository: https://github.com/vercel/vercel + +> Apache License +> Version 2.0, January 2004 +> http://www.apache.org/licenses/ +> +> TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +> +> 1. Definitions. +> +> "License" shall mean the terms and conditions for use, reproduction, +> and distribution as defined by Sections 1 through 9 of this document. +> +> "Licensor" shall mean the copyright owner or entity authorized by +> the copyright owner that is granting the License. +> +> "Legal Entity" shall mean the union of the acting entity and all +> other entities that control, are controlled by, or are under common +> control with that entity. For the purposes of this definition, +> "control" means (i) the power, direct or indirect, to cause the +> direction or management of such entity, whether by contract or +> otherwise, or (ii) ownership of fifty percent (50%) or more of the +> outstanding shares, or (iii) beneficial ownership of such entity. +> +> "You" (or "Your") shall mean an individual or Legal Entity +> exercising permissions granted by this License. +> +> "Source" form shall mean the preferred form for making modifications, +> including but not limited to software source code, documentation +> source, and configuration files. +> +> "Object" form shall mean any form resulting from mechanical +> transformation or translation of a Source form, including but +> not limited to compiled object code, generated documentation, +> and conversions to other media types. +> +> "Work" shall mean the work of authorship, whether in Source or +> Object form, made available under the License, as indicated by a +> copyright notice that is included in or attached to the work +> (an example is provided in the Appendix below). +> +> "Derivative Works" shall mean any work, whether in Source or Object +> form, that is based on (or derived from) the Work and for which the +> editorial revisions, annotations, elaborations, or other modifications +> represent, as a whole, an original work of authorship. For the purposes +> of this License, Derivative Works shall not include works that remain +> separable from, or merely link (or bind by name) to the interfaces of, +> the Work and Derivative Works thereof. +> +> "Contribution" shall mean any work of authorship, including +> the original version of the Work and any modifications or additions +> to that Work or Derivative Works thereof, that is intentionally +> submitted to Licensor for inclusion in the Work by the copyright owner +> or by an individual or Legal Entity authorized to submit on behalf of +> the copyright owner. For the purposes of this definition, "submitted" +> means any form of electronic, verbal, or written communication sent +> to the Licensor or its representatives, including but not limited to +> communication on electronic mailing lists, source code control systems, +> and issue tracking systems that are managed by, or on behalf of, the +> Licensor for the purpose of discussing and improving the Work, but +> excluding communication that is conspicuously marked or otherwise +> designated in writing by the copyright owner as "Not a Contribution." +> +> "Contributor" shall mean Licensor and any individual or Legal Entity +> on behalf of whom a Contribution has been received by Licensor and +> subsequently incorporated within the Work. +> +> 2. Grant of Copyright License. Subject to the terms and conditions of +> this License, each Contributor hereby grants to You a perpetual, +> worldwide, non-exclusive, no-charge, royalty-free, irrevocable +> copyright license to reproduce, prepare Derivative Works of, +> publicly display, publicly perform, sublicense, and distribute the +> Work and such Derivative Works in Source or Object form. +> +> 3. Grant of Patent License. Subject to the terms and conditions of +> this License, each Contributor hereby grants to You a perpetual, +> worldwide, non-exclusive, no-charge, royalty-free, irrevocable +> (except as stated in this section) patent license to make, have made, +> use, offer to sell, sell, import, and otherwise transfer the Work, +> where such license applies only to those patent claims licensable +> by such Contributor that are necessarily infringed by their +> Contribution(s) alone or by combination of their Contribution(s) +> with the Work to which such Contribution(s) was submitted. If You +> institute patent litigation against any entity (including a +> cross-claim or counterclaim in a lawsuit) alleging that the Work +> or a Contribution incorporated within the Work constitutes direct +> or contributory patent infringement, then any patent licenses +> granted to You under this License for that Work shall terminate +> as of the date such litigation is filed. +> +> 4. Redistribution. You may reproduce and distribute copies of the +> Work or Derivative Works thereof in any medium, with or without +> modifications, and in Source or Object form, provided that You +> meet the following conditions: +> +> (a) You must give any other recipients of the Work or +> Derivative Works a copy of this License; and +> +> (b) You must cause any modified files to carry prominent notices +> stating that You changed the files; and +> +> (c) You must retain, in the Source form of any Derivative Works +> that You distribute, all copyright, patent, trademark, and +> attribution notices from the Source form of the Work, +> excluding those notices that do not pertain to any part of +> the Derivative Works; and +> +> (d) If the Work includes a "NOTICE" text file as part of its +> distribution, then any Derivative Works that You distribute must +> include a readable copy of the attribution notices contained +> within such NOTICE file, excluding those notices that do not +> pertain to any part of the Derivative Works, in at least one +> of the following places: within a NOTICE text file distributed +> as part of the Derivative Works; within the Source form or +> documentation, if provided along with the Derivative Works; or, +> within a display generated by the Derivative Works, if and +> wherever such third-party notices normally appear. The contents +> of the NOTICE file are for informational purposes only and +> do not modify the License. You may add Your own attribution +> notices within Derivative Works that You distribute, alongside +> or as an addendum to the NOTICE text from the Work, provided +> that such additional attribution notices cannot be construed +> as modifying the License. +> +> You may add Your own copyright statement to Your modifications and +> may provide additional or different license terms and conditions +> for use, reproduction, or distribution of Your modifications, or +> for any such Derivative Works as a whole, provided Your use, +> reproduction, and distribution of the Work otherwise complies with +> the conditions stated in this License. +> +> 5. Submission of Contributions. Unless You explicitly state otherwise, +> any Contribution intentionally submitted for inclusion in the Work +> by You to the Licensor shall be under the terms and conditions of +> this License, without any additional terms or conditions. +> Notwithstanding the above, nothing herein shall supersede or modify +> the terms of any separate license agreement you may have executed +> with Licensor regarding such Contributions. +> +> 6. Trademarks. This License does not grant permission to use the trade +> names, trademarks, service marks, or product names of the Licensor, +> except as required for reasonable and customary use in describing the +> origin of the Work and reproducing the content of the NOTICE file. +> +> 7. Disclaimer of Warranty. Unless required by applicable law or +> agreed to in writing, Licensor provides the Work (and each +> Contributor provides its Contributions) on an "AS IS" BASIS, +> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +> implied, including, without limitation, any warranties or conditions +> of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +> PARTICULAR PURPOSE. You are solely responsible for determining the +> appropriateness of using or redistributing the Work and assume any +> risks associated with Your exercise of permissions under this License. +> +> 8. Limitation of Liability. In no event and under no legal theory, +> whether in tort (including negligence), contract, or otherwise, +> unless required by applicable law (such as deliberate and grossly +> negligent acts) or agreed to in writing, shall any Contributor be +> liable to You for damages, including any direct, indirect, special, +> incidental, or consequential damages of any character arising as a +> result of this License or out of the use or inability to use the +> Work (including but not limited to damages for loss of goodwill, +> work stoppage, computer failure or malfunction, or any and all +> other commercial damages or losses), even if such Contributor +> has been advised of the possibility of such damages. +> +> 9. Accepting Warranty or Additional Liability. While redistributing +> the Work or Derivative Works thereof, You may choose to offer, +> and charge a fee for, acceptance of support, warranty, indemnity, +> or other liability obligations and/or rights consistent with this +> License. However, in accepting such obligations, You may act only +> on Your own behalf and on Your sole responsibility, not on behalf +> of any other Contributor, and only if You agree to indemnify, +> defend, and hold each Contributor harmless for any liability +> incurred by, or claims asserted against, such Contributor by reason +> of your accepting any such warranty or additional liability. +> +> END OF TERMS AND CONDITIONS +> +> APPENDIX: How to apply the Apache License to your work. +> +> To apply the Apache License to your work, attach the following +> boilerplate notice, with the fields enclosed by brackets "[]" +> replaced with your own identifying information. (Don't include +> the brackets!) The text should be enclosed in the appropriate +> comment syntax for the file format. We also recommend that a +> file or class name and description of purpose be included on the +> same "printed page" as the copyright notice for easier +> identification within third-party archives. +> +> Copyright 2017 Vercel, Inc. +> +> Licensed under the Apache License, Version 2.0 (the "License"); +> you may not use this file except in compliance with the License. +> You may obtain a copy of the License at +> +> http://www.apache.org/licenses/LICENSE-2.0 +> +> Unless required by applicable law or agreed to in writing, software +> distributed under the License is distributed on an "AS IS" BASIS, +> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +> See the License for the specific language governing permissions and +> limitations under the License. + +--------------------------------------- + +## @vitest/utils +License: MIT +Repository: https://github.com/vitest-dev/vitest + +> MIT License +> +> Copyright (c) 2021-Present VoidZero Inc. and Vitest contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## @voidzero-dev/vite-task-client +License: MIT +Repository: https://github.com/voidzero-dev/vite-task + +> MIT License +> +> Copyright (c) 2026-present, VoidZero Inc. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## anymatch +License: ISC +By: Elan Shanker +Repository: https://github.com/micromatch/anymatch + +> The ISC License +> +> Copyright (c) 2019 Elan Shanker, Paul Miller (https://paulmillr.com) +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## artichokie +License: MIT +By: sapphi-red, Evan You +Repository: https://github.com/sapphi-red/artichokie + +> MIT License +> +> Copyright (c) 2020-present, Yuxi (Evan) You +> Copyright (c) 2023-present, sapphi-red +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## binary-extensions +License: MIT +By: Sindre Sorhus +Repository: https://github.com/sindresorhus/binary-extensions + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> Copyright (c) Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## braces, fill-range, is-number +License: MIT +By: Jon Schlinkert, Brian Woodward, Elan Shanker, Eugene Sharygin, hemanth.hm +Repository: https://github.com/micromatch/braces + +License: MIT +By: Jon Schlinkert, Edo Rivai, Paul Miller, Rouven Weßling +Repository: https://github.com/jonschlinkert/fill-range + +License: MIT +By: Jon Schlinkert, Olsten Larck, Rouven Weßling +Repository: https://github.com/jonschlinkert/is-number + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## bundle-name, default-browser, default-browser-id, define-lazy-prop, is-docker, is-inside-container, is-wsl, open, run-applescript, wsl-utils +License: MIT +By: Sindre Sorhus +Repositories: https://github.com/sindresorhus/bundle-name, https://github.com/sindresorhus/default-browser, https://github.com/sindresorhus/default-browser-id, https://github.com/sindresorhus/define-lazy-prop, https://github.com/sindresorhus/is-docker, https://github.com/sindresorhus/is-inside-container, https://github.com/sindresorhus/is-wsl, https://github.com/sindresorhus/open, https://github.com/sindresorhus/run-applescript, https://github.com/sindresorhus/wsl-utils + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## cac +License: MIT +By: egoist +Repository: https://github.com/cacjs/cac + +> The MIT License (MIT) +> +> Copyright (c) EGOIST <0x142857@gmail.com> (https://github.com/egoist) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## chokidar +License: MIT +By: Paul Miller, Elan Shanker +Repository: https://github.com/paulmillr/chokidar + +> The MIT License (MIT) +> +> Copyright (c) 2012-2019 Paul Miller (https://paulmillr.com), Elan Shanker +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the “Software”), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## connect +License: MIT +By: TJ Holowaychuk, Douglas Christopher Wilson, Jonathan Ong, Tim Caswell +Repository: https://github.com/senchalabs/connect + +> (The MIT License) +> +> Copyright (c) 2010 Sencha Inc. +> Copyright (c) 2011 LearnBoost +> Copyright (c) 2011-2014 TJ Holowaychuk +> Copyright (c) 2015 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## convert-source-map +License: MIT +By: Thorsten Lorenz +Repository: https://github.com/thlorenz/convert-source-map + +> Copyright 2013 Thorsten Lorenz. +> All rights reserved. +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## cors +License: MIT +By: Troy Goode +Repository: https://github.com/expressjs/cors + +> (The MIT License) +> +> Copyright (c) 2013 Troy Goode +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## cross-spawn +License: MIT +By: André Cruz +Repository: https://github.com/moxystudio/node-cross-spawn + +> The MIT License (MIT) +> +> Copyright (c) 2018 Made With MOXY Lda +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## cssesc +License: MIT +By: Mathias Bynens +Repository: https://github.com/mathiasbynens/cssesc + +> Copyright Mathias Bynens +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## dotenv-expand +License: BSD-2-Clause +By: motdotla +Repository: https://github.com/motdotla/dotenv-expand + +> Copyright (c) 2016, Scott Motte +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above copyright notice, this +> list of conditions and the following disclaimer. +> +> * Redistributions in binary form must reproduce the above copyright notice, +> this list of conditions and the following disclaimer in the documentation +> and/or other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------- + +## ee-first +License: MIT +By: Jonathan Ong, Douglas Christopher Wilson +Repository: https://github.com/jonathanong/ee-first + +> The MIT License (MIT) +> +> Copyright (c) 2014 Jonathan Ong me@jongleberry.com +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## encodeurl +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/pillarjs/encodeurl + +> (The MIT License) +> +> Copyright (c) 2016 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## entities +License: BSD-2-Clause +By: Felix Boehm +Repository: https://github.com/fb55/entities + +> Copyright (c) Felix Böhm +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +> +> Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +> +> Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +> +> THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, +> EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------- + +## es-module-lexer +License: MIT +By: Guy Bedford +Repository: https://github.com/guybedford/es-module-lexer + +> MIT License +> ----------- +> +> Copyright (C) 2018-2022 Guy Bedford +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## escape-html +License: MIT +Repository: https://github.com/component/escape-html + +> (The MIT License) +> +> Copyright (c) 2012-2013 TJ Holowaychuk +> Copyright (c) 2015 Andreas Lubbe +> Copyright (c) 2015 Tiancheng "Timothy" Gu +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## estree-walker +License: MIT +By: Rich Harris +Repository: https://github.com/Rich-Harris/estree-walker + +> Copyright (c) 2015-20 [these people](https://github.com/Rich-Harris/estree-walker/graphs/contributors) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## etag +License: MIT +By: Douglas Christopher Wilson, David Björklund +Repository: https://github.com/jshttp/etag + +> (The MIT License) +> +> Copyright (c) 2014-2016 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## finalhandler +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/pillarjs/finalhandler + +> (The MIT License) +> +> Copyright (c) 2014-2017 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## follow-redirects +License: MIT +By: Ruben Verborgh, Olivier Lalonde, James Talmage +Repository: https://github.com/follow-redirects/follow-redirects + +> Copyright 2014–present Olivier Lalonde , James Talmage , Ruben Verborgh +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do +> so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +> IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## fresh-import +License: MIT +By: sapphi-red +Repository: https://github.com/sapphi-red/fresh-import + +> MIT License +> +> Copyright (c) 2026 sapphi-red +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## generic-names +License: MIT +By: Alexey Litvinov +Repository: https://github.com/css-modules/generic-names + +> The MIT License (MIT) +> +> Copyright (c) 2015 Alexey Litvinov +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## glob-parent +License: ISC +By: Gulp Team, Elan Shanker, Blaine Bublitz +Repository: https://github.com/gulpjs/glob-parent + +> The ISC License +> +> Copyright (c) 2015, 2019 Elan Shanker +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## host-validation-middleware +License: MIT +By: sapphi-red +Repository: https://github.com/sapphi-red/host-validation-middleware + +> MIT License +> +> Copyright (c) 2025 sapphi-red +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## http-proxy-3 +License: MIT +By: William Stein, Charlie Robbins, Jimb Esser, jcrugzz +Repository: https://github.com/sagemathinc/http-proxy-3 + +> node-http-3 +> +> Copyright (c) 2010-2025 William Stein, Charlie Robbins, Jarrett Cruger & the Contributors. +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## icss-utils +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/icss-utils + +> ISC License (ISC) +> Copyright 2018 Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## is-binary-path +License: MIT +By: Sindre Sorhus +Repository: https://github.com/sindresorhus/is-binary-path + +> MIT License +> +> Copyright (c) 2019 Sindre Sorhus (https://sindresorhus.com), Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## is-extglob +License: MIT +By: Jon Schlinkert +Repository: https://github.com/jonschlinkert/is-extglob + +> The MIT License (MIT) +> +> Copyright (c) 2014-2016, Jon Schlinkert +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-glob +License: MIT +By: Jon Schlinkert, Brian Woodward, Daniel Perez +Repository: https://github.com/micromatch/is-glob + +> The MIT License (MIT) +> +> Copyright (c) 2014-2017, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## isexe, which +License: ISC +By: Isaac Z. Schlueter +Repositories: https://github.com/isaacs/isexe, https://github.com/isaacs/node-which + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## js-tokens +License: MIT +By: Simon Lydell +Repository: https://github.com/lydell/js-tokens + +> The MIT License (MIT) +> +> Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024 Simon Lydell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## launch-editor, launch-editor-middleware +License: MIT +By: Evan You +Repositories: https://github.com/vitejs/launch-editor, https://github.com/vitejs/launch-editor + +> The MIT License (MIT) +> +> Copyright (c) 2017-present, Yuxi (Evan) You +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## lilconfig +License: MIT +By: antonk52 +Repository: https://github.com/antonk52/lilconfig + +> MIT License +> +> Copyright (c) 2022 Anton Kastritskiy +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## loader-utils +License: MIT +By: Tobias Koppers @sokra +Repository: https://github.com/webpack/loader-utils + +> Copyright JS Foundation and other contributors +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## lodash.camelcase +License: MIT +By: John-David Dalton, Blaine Bublitz, Mathias Bynens +Repository: https://github.com/lodash/lodash + +> Copyright jQuery Foundation and other contributors +> +> Based on Underscore.js, copyright Jeremy Ashkenas, +> DocumentCloud and Investigative Reporters & Editors +> +> This software consists of voluntary contributions made by many +> individuals. For exact contribution history, see the revision history +> available at https://github.com/lodash/lodash +> +> The following license applies to all parts of this software except as +> documented below: +> +> ==== +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +> +> ==== +> +> Copyright and related rights for sample code are waived via CC0. Sample +> code is defined as all source code displayed within the prose of the +> documentation. +> +> CC0: http://creativecommons.org/publicdomain/zero/1.0/ +> +> ==== +> +> Files located in the node_modules and vendor directories are externally +> maintained libraries used by this software which have their own +> licenses; we recommend you read them, as their terms may differ from the +> terms above. + +--------------------------------------- + +## magic-string +License: MIT +By: Rich Harris +Repository: https://github.com/Rich-Harris/magic-string + +> Copyright 2018 Rich Harris +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## mlly, ufo +License: MIT +Repositories: https://github.com/unjs/mlly, https://github.com/unjs/ufo + +> MIT License +> +> Copyright (c) Pooya Parsa +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## mrmime +License: MIT +By: Luke Edwards +Repository: https://github.com/lukeed/mrmime + +> The MIT License (MIT) +> +> Copyright (c) Luke Edwards (https://lukeed.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## normalize-path +License: MIT +By: Jon Schlinkert, Blaine Bublitz +Repository: https://github.com/jonschlinkert/normalize-path + +> The MIT License (MIT) +> +> Copyright (c) 2014-2018, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## object-assign +License: MIT +By: Sindre Sorhus +Repository: https://github.com/sindresorhus/object-assign + +> The MIT License (MIT) +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## obug +License: MIT +By: Kevin Deng +Repository: https://github.com/sxzz/obug + +> The MIT License (MIT) +> +> Copyright © 2025-PRESENT Kevin Deng (https://github.com/sxzz) +> Copyright (c) 2014-2017 TJ Holowaychuk +> Copyright (c) 2018-2021 Josh Junon +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## on-finished +License: MIT +By: Douglas Christopher Wilson, Jonathan Ong +Repository: https://github.com/jshttp/on-finished + +> (The MIT License) +> +> Copyright (c) 2013 Jonathan Ong +> Copyright (c) 2014 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## parse5 +License: MIT +By: Ivan Nikulin, James Garbutt, Felix Boehm, Titus +Repository: https://github.com/inikulin/parse5 + +> Copyright (c) 2013-2019 Ivan Nikulin (ifaaan@gmail.com, https://github.com/inikulin) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## parseurl +License: MIT +By: Douglas Christopher Wilson, Jonathan Ong +Repository: https://github.com/pillarjs/parseurl + +> (The MIT License) +> +> Copyright (c) 2014 Jonathan Ong +> Copyright (c) 2014-2017 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## path-key, shebang-regex +License: MIT +By: Sindre Sorhus +Repositories: https://github.com/sindresorhus/path-key, https://github.com/sindresorhus/shebang-regex + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## periscopic +License: MIT +Repository: https://github.com/Rich-Harris/periscopic + +> Copyright (c) 2019 Rich Harris +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## picocolors +License: ISC +By: Alexey Raspopov +Repository: https://github.com/alexeyraspopov/picocolors + +> ISC License +> +> Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-import +License: MIT +By: Maxime Thirouin +Repository: https://github.com/postcss/postcss-import + +> The MIT License (MIT) +> +> Copyright (c) 2014 Maxime Thirouin, Jason Campbell & Kevin Mårtensson +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-load-config +License: MIT +By: Michael Ciniawky, Ryan Dunckel, Mateusz Derks, Dalton Santos, Patrick Gilday, François Wouts +Repository: https://github.com/postcss/postcss-load-config + +> The MIT License (MIT) +> +> Copyright Michael Ciniawsky +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-modules +License: MIT +By: Alexander Madyankin +Repository: https://github.com/css-modules/postcss-modules + +> The MIT License (MIT) +> +> Copyright 2015-present Alexander Madyankin +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-modules-extract-imports +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/postcss-modules-extract-imports + +> Copyright 2015 Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-modules-local-by-default +License: MIT +By: Mark Dalgleish +Repository: https://github.com/css-modules/postcss-modules-local-by-default + +> The MIT License (MIT) +> +> Copyright 2015 Mark Dalgleish +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-modules-scope +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/postcss-modules-scope + +> ISC License (ISC) +> +> Copyright (c) 2015, Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-modules-values +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/postcss-modules-values + +> ISC License (ISC) +> +> Copyright (c) 2015, Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-selector-parser +License: MIT +By: Ben Briggs, Chris Eppstein +Repository: https://github.com/postcss/postcss-selector-parser + +> Copyright (c) Ben Briggs (http://beneb.info) +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-value-parser +License: MIT +By: Bogdan Chadkin +Repository: https://github.com/TrySound/postcss-value-parser + +> Copyright (c) Bogdan Chadkin +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## readdirp +License: MIT +By: Thorsten Lorenz, Paul Miller +Repository: https://github.com/paulmillr/readdirp + +> MIT License +> +> Copyright (c) 2012-2019 Thorsten Lorenz, Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## resolve.exports, totalist +License: MIT +By: Luke Edwards +Repositories: https://github.com/lukeed/resolve.exports, https://github.com/lukeed/totalist + +> The MIT License (MIT) +> +> Copyright (c) Luke Edwards (lukeed.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## shebang-command +License: MIT +By: Kevin Mårtensson +Repository: https://github.com/kevva/shebang-command + +> MIT License +> +> Copyright (c) Kevin Mårtensson (github.com/kevva) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## shell-quote +License: MIT +By: James Halliday +Repository: http://github.com/ljharb/shell-quote + +> The MIT License +> +> Copyright (c) 2013 James Halliday (mail@substack.net) +> +> Permission is hereby granted, free of charge, +> to any person obtaining a copy of this software and +> associated documentation files (the "Software"), to +> deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, +> merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom +> the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice +> shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## sirv +License: MIT +By: Luke Edwards +Repository: https://github.com/lukeed/sirv + +--------------------------------------- + +## statuses +License: MIT +By: Douglas Christopher Wilson, Jonathan Ong +Repository: https://github.com/jshttp/statuses + +> The MIT License (MIT) +> +> Copyright (c) 2014 Jonathan Ong +> Copyright (c) 2016 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## string-hash +License: CC0-1.0 +By: The Dark Sky Company +Repository: https://github.com/darkskyapp/string-hash + +--------------------------------------- + +## strip-literal +License: MIT +By: Anthony Fu +Repository: https://github.com/antfu/strip-literal + +> MIT License +> +> Copyright (c) 2022 Anthony Fu +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## to-regex-range +License: MIT +By: Jon Schlinkert, Rouven Weßling +Repository: https://github.com/micromatch/to-regex-range + +> The MIT License (MIT) +> +> Copyright (c) 2015-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## unpipe +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/stream-utils/unpipe + +> (The MIT License) +> +> Copyright (c) 2015 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## util-deprecate +License: MIT +By: Nathan Rajlich +Repository: https://github.com/TooTallNate/util-deprecate + +> (The MIT License) +> +> Copyright (c) 2014 Nathan Rajlich +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## utils-merge +License: MIT +By: Jared Hanson +Repository: https://github.com/jaredhanson/utils-merge + +> The MIT License (MIT) +> +> Copyright (c) 2013-2017 Jared Hanson +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## vary +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/jshttp/vary + +> (The MIT License) +> +> Copyright (c) 2014-2017 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## ws +License: MIT +By: Einar Otto Stangvik +Repository: https://github.com/websockets/ws + +> Copyright (c) 2011 Einar Otto Stangvik +> Copyright (c) 2013 Arnout Kazemier and contributors +> Copyright (c) 2016 Luigi Pinca and contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ +zustand@4.5.7 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2019 Paul Henschel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ +zustand@5.0.14 +Declared license: MIT +================================================================================ + +--- LICENSE --- + +MIT License + +Copyright (c) 2019 Paul Henschel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/web/assets/graph-vendor.css b/web/assets/graph-vendor.css new file mode 100644 index 0000000..e272a7b --- /dev/null +++ b/web/assets/graph-vendor.css @@ -0,0 +1 @@ +.react-flow{--xy-edge-stroke-default:#b1b1b7;--xy-edge-stroke-width-default:1;--xy-edge-stroke-selected-default:#555;--xy-connectionline-stroke-default:#b1b1b7;--xy-connectionline-stroke-width-default:1;--xy-attribution-background-color-default:#ffffff80;--xy-minimap-background-color-default:#fff;--xy-minimap-mask-background-color-default:#f0f0f099;--xy-minimap-mask-stroke-color-default:transparent;--xy-minimap-mask-stroke-width-default:1;--xy-minimap-node-background-color-default:#e2e2e2;--xy-minimap-node-stroke-color-default:transparent;--xy-minimap-node-stroke-width-default:2;--xy-background-color-default:transparent;--xy-background-pattern-dots-color-default:#91919a;--xy-background-pattern-lines-color-default:#eee;--xy-background-pattern-cross-color-default:#e2e2e2;background-color:var(--xy-background-color,var(--xy-background-color-default));--xy-node-border-default:1px solid #bbb;--xy-node-border-selected-default:1px solid #555;--xy-handle-background-color-default:#333;--xy-selection-background-color-default:#9696b41a;--xy-selection-border-default:1px dotted #9b9b9bcc;--xy-resize-background-color-default:#3367d9;direction:ltr}.react-flow.dark{--xy-edge-stroke-default:#3e3e3e;--xy-edge-stroke-width-default:1;--xy-edge-stroke-selected-default:#727272;--xy-connectionline-stroke-default:#b1b1b7;--xy-connectionline-stroke-width-default:1;--xy-attribution-background-color-default:#96969640;--xy-minimap-background-color-default:#141414;--xy-minimap-mask-background-color-default:#3c3c3c99;--xy-minimap-mask-stroke-color-default:transparent;--xy-minimap-mask-stroke-width-default:1;--xy-minimap-node-background-color-default:#2b2b2b;--xy-minimap-node-stroke-color-default:transparent;--xy-minimap-node-stroke-width-default:2;--xy-background-color-default:#141414;--xy-background-pattern-dots-color-default:#777;--xy-background-pattern-lines-color-default:#777;--xy-background-pattern-cross-color-default:#777;--xy-node-color-default:#f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props,var(--xy-background-color,var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke,var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width,var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke,var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width,var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{pointer-events:none;position:absolute;overflow:visible}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:.5s linear infinite dashdraw}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected,var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke,var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke,var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:.5s linear infinite dashdraw}svg.react-flow__connectionline{z-index:1001;position:absolute;overflow:visible}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default;position:absolute}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:0 0;pointer-events:none}.react-flow__nodesselection-rect{pointer-events:all;cursor:grab;position:absolute}.react-flow__handle{pointer-events:none;background-color:var(--xy-handle-background-color,var(--xy-handle-background-color-default));min-width:5px;min-height:5px;position:absolute}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;bottom:0;left:50%;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{z-index:5;margin:15px;position:absolute}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px)translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px)translateY(-50%)}.react-flow__attribution{background:var(--xy-attribution-background-color,var(--xy-attribution-background-color-default));margin:0;padding:2px 3px;font-size:10px}.react-flow__attribution a{color:#999;text-decoration:none}@keyframes dashdraw{0%{stroke-dashoffset:10px}}.react-flow__edgelabel-renderer{pointer-events:none;user-select:none;width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__viewport-portal{user-select:none;width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__minimap{background:var(--xy-minimap-background-color-props,var(--xy-minimap-background-color,var(--xy-minimap-background-color-default)))}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var(--xy-minimap-mask-background-color-props,var(--xy-minimap-mask-background-color,var(--xy-minimap-mask-background-color-default)));stroke:var(--xy-minimap-mask-stroke-color-props,var(--xy-minimap-mask-stroke-color,var(--xy-minimap-mask-stroke-color-default)));stroke-width:var(--xy-minimap-mask-stroke-width-props,var(--xy-minimap-mask-stroke-width,var(--xy-minimap-mask-stroke-width-default)))}.react-flow__minimap-node{fill:var(--xy-minimap-node-background-color-props,var(--xy-minimap-node-background-color,var(--xy-minimap-node-background-color-default)));stroke:var(--xy-minimap-node-stroke-color-props,var(--xy-minimap-node-stroke-color,var(--xy-minimap-node-stroke-color-default)));stroke-width:var(--xy-minimap-node-stroke-width-props,var(--xy-minimap-node-stroke-width,var(--xy-minimap-node-stroke-width-default)))}.react-flow__background-pattern.dots{fill:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-dots-color-default)))}.react-flow__background-pattern.lines{stroke:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-lines-color-default)))}.react-flow__background-pattern.cross{stroke:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-cross-color-default)))}.react-flow__controls{flex-direction:column;display:flex}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{justify-content:center;align-items:center;width:26px;height:26px;padding:4px;display:flex}.react-flow__controls-button svg{fill:currentColor;width:100%;max-width:12px;max-height:12px}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{border:var(--xy-node-border,var(--xy-node-border-default));color:var(--xy-node-color,var(--xy-node-color-default))}.react-flow__node-input.selected,.react-flow__node-input:focus,.react-flow__node-input:focus-visible,.react-flow__node-default.selected,.react-flow__node-default:focus,.react-flow__node-default:focus-visible,.react-flow__node-output.selected,.react-flow__node-output:focus,.react-flow__node-output:focus-visible,.react-flow__node-group.selected,.react-flow__node-group:focus,.react-flow__node-group:focus-visible{border:var(--xy-node-border-selected,var(--xy-node-border-selected-default));outline:none}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color,var(--xy-selection-background-color-default));border:var(--xy-selection-border,var(--xy-selection-border-default))}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{background-color:var(--xy-resize-background-color,var(--xy-resize-background-color-default));border:1px solid #fff;border-radius:1px;width:5px;height:5px;translate:-50% -50%}.react-flow__resize-control.handle.left{top:50%;left:0}.react-flow__resize-control.handle.right{top:50%;left:100%}.react-flow__resize-control.handle.top{top:0;left:50%}.react-flow__resize-control.handle.bottom{top:100%;left:50%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color,var(--xy-resize-background-color-default));border-style:solid;border-width:0}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;height:100%;top:0;transform:translate(-50%)}.react-flow__resize-control.line.left{border-left-width:1px;left:0}.react-flow__resize-control.line.right{border-right-width:1px;left:100%}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{width:100%;height:1px;left:0;transform:translateY(-50%)}.react-flow__resize-control.line.top{border-top-width:1px;top:0}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%} diff --git a/web/assets/graph-vendor.js b/web/assets/graph-vendor.js new file mode 100644 index 0000000..5b566a0 --- /dev/null +++ b/web/assets/graph-vendor.js @@ -0,0 +1,19 @@ +import{n as e,t}from"./rolldown-runtime.js";var n=t((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function D(e,t){return E(e.type,t,e.props)}function O(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function k(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var A=/\/+/g;function j(e,t){return typeof e==`object`&&e&&e.key!=null?k(``+e.key):t.toString(36)}function M(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function N(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,N(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+j(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(A,`$&/`)+`/`),N(o,r,i,``,function(e){return e})):o!=null&&(O(o)&&(o=D(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(A,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=n()})),i=t((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,O());else{var t=n(l);t!==null&&j(x,t.startTime-e)}}var S=!1,C=-1,w=5,T=-1;function E(){return g?!0:!(e.unstable_now()-Tt&&E());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&j(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?O():S=!1}}}var O;if(typeof y==`function`)O=function(){y(D)};else if(typeof MessageChannel<`u`){var k=new MessageChannel,A=k.port2;k.port1.onmessage=D,O=function(){A.postMessage(null)}}else O=function(){_(D,0)};function j(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,j(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,O()))),r},e.unstable_shouldYield=E,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),a=t(((e,t)=>{t.exports=i()})),o=t((e=>{var t=r();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=o()})),c=t((e=>{var t=a(),n=r(),i=s();function o(e){var t=`https://react.dev/errors/`+e;if(1te||(e.current=ee[te],ee[te]=null,te--)}function re(e,t){te++,ee[te]=e.current,e.current=t}var ie=ne(null),ae=ne(null),oe=ne(null),se=ne(null);function ce(e,t){switch(re(oe,t),re(ae,e),re(ie,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Yd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Yd(t),e=Xd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}z(ie),re(ie,e)}function le(){z(ie),z(ae),z(oe)}function ue(e){e.memoizedState!==null&&re(se,e);var t=ie.current,n=Xd(t,e.type);t!==n&&(re(ae,e),re(ie,n))}function de(e){ae.current===e&&(z(ie),z(ae)),se.current===e&&(z(se),op._currentValue=R)}var fe,pe;function me(e){if(fe===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);fe=t&&t[1]||``,pe=-1)`:-1i||c[r]!==l[i]){var u=` +`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{he=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?me(n):``}function _e(e,t){switch(e.tag){case 26:case 27:case 5:return me(e.type);case 16:return me(`Lazy`);case 13:return e.child!==t&&t!==null?me(`Suspense Fallback`):me(`Suspense`);case 19:return me(`SuspenseList`);case 0:case 15:return ge(e.type,!1);case 11:return ge(e.type.render,!1);case 1:return ge(e.type,!0);case 31:return me(`Activity`);default:return``}}function ve(e){try{var t=``,n=null;do t+=_e(e,n),n=e,e=e.return;while(e);return t}catch(e){return` +Error generating stack: `+e.message+` +`+e.stack}}var ye=Object.prototype.hasOwnProperty,be=t.unstable_scheduleCallback,xe=t.unstable_cancelCallback,Se=t.unstable_shouldYield,Ce=t.unstable_requestPaint,we=t.unstable_now,Te=t.unstable_getCurrentPriorityLevel,Ee=t.unstable_ImmediatePriority,De=t.unstable_UserBlockingPriority,Oe=t.unstable_NormalPriority,ke=t.unstable_LowPriority,Ae=t.unstable_IdlePriority,je=t.log,Me=t.unstable_setDisableYieldValue,Ne=null,Pe=null;function Fe(e){if(typeof je==`function`&&Me(e),Pe&&typeof Pe.setStrictMode==`function`)try{Pe.setStrictMode(Ne,e)}catch{}}var Ie=Math.clz32?Math.clz32:ze,Le=Math.log,Re=Math.LN2;function ze(e){return e>>>=0,e===0?32:31-(Le(e)/Re|0)|0}var Be=256,Ve=262144,He=4194304;function Ue(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function We(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Ue(n))):i=Ue(o):i=Ue(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Ue(n))):i=Ue(o)):i=Ue(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Ge(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Ke(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function qe(){var e=He;return He<<=1,!(He&62914560)&&(He=4194304),e}function Je(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Ye(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Xe(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),ln=!1;if(cn)try{var un={};Object.defineProperty(un,"passive",{get:function(){ln=!0}}),window.addEventListener(`test`,un,un),window.removeEventListener(`test`,un,un)}catch{ln=!1}var dn=null,fn=null,pn=null;function mn(){if(pn)return pn;var e,t=fn,n=t.length,r,i=`value`in dn?dn.value:dn.textContent,a=i.length;for(e=0;e=Kn),Yn=` `,Xn=!1;function Zn(e,t){switch(e){case`keyup`:return Wn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function Qn(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var $n=!1;function er(e,t){switch(e){case`compositionend`:return Qn(t);case`keypress`:return t.which===32?(Xn=!0,Yn):null;case`textInput`:return e=t.data,e===Yn&&Xn?null:e;default:return null}}function tr(e,t){if($n)return e===`compositionend`||!Gn&&Zn(e,t)?(e=mn(),pn=fn=dn=null,$n=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Cr(n)}}function Tr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Tr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Er(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Ft(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ft(e.document)}return t}function Dr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Or=cn&&`documentMode`in document&&11>=document.documentMode,kr=null,Ar=null,jr=null,Mr=!1;function Nr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Mr||kr==null||kr!==Ft(r)||(r=kr,`selectionStart`in r&&Dr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),jr&&Sr(jr,r)||(jr=r,r=Nd(Ar,`onSelect`),0>=o,i-=o,Ti=1<<32-Ie(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),o=a(_,o,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),Pi&&Di(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(i,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(i,h),s=a(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(i,h),Pi&&Di(i,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(i,v.value,l),v!==null&&(s=a(v,s,g),d===null?u=v:d.sibling=v,d=v);return Pi&&Di(i,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,i,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=a(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(i,e)}),Pi&&Di(i,g),u}function b(e,r,a,c){if(typeof a==`object`&&a&&a.type===y&&a.key===null&&(a=a.props.children),typeof a==`object`&&a){switch(a.$$typeof){case _:a:{for(var l=a.key;r!==null;){if(r.key===l){if(l=a.type,l===y){if(r.tag===7){n(e,r.sibling),c=i(r,a.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===O&&Da(l)===r.type){n(e,r.sibling),c=i(r,a.props),Pa(c,a),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}a.type===y?(c=fi(a.props.children,e.mode,c,a.key),c.return=e,e=c):(c=di(a.type,a.key,a.props,null,e.mode,c),Pa(c,a),c.return=e,e=c)}return s(e);case v:a:{for(l=a.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===a.containerInfo&&r.stateNode.implementation===a.implementation){n(e,r.sibling),c=i(r,a.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=hi(a,e.mode,c),c.return=e,e=c}return s(e);case O:return a=Da(a),b(e,r,a,c)}if(F(a))return h(e,r,a,c);if(M(a)){if(l=M(a),typeof l!=`function`)throw Error(o(150));return a=l.call(a),g(e,r,a,c)}if(typeof a.then==`function`)return b(e,r,Na(a),c);if(a.$$typeof===C)return b(e,r,na(e,a),c);Fa(e,a)}return typeof a==`string`&&a!==``||typeof a==`number`||typeof a==`bigint`?(a=``+a,r!==null&&r.tag===6?(n(e,r.sibling),c=i(r,a),c.return=e,e=c):(n(e,r),c=pi(a,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Ma=0;var i=b(e,t,n,r);return ja=null,i}catch(t){if(t===Sa||t===Ca)throw t;var a=si(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var La=Ia(!0),Ra=Ia(!1),za=!1;function Ba(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Va(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Ha(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ua(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,Hl&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ii(e),ri(e,null,n),t}return ei(e,r,t,n),ii(e)}function Wa(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Qe(e,n)}}function Ga(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ka=!1;function qa(){if(Ka){var e=pa;if(e!==null)throw e}}function Ja(e,t,n,r){Ka=!1;var i=e.updateQueue;za=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(W&f)===f:(r&f)===f){f!==0&&f===fa&&(Ka=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:za=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Zl|=o,e.lanes=o,e.memoizedState=d}}function Ya(e,t){if(typeof e!=`function`)throw Error(o(191,e));e.call(t)}function Xa(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=I.T,s={};I.T=s,Is(e,!1,t,n);try{var c=i(),l=I.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Fs(e,t,ga(c,r),yu(e)):Fs(e,t,r,yu(e))}catch(n){Fs(e,t,{then:function(){},status:`rejected`,reason:n},yu())}finally{L.p=a,o!==null&&s.types!==null&&(o.types=s.types),I.T=o}}function Ts(){}function Es(e,t,n,r){if(e.tag!==5)throw Error(o(476));var i=Ds(e).queue;ws(e,i,t,R,n===null?Ts:function(){return Os(e),n(r)})}function Ds(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:R,baseState:R,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Lo,lastRenderedState:R},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Lo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Os(e){var t=Ds(e);t.next===null&&(t=e.alternate.memoizedState),Fs(e,t.next.queue,{},yu())}function ks(){return ta(op)}function As(){return Mo().memoizedState}function js(){return Mo().memoizedState}function Ms(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=yu();e=Ha(n);var r=Ua(t,e,n);r!==null&&(xu(r,t,n),Wa(r,t,n)),t={cache:ca()},e.payload=t;return}t=t.return}}function Ns(e,t,n){var r=yu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Ls(e)?Rs(t,n):(n=ti(e,t,n,r),n!==null&&(xu(n,e,r),zs(n,t,r)))}function Ps(e,t,n){Fs(e,t,n,yu())}function Fs(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Ls(e))Rs(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,xr(s,o))return ei(e,t,i,0),Ul===null&&$r(),!1}catch{}if(n=ti(e,t,i,r),n!==null)return xu(n,e,r),zs(n,t,r),!0}return!1}function Is(e,t,n,r){if(r={lane:2,revertLane:vd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Ls(e)){if(t)throw Error(o(479))}else t=ti(e,n,r,2),t!==null&&xu(t,e,2)}function Ls(e){var t=e.alternate;return e===V||t!==null&&t===V}function Rs(e,t){go=ho=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function zs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Qe(e,n)}}var Bs={readContext:ta,use:Fo,useCallback:So,useContext:So,useEffect:So,useImperativeHandle:So,useLayoutEffect:So,useInsertionEffect:So,useMemo:So,useReducer:So,useRef:So,useState:So,useDebugValue:So,useDeferredValue:So,useTransition:So,useSyncExternalStore:So,useId:So,useHostTransitionStatus:So,useFormState:So,useActionState:So,useOptimistic:So,useMemoCache:So,useCacheRefresh:So};Bs.useEffectEvent=So;var Vs={readContext:ta,use:Fo,useCallback:function(e,t){return jo().memoizedState=[e,t===void 0?null:t],e},useContext:ta,useEffect:ds,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ls(4194308,4,_s.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ls(4194308,4,e,t)},useInsertionEffect:function(e,t){ls(4,2,e,t)},useMemo:function(e,t){var n=jo();t=t===void 0?null:t;var r=e();if(_o){Fe(!0);try{e()}finally{Fe(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=jo();if(n!==void 0){var i=n(t);if(_o){Fe(!0);try{n(t)}finally{Fe(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ns.bind(null,V,e),[r.memoizedState,e]},useRef:function(e){var t=jo();return e={current:e},t.memoizedState=e},useState:function(e){e=qo(e);var t=e.queue,n=Ps.bind(null,V,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:ys,useDeferredValue:function(e,t){return Ss(jo(),e,t)},useTransition:function(){var e=qo(!1);return e=ws.bind(null,V,e.queue,!0,!1),jo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=V,i=jo();if(Pi){if(n===void 0)throw Error(o(407));n=n()}else{if(n=t(),Ul===null)throw Error(o(349));W&127||Ho(r,t,n)}i.memoizedState=n;var a={value:n,getSnapshot:t};return i.queue=a,ds(Wo.bind(null,r,a,e),[e]),r.flags|=2048,ss(9,{destroy:void 0},Uo.bind(null,r,a,n,t),null),n},useId:function(){var e=jo(),t=Ul.identifierPrefix;if(Pi){var n=Ei,r=Ti;n=(r&~(1<<32-Ie(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=vo++,0<\/script>`,a=a.removeChild(a.firstChild);break;case`select`:a=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?a.multiple=!0:r.size&&(a.size=r.size);break;default:a=typeof r.is==`string`?s.createElement(i,{is:r.is}):s.createElement(i)}}a[at]=t,a[ot]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)a.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=a;a:switch(Hd(a,i,r),i){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Pc(t)}}return zc(t),Fc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Pc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(o(166));if(e=oe.current,Vi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,i=Mi,i!==null)switch(i.tag){case 27:case 5:r=i.memoizedProps}e[at]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||zd(e.nodeValue,n)),e||Ri(t,!0)}else e=Jd(e).createTextNode(r),e[at]=t,t.stateNode=e}return zc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Vi(t),n!==null){if(e===null){if(!r)throw Error(o(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(o(557));e[at]=t}else Hi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;zc(t),e=!1}else n=Ui(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(co(t),t):(co(t),null);if(t.flags&128)throw Error(o(558))}return zc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(i=Vi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!i)throw Error(o(318));if(i=t.memoizedState,i=i===null?null:i.dehydrated,!i)throw Error(o(317));i[at]=t}else Hi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;zc(t),i=!1}else i=Ui(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=i),i=!0;if(!i)return t.flags&256?(co(t),t):(co(t),null)}return co(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,i=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(i=r.alternate.memoizedState.cachePool.pool),a=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(a=r.memoizedState.cachePool.pool),a!==i&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Lc(t,t.updateQueue),zc(t),null);case 4:return le(),e===null&&kd(t.stateNode.containerInfo),zc(t),null;case 10:return Yi(t.type),zc(t),null;case 19:if(z(lo),r=t.memoizedState,r===null)return zc(t),null;if(i=(t.flags&128)!=0,a=r.rendering,a===null)if(i)Rc(r,!1);else{if(Xl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(a=uo(e),a!==null){for(t.flags|=128,Rc(r,!1),e=a.updateQueue,t.updateQueue=e,Lc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)ui(n,e),n=n.sibling;return re(lo,lo.current&1|2),Pi&&Di(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&we()>su&&(t.flags|=128,i=!0,Rc(r,!1),t.lanes=4194304)}else{if(!i)if(e=uo(a),e!==null){if(t.flags|=128,i=!0,e=e.updateQueue,t.updateQueue=e,Lc(t,e),Rc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!a.alternate&&!Pi)return zc(t),null}else 2*we()-r.renderingStartTime>su&&n!==536870912&&(t.flags|=128,i=!0,Rc(r,!1),t.lanes=4194304);r.isBackwards?(a.sibling=t.child,t.child=a):(e=r.last,e===null?t.child=a:e.sibling=a,r.last=a)}return r.tail===null?(zc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=we(),e.sibling=null,n=lo.current,re(lo,i?n&1|2:n&1),Pi&&Di(t,r.treeForkCount),e);case 22:case 23:return co(t),to(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(zc(t),t.subtreeFlags&6&&(t.flags|=8192)):zc(t),n=t.updateQueue,n!==null&&Lc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&z(va),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Yi(sa),zc(t),null;case 25:return null;case 30:return null}throw Error(o(156,t.tag))}function Vc(e,t){switch(Ai(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Yi(sa),le(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return de(t),null;case 31:if(t.memoizedState!==null){if(co(t),t.alternate===null)throw Error(o(340));Hi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(co(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(o(340));Hi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return z(lo),null;case 4:return le(),null;case 10:return Yi(t.type),null;case 22:case 23:return co(t),to(),e!==null&&z(va),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Yi(sa),null;case 25:return null;default:return null}}function Hc(e,t){switch(Ai(t),t.tag){case 3:Yi(sa),le();break;case 26:case 27:case 5:de(t);break;case 4:le();break;case 31:t.memoizedState!==null&&co(t);break;case 13:co(t);break;case 19:z(lo);break;case 10:Yi(t.type);break;case 22:case 23:co(t),to(),e!==null&&z(va);break;case 24:Yi(sa)}}function Uc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Zu(t,t.return,e)}}function Wc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Zu(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Zu(t,t.return,e)}}function Gc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Xa(t,n)}catch(t){Zu(e,e.return,t)}}}function Kc(e,t,n){n.props=Js(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Zu(e,t,n)}}function qc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Zu(e,t,n)}}function Jc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Zu(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Zu(e,t,n)}else n.current=null}function Yc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Zu(e,e.return,t)}}function Xc(e,t,n){try{var r=e.stateNode;Ud(r,e.type,n,t),r[ot]=t}catch(t){Zu(e,e.return,t)}}function Zc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&of(e.type)||e.tag===4}function Qc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Zc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&of(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function $c(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Qt));else if(r!==4&&(r===27&&of(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for($c(e,t,n),e=e.sibling;e!==null;)$c(e,t,n),e=e.sibling}function el(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&of(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(el(e,t,n),e=e.sibling;e!==null;)el(e,t,n),e=e.sibling}function tl(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Hd(t,r,n),t[at]=e,t[ot]=n}catch(t){Zu(e,e.return,t)}}var nl=!1,rl=!1,il=!1,al=typeof WeakSet==`function`?WeakSet:Set,ol=null;function sl(e,t){if(e=e.containerInfo,Kd=q,e=Er(e),Dr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var i=r.anchorOffset,a=r.focusNode;r=r.focusOffset;try{n.nodeType,a.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||i!==0&&f.nodeType!==3||(c=s+i),f!==a||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===i&&(c=s),p===a&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(qd={focusedElem:e,selectionRange:n},q=!1,ol=t;ol!==null;)if(t=ol,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,ol=e;else for(;ol!==null;){switch(t=ol,a=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Hd(a,r,n),a[at]=e,vt(a),r=a;break a;case`link`:var s=Yf(`link`,`href`,i).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=wr(s,h),v=wr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,I.T=null,n=hu,hu=null;var a=du,s=pu;if(uu=0,fu=du=null,pu=0,Hl&6)throw Error(o(331));var c=Hl;if(Hl|=4,Ll(a.current),kl(a,a.current,s,n),Hl=c,dd(0,!1),Pe&&typeof Pe.onPostCommitFiberRoot==`function`)try{Pe.onPostCommitFiberRoot(Ne,a)}catch{}return!0}finally{L.p=i,I.T=r,qu(e,t)}}function Xu(e,t,n){t=_i(n,t),t=ec(e.stateNode,t,2),e=Ua(e,t,2),e!==null&&(Ye(e,2),ud(e))}function Zu(e,t,n){if(e.tag===3)Xu(e,e,n);else for(;t!==null;){if(t.tag===3){Xu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(lu===null||!lu.has(r))){e=_i(n,e),n=tc(2),r=Ua(t,n,2),r!==null&&(nc(n,r,t,e),Ye(r,2),ud(r));break}}t=t.return}}function Qu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Vl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Jl=!0,i.add(n),e=$u.bind(null,e,t,n),t.then(e,e))}function $u(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,Ul===e&&(W&n)===n&&(Xl===4||Xl===3&&(W&62914560)===W&&300>we()-au?!(Hl&2)&&Ou(e,0):$l|=n,tu===W&&(tu=0)),ud(e)}function ed(e,t){t===0&&(t=qe()),e=ni(e,t),e!==null&&(Ye(e,t),ud(e))}function td(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),ed(e,n)}function nd(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,i=e.memoizedState;i!==null&&(n=i.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(o(314))}r!==null&&r.delete(t),ed(e,n)}function rd(e,t){return be(e,t)}var id=null,ad=null,od=!1,sd=!1,cd=!1,ld=0;function ud(e){e!==ad&&e.next===null&&(ad===null?id=ad=e:ad=ad.next=e),sd=!0,od||(od=!0,_d())}function dd(e,t){if(!cd&&sd){cd=!0;do for(var n=!1,r=id;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Ie(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,gd(r,a))}else a=W,a=We(r,r===Ul?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||Ge(r,a)||(n=!0,gd(r,a));r=r.next}while(n);cd=!1}}function fd(){pd()}function pd(){sd=od=!1;var e=0;ld!==0&&$d()&&(e=ld);for(var t=we(),n=null,r=id;r!==null;){var i=r.next,a=md(r,t);a===0?(r.next=null,n===null?id=i:n.next=i,i===null&&(ad=n)):(n=r,(e!==0||a&3)&&(sd=!0)),r=i}uu!==0&&uu!==5||dd(e,!1),ld!==0&&(ld=0)}function md(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Wd(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function kf(e,t,n){var r=Of;if(r&&typeof t==`string`&&t){var i=Lt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),Cf.has(i)||(Cf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Hd(t,`link`,e),vt(t),r.head.appendChild(t)))}}function Af(e){Tf.D(e),kf(`dns-prefetch`,e,null)}function jf(e,t){Tf.C(e,t),kf(`preconnect`,e,t)}function Mf(e,t,n){Tf.L(e,t,n);var r=Of;if(r&&e&&t){var i=`link[rel="preload"][as="`+Lt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Lt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Lt(n.imageSizes)+`"]`)):i+=`[href="`+Lt(e)+`"]`;var a=i;switch(t){case`style`:a=Rf(e);break;case`script`:a=Hf(e)}Sf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),Sf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(zf(a))||t===`script`&&r.querySelector(Uf(a))||(t=r.createElement(`link`),Hd(t,`link`,e),vt(t),r.head.appendChild(t)))}}function Nf(e,t){Tf.m(e,t);var n=Of;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Lt(r)+`"][href="`+Lt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Hf(e)}if(!Sf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),Sf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Uf(a)))return}r=n.createElement(`link`),Hd(r,`link`,e),vt(r),n.head.appendChild(r)}}}function Pf(e,t,n){Tf.S(e,t,n);var r=Of;if(r&&e){var i=_t(r).hoistableStyles,a=Rf(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(zf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=Sf.get(a))&&Kf(e,n);var c=o=r.createElement(`link`);vt(c),Hd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Gf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Ff(e,t){Tf.X(e,t);var n=Of;if(n&&e){var r=_t(n).hoistableScripts,i=Hf(e),a=r.get(i);a||(a=n.querySelector(Uf(i)),a||(e=h({src:e,async:!0},t),(t=Sf.get(i))&&qf(e,t),a=n.createElement(`script`),vt(a),Hd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function If(e,t){Tf.M(e,t);var n=Of;if(n&&e){var r=_t(n).hoistableScripts,i=Hf(e),a=r.get(i);a||(a=n.querySelector(Uf(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=Sf.get(i))&&qf(e,t),a=n.createElement(`script`),vt(a),Hd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Lf(e,t,n,r){var i=(i=oe.current)?wf(i):null;if(!i)throw Error(o(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Rf(n.href),n=_t(i).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Rf(n.href);var a=_t(i).hoistableStyles,s=a.get(e);if(s||(i=i.ownerDocument||i,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},a.set(e,s),(a=i.querySelector(zf(e)))&&!a._p&&(s.instance=a,s.state.loading=5),Sf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},Sf.set(e,n),a||Vf(i,e,n,s.state))),t&&r===null)throw Error(o(528,``));return s}if(t&&r!==null)throw Error(o(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Hf(n),n=_t(i).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(o(444,e))}}function Rf(e){return`href="`+Lt(e)+`"`}function zf(e){return`link[rel="stylesheet"][`+e+`]`}function Bf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Vf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Hd(t,`link`,n),vt(t),e.head.appendChild(t))}function Hf(e){return`[src="`+Lt(e)+`"]`}function Uf(e){return`script[async]`+e}function Wf(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Lt(n.href)+`"]`);if(r)return t.instance=r,vt(r),r;var i=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),vt(r),Hd(r,`style`,i),Gf(r,n.precedence,e),t.instance=r;case`stylesheet`:i=Rf(n.href);var a=e.querySelector(zf(i));if(a)return t.state.loading|=4,t.instance=a,vt(a),a;r=Bf(n),(i=Sf.get(i))&&Kf(r,i),a=(e.ownerDocument||e).createElement(`link`),vt(a);var s=a;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Hd(a,`link`,r),t.state.loading|=4,Gf(a,n.precedence,e),t.instance=a;case`script`:return a=Hf(n.src),(i=e.querySelector(Uf(a)))?(t.instance=i,vt(i),i):(r=n,(i=Sf.get(a))&&(r=h({},n),qf(r,i)),e=e.ownerDocument||e,i=e.createElement(`script`),vt(i),Hd(i,`link`,r),e.head.appendChild(i),t.instance=i);case`void`:return null;default:throw Error(o(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Gf(r,n.precedence,e));return t.instance}function Gf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Zf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Qf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function $f(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Rf(r.href),a=t.querySelector(zf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=np.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,vt(a);return}a=t.ownerDocument||t,r=Bf(r),(i=Sf.get(i))&&Kf(r,i),a=a.createElement(`link`),vt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Hd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=np.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var ep=0;function tp(e,t){return e.stylesheets&&e.count===0&&ip(e,e.stylesheets),0ep?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function np(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)ip(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var rp=null;function ip(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,rp=new Map,t.forEach(ap,e),rp=null,np.call(e))}function ap(e,t){if(!(t.state.loading&4)){var n=rp.get(e);if(n)var r=n.get(null);else{n=new Map,rp.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=c()})),u=t((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),d=t(((e,t)=>{t.exports=u()})),f=e(r(),1),p=d();function m(e){if(typeof e==`string`||typeof e==`number`)return``+e;let t=``;if(Array.isArray(e))for(let n=0,r;n{}};function g(){for(var e=0,t=arguments.length,n={},r;e=0&&(n=e.slice(r+1),e=e.slice(0,r)),e&&!t.hasOwnProperty(e))throw Error(`unknown type: `+e);return{type:e,name:n}})}_.prototype=g.prototype={constructor:_,on:function(e,t){var n=this._,r=v(e+``,n),i,a=-1,o=r.length;if(arguments.length<2){for(;++a0)for(var n=Array(i),r=0,i,a;r=0&&(t=e.slice(0,n))!==`xmlns`&&(e=e.slice(n+1)),x.hasOwnProperty(t)?{space:x[t],local:e}:e}function C(e){return function(){var t=this.ownerDocument,n=this.namespaceURI;return n===`http://www.w3.org/1999/xhtml`&&t.documentElement.namespaceURI===`http://www.w3.org/1999/xhtml`?t.createElement(e):t.createElementNS(n,e)}}function w(e){return function(){return this.ownerDocument.createElementNS(e.space,e.local)}}function T(e){var t=S(e);return(t.local?w:C)(t)}function E(){}function D(e){return e==null?E:function(){return this.querySelector(e)}}function O(e){typeof e!=`function`&&(e=D(e));for(var t=this._groups,n=t.length,r=Array(n),i=0;i=v&&(v=_+1);!(b=g[v])&&++v=0;)(o=r[i])&&(a&&o.compareDocumentPosition(a)^4&&a.parentNode.insertBefore(o,a),a=o);return this}function ve(e){e||=ye;function t(t,n){return t&&n?e(t.__data__,n.__data__):!t-!n}for(var n=this._groups,r=n.length,i=Array(r),a=0;at?1:e>=t?0:NaN}function be(){var e=arguments[0];return arguments[0]=this,e.apply(null,arguments),this}function xe(){return Array.from(this)}function Se(){for(var e=this._groups,t=0,n=e.length;t1?this.each((t==null?Pe:typeof t==`function`?Ie:Fe)(e,t,n??``)):Re(this.node(),e)}function Re(e,t){return e.style.getPropertyValue(t)||Ne(e).getComputedStyle(e,null).getPropertyValue(t)}function ze(e){return function(){delete this[e]}}function Be(e,t){return function(){this[e]=t}}function Ve(e,t){return function(){var n=t.apply(this,arguments);n==null?delete this[e]:this[e]=n}}function He(e,t){return arguments.length>1?this.each((t==null?ze:typeof t==`function`?Ve:Be)(e,t)):this.node()[e]}function Ue(e){return e.trim().split(/^|\s+/)}function We(e){return e.classList||new Ge(e)}function Ge(e){this._node=e,this._names=Ue(e.getAttribute(`class`)||``)}Ge.prototype={add:function(e){this._names.indexOf(e)<0&&(this._names.push(e),this._node.setAttribute(`class`,this._names.join(` `)))},remove:function(e){var t=this._names.indexOf(e);t>=0&&(this._names.splice(t,1),this._node.setAttribute(`class`,this._names.join(` `)))},contains:function(e){return this._names.indexOf(e)>=0}};function Ke(e,t){for(var n=We(e),r=-1,i=t.length;++r=0&&(t=e.slice(n+1),e=e.slice(0,n)),{type:e,name:t}})}function xt(e){return function(){var t=this.__on;if(t){for(var n=0,r=-1,i=t.length,a;n()=>e;function Ut(e,{sourceEvent:t,subject:n,target:r,identifier:i,active:a,x:o,y:s,dx:c,dy:l,dispatch:u}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:t,enumerable:!0,configurable:!0},subject:{value:n,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:a,enumerable:!0,configurable:!0},x:{value:o,enumerable:!0,configurable:!0},y:{value:s,enumerable:!0,configurable:!0},dx:{value:c,enumerable:!0,configurable:!0},dy:{value:l,enumerable:!0,configurable:!0},_:{value:u}})}Ut.prototype.on=function(){var e=this._.on.apply(this._,arguments);return e===this._?this:e};function Wt(e){return!e.ctrlKey&&!e.button}function Gt(){return this.parentNode}function Kt(e,t){return t??{x:e.x,y:e.y}}function qt(){return navigator.maxTouchPoints||`ontouchstart`in this}function Jt(){var e=Wt,t=Gt,n=Kt,r=qt,i={},a=g(`start`,`drag`,`end`),o=0,s,c,l,u,d=0;function f(e){e.on(`mousedown.drag`,p).filter(r).on(`touchstart.drag`,_).on(`touchmove.drag`,v,It).on(`touchend.drag touchcancel.drag`,y).style(`touch-action`,`none`).style(`-webkit-tap-highlight-color`,`rgba(0,0,0,0)`)}function p(n,r){if(!(u||!e.call(this,n,r))){var i=b(this,t.call(this,n,r),n,r,`mouse`);i&&(Nt(n.view).on(`mousemove.drag`,m,Lt).on(`mouseup.drag`,h,Lt),Bt(n.view),Rt(n),l=!1,s=n.clientX,c=n.clientY,i(`start`,n))}}function m(e){if(zt(e),!l){var t=e.clientX-s,n=e.clientY-c;l=t*t+n*n>d}i.mouse(`drag`,e)}function h(e){Nt(e.view).on(`mousemove.drag mouseup.drag`,null),Vt(e.view,l),zt(e),i.mouse(`end`,e)}function _(n,r){if(e.call(this,n,r)){var i=n.changedTouches,a=t.call(this,n,r),o=i.length,s,c;for(s=0;s>8&15|t>>4&240,t>>4&15|t&240,(t&15)<<4|t&15,1):n===8?vn(t>>24&255,t>>16&255,t>>8&255,(t&255)/255):n===4?vn(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|t&240,((t&15)<<4|t&15)/255):null):(t=an.exec(e))?new xn(t[1],t[2],t[3],1):(t=on.exec(e))?new xn(t[1]*255/100,t[2]*255/100,t[3]*255/100,1):(t=sn.exec(e))?vn(t[1],t[2],t[3],t[4]):(t=cn.exec(e))?vn(t[1]*255/100,t[2]*255/100,t[3]*255/100,t[4]):(t=ln.exec(e))?On(t[1],t[2]/100,t[3]/100,1):(t=un.exec(e))?On(t[1],t[2]/100,t[3]/100,t[4]):dn.hasOwnProperty(e)?_n(dn[e]):e===`transparent`?new xn(NaN,NaN,NaN,0):null}function _n(e){return new xn(e>>16&255,e>>8&255,e&255,1)}function vn(e,t,n,r){return r<=0&&(e=t=n=NaN),new xn(e,t,n,r)}function yn(e){return e instanceof Zt||(e=gn(e)),e?(e=e.rgb(),new xn(e.r,e.g,e.b,e.opacity)):new xn}function bn(e,t,n,r){return arguments.length===1?yn(e):new xn(e,t,n,r??1)}function xn(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}Yt(xn,bn,Xt(Zt,{brighter(e){return e=e==null?$t:$t**+e,new xn(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?Qt:Qt**+e,new xn(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new xn(En(this.r),En(this.g),En(this.b),Tn(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:Sn,formatHex:Sn,formatHex8:Cn,formatRgb:wn,toString:wn}));function Sn(){return`#${Dn(this.r)}${Dn(this.g)}${Dn(this.b)}`}function Cn(){return`#${Dn(this.r)}${Dn(this.g)}${Dn(this.b)}${Dn((isNaN(this.opacity)?1:this.opacity)*255)}`}function wn(){let e=Tn(this.opacity);return`${e===1?`rgb(`:`rgba(`}${En(this.r)}, ${En(this.g)}, ${En(this.b)}${e===1?`)`:`, ${e})`}`}function Tn(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function En(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function Dn(e){return e=En(e),(e<16?`0`:``)+e.toString(16)}function On(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new jn(e,t,n,r)}function kn(e){if(e instanceof jn)return new jn(e.h,e.s,e.l,e.opacity);if(e instanceof Zt||(e=gn(e)),!e)return new jn;if(e instanceof jn)return e;e=e.rgb();var t=e.r/255,n=e.g/255,r=e.b/255,i=Math.min(t,n,r),a=Math.max(t,n,r),o=NaN,s=a-i,c=(a+i)/2;return s?(o=t===a?(n-r)/s+(n0&&c<1?0:o,new jn(o,s,c,e.opacity)}function An(e,t,n,r){return arguments.length===1?kn(e):new jn(e,t,n,r??1)}function jn(e,t,n,r){this.h=+e,this.s=+t,this.l=+n,this.opacity=+r}Yt(jn,An,Xt(Zt,{brighter(e){return e=e==null?$t:$t**+e,new jn(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=e==null?Qt:Qt**+e,new jn(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,t=isNaN(e)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*t,i=2*n-r;return new xn(Pn(e>=240?e-240:e+120,i,r),Pn(e,i,r),Pn(e<120?e+240:e-120,i,r),this.opacity)},clamp(){return new jn(Mn(this.h),Nn(this.s),Nn(this.l),Tn(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){let e=Tn(this.opacity);return`${e===1?`hsl(`:`hsla(`}${Mn(this.h)}, ${Nn(this.s)*100}%, ${Nn(this.l)*100}%${e===1?`)`:`, ${e})`}`}}));function Mn(e){return e=(e||0)%360,e<0?e+360:e}function Nn(e){return Math.max(0,Math.min(1,e||0))}function Pn(e,t,n){return(e<60?t+(n-t)*e/60:e<180?n:e<240?t+(n-t)*(240-e)/60:t)*255}var Fn=e=>()=>e;function In(e,t){return function(n){return e+n*t}}function Ln(e,t,n){return e**=+n,t=t**+n-e,n=1/n,function(r){return(e+r*t)**+n}}function Rn(e){return(e=+e)==1?zn:function(t,n){return n-t?Ln(t,n,e):Fn(isNaN(t)?n:t)}}function zn(e,t){var n=t-e;return n?In(e,n):Fn(isNaN(e)?t:e)}var Bn=(function e(t){var n=Rn(t);function r(e,t){var r=n((e=bn(e)).r,(t=bn(t)).r),i=n(e.g,t.g),a=n(e.b,t.b),o=zn(e.opacity,t.opacity);return function(t){return e.r=r(t),e.g=i(t),e.b=a(t),e.opacity=o(t),e+``}}return r.gamma=e,r})(1);function Vn(e,t){t||=[];var n=e?Math.min(t.length,e.length):0,r=t.slice(),i;return function(a){for(i=0;in&&(a=t.slice(n,a),s[o]?s[o]+=a:s[++o]=a),(r=r[0])===(i=i[0])?s[o]?s[o]+=i:s[++o]=i:(s[++o]=null,c.push({i:o,x:Gn(r,i)})),n=Jn.lastIndex;return n180?t+=360:t-e>180&&(e+=360),a.push({i:n.push(i(n)+`rotate(`,null,r)-2,x:Gn(e,t)}))}function s(e,t,n,a){e===t?t&&n.push(i(n)+`skewX(`+t+r):a.push({i:n.push(i(n)+`skewX(`,null,r)-2,x:Gn(e,t)})}function c(e,t,n,r,a,o){if(e!==n||t!==r){var s=a.push(i(a)+`scale(`,null,`,`,null,`)`);o.push({i:s-4,x:Gn(e,n)},{i:s-2,x:Gn(t,r)})}else(n!==1||r!==1)&&a.push(i(a)+`scale(`+n+`,`+r+`)`)}return function(t,n){var r=[],i=[];return t=e(t),n=e(n),a(t.translateX,t.translateY,n.translateX,n.translateY,r,i),o(t.rotate,n.rotate,r,i),s(t.skewX,n.skewX,r,i),c(t.scaleX,t.scaleY,n.scaleX,n.scaleY,r,i),t=n=null,function(e){for(var t=-1,n=i.length,a;++t=0&&e._call.call(void 0,t),e=e._next;--pr}function kr(){br=(yr=Sr.now())+xr,pr=mr=0;try{Or()}finally{pr=0,jr(),br=0}}function Ar(){var e=Sr.now(),t=e-yr;t>gr&&(xr-=t,yr=e)}function jr(){for(var e,t=_r,n,r=1/0;t;)t._call?(r>t._time&&(r=t._time),e=t,t=t._next):(n=t._next,t._next=null,t=e?e._next=n:_r=n);vr=e,Mr(r)}function Mr(e){pr||(mr&&=clearTimeout(mr),e-br>24?(e<1/0&&(mr=setTimeout(kr,e-Sr.now()-xr)),hr&&=clearInterval(hr)):(hr||=(yr=Sr.now(),setInterval(Ar,gr)),pr=1,Cr(kr)))}function Nr(e,t,n){var r=new Er;return t=t==null?0:+t,r.restart(n=>{r.stop(),e(n+t)},t,n),r}var Pr=g(`start`,`end`,`cancel`,`interrupt`),Fr=[];function Ir(e,t,n,r,i,a){var o=e.__transition;if(!o)e.__transition={};else if(n in o)return;Br(e,n,{name:t,index:r,group:i,on:Pr,tween:Fr,time:a.time,delay:a.delay,duration:a.duration,ease:a.ease,timer:null,state:0})}function Lr(e,t){var n=zr(e,t);if(n.state>0)throw Error(`too late; already scheduled`);return n}function Rr(e,t){var n=zr(e,t);if(n.state>3)throw Error(`too late; already running`);return n}function zr(e,t){var n=e.__transition;if(!n||!(n=n[t]))throw Error(`transition not found`);return n}function Br(e,t,n){var r=e.__transition,i;r[t]=n,n.timer=Dr(a,0,n.time);function a(e){n.state=1,n.timer.restart(o,n.delay,n.time),n.delay<=e&&o(e-n.delay)}function o(a){var l,u,d,f;if(n.state!==1)return c();for(l in r)if(f=r[l],f.name===n.name){if(f.state===3)return Nr(o);f.state===4?(f.state=6,f.timer.stop(),f.on.call(`interrupt`,e,e.__data__,f.index,f.group),delete r[l]):+l2&&r.state<5,r.state=6,r.timer.stop(),r.on.call(i?`interrupt`:`cancel`,e,e.__data__,r.index,r.group),delete n[o]}a&&delete e.__transition}}function Hr(e){return this.each(function(){Vr(this,e)})}function Ur(e,t){var n,r;return function(){var i=Rr(this,e),a=i.tween;if(a!==n){r=n=a;for(var o=0,s=r.length;o=0&&(e=e.slice(0,t)),!e||e===`start`})}function yi(e,t,n){var r,i,a=vi(t)?Lr:Rr;return function(){var o=a(this,e),s=o.on;s!==r&&(i=(r=s).copy()).on(t,n),o.on=i}}function bi(e,t){var n=this._id;return arguments.length<2?zr(this.node(),n).on.on(e):this.each(yi(n,e,t))}function xi(e){return function(){var t=this.parentNode;for(var n in this.__transition)if(+n!==e)return;t&&t.removeChild(this)}}function Si(){return this.on(`end.remove`,xi(this._id))}function Ci(e){var t=this._name,n=this._id;typeof e!=`function`&&(e=D(e));for(var r=this._groups,i=r.length,a=Array(i),o=0;o()=>e;function ea(e,{sourceEvent:t,target:n,transform:r,dispatch:i}){Object.defineProperties(this,{type:{value:e,enumerable:!0,configurable:!0},sourceEvent:{value:t,enumerable:!0,configurable:!0},target:{value:n,enumerable:!0,configurable:!0},transform:{value:r,enumerable:!0,configurable:!0},_:{value:i}})}function ta(e,t,n){this.k=e,this.x=t,this.y=n}ta.prototype={constructor:ta,scale:function(e){return e===1?this:new ta(this.k*e,this.x,this.y)},translate:function(e,t){return e===0&t===0?this:new ta(this.k,this.x+this.k*e,this.y+this.k*t)},apply:function(e){return[e[0]*this.k+this.x,e[1]*this.k+this.y]},applyX:function(e){return e*this.k+this.x},applyY:function(e){return e*this.k+this.y},invert:function(e){return[(e[0]-this.x)/this.k,(e[1]-this.y)/this.k]},invertX:function(e){return(e-this.x)/this.k},invertY:function(e){return(e-this.y)/this.k},rescaleX:function(e){return e.copy().domain(e.range().map(this.invertX,this).map(e.invert,e))},rescaleY:function(e){return e.copy().domain(e.range().map(this.invertY,this).map(e.invert,e))},toString:function(){return`translate(`+this.x+`,`+this.y+`) scale(`+this.k+`)`}};var na=new ta(1,0,0);ra.prototype=ta.prototype;function ra(e){for(;!e.__zoom;)if(!(e=e.parentNode))return na;return e.__zoom}function ia(e){e.stopImmediatePropagation()}function aa(e){e.preventDefault(),e.stopImmediatePropagation()}function oa(e){return(!e.ctrlKey||e.type===`wheel`)&&!e.button}function sa(){var e=this;return e instanceof SVGElement?(e=e.ownerSVGElement||e,e.hasAttribute(`viewBox`)?(e=e.viewBox.baseVal,[[e.x,e.y],[e.x+e.width,e.y+e.height]]):[[0,0],[e.width.baseVal.value,e.height.baseVal.value]]):[[0,0],[e.clientWidth,e.clientHeight]]}function ca(){return this.__zoom||na}function la(e){return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*(e.ctrlKey?10:1)}function ua(){return navigator.maxTouchPoints||`ontouchstart`in this}function da(e,t,n){var r=e.invertX(t[0][0])-n[0][0],i=e.invertX(t[1][0])-n[1][0],a=e.invertY(t[0][1])-n[0][1],o=e.invertY(t[1][1])-n[1][1];return e.translate(i>r?(r+i)/2:Math.min(0,r)||Math.max(0,i),o>a?(a+o)/2:Math.min(0,a)||Math.max(0,o))}function fa(){var e=oa,t=sa,n=da,r=la,i=ua,a=[0,1/0],o=[[-1/0,-1/0],[1/0,1/0]],s=250,c=fr,l=g(`start`,`zoom`,`end`),u,d,f,p=500,m=150,h=0,_=10;function v(e){e.property(`__zoom`,ca).on(`wheel.zoom`,T,{passive:!1}).on(`mousedown.zoom`,E).on(`dblclick.zoom`,D).filter(i).on(`touchstart.zoom`,O).on(`touchmove.zoom`,k).on(`touchend.zoom touchcancel.zoom`,A).style(`-webkit-tap-highlight-color`,`rgba(0,0,0,0)`)}v.transform=function(e,t,n,r){var i=e.selection?e.selection():e;i.property(`__zoom`,ca),e===i?i.interrupt().each(function(){C(this,arguments).event(r).start().zoom(null,typeof t==`function`?t.apply(this,arguments):t).end()}):S(e,t,n,r)},v.scaleBy=function(e,t,n,r){v.scaleTo(e,function(){return this.__zoom.k*(typeof t==`function`?t.apply(this,arguments):t)},n,r)},v.scaleTo=function(e,r,i,a){v.transform(e,function(){var e=t.apply(this,arguments),a=this.__zoom,s=i==null?x(e):typeof i==`function`?i.apply(this,arguments):i,c=a.invert(s),l=typeof r==`function`?r.apply(this,arguments):r;return n(b(y(a,l),s,c),e,o)},i,a)},v.translateBy=function(e,r,i,a){v.transform(e,function(){return n(this.__zoom.translate(typeof r==`function`?r.apply(this,arguments):r,typeof i==`function`?i.apply(this,arguments):i),t.apply(this,arguments),o)},null,a)},v.translateTo=function(e,r,i,a,s){v.transform(e,function(){var e=t.apply(this,arguments),s=this.__zoom,c=a==null?x(e):typeof a==`function`?a.apply(this,arguments):a;return n(na.translate(c[0],c[1]).scale(s.k).translate(typeof r==`function`?-r.apply(this,arguments):-r,typeof i==`function`?-i.apply(this,arguments):-i),e,o)},a,s)};function y(e,t){return t=Math.max(a[0],Math.min(a[1],t)),t===e.k?e:new ta(t,e.x,e.y)}function b(e,t,n){var r=t[0]-n[0]*e.k,i=t[1]-n[1]*e.k;return r===e.x&&i===e.y?e:new ta(e.k,r,i)}function x(e){return[(+e[0][0]+ +e[1][0])/2,(+e[0][1]+ +e[1][1])/2]}function S(e,n,r,i){e.on(`start.zoom`,function(){C(this,arguments).event(i).start()}).on(`interrupt.zoom end.zoom`,function(){C(this,arguments).event(i).end()}).tween(`zoom`,function(){var e=this,a=arguments,o=C(e,a).event(i),s=t.apply(e,a),l=r==null?x(s):typeof r==`function`?r.apply(e,a):r,u=Math.max(s[1][0]-s[0][0],s[1][1]-s[0][1]),d=e.__zoom,f=typeof n==`function`?n.apply(e,a):n,p=c(d.invert(l).concat(u/d.k),f.invert(l).concat(u/f.k));return function(e){if(e===1)e=f;else{var t=p(e),n=u/t[2];e=new ta(n,l[0]-t[0]*n,l[1]-t[1]*n)}o.zoom(null,e)}})}function C(e,t,n){return!n&&e.__zooming||new w(e,t)}function w(e,n){this.that=e,this.args=n,this.active=0,this.sourceEvent=null,this.extent=t.apply(e,n),this.taps=0}w.prototype={event:function(e){return e&&(this.sourceEvent=e),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit(`start`)),this},zoom:function(e,t){return this.mouse&&e!==`mouse`&&(this.mouse[1]=t.invert(this.mouse[0])),this.touch0&&e!==`touch`&&(this.touch0[1]=t.invert(this.touch0[0])),this.touch1&&e!==`touch`&&(this.touch1[1]=t.invert(this.touch1[0])),this.that.__zoom=t,this.emit(`zoom`),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit(`end`)),this},emit:function(e){var t=Nt(this.that).datum();l.call(e,this.that,new ea(e,{sourceEvent:this.sourceEvent,target:v,type:e,transform:this.that.__zoom,dispatch:l}),t)}};function T(t,...i){if(!e.apply(this,arguments))return;var s=C(this,i).event(t),c=this.__zoom,l=Math.max(a[0],Math.min(a[1],c.k*2**r.apply(this,arguments))),u=Ft(t);if(s.wheel)(s.mouse[0][0]!==u[0]||s.mouse[0][1]!==u[1])&&(s.mouse[1]=c.invert(s.mouse[0]=u)),clearTimeout(s.wheel);else if(c.k===l)return;else s.mouse=[u,c.invert(u)],Vr(this),s.start();aa(t),s.wheel=setTimeout(d,m),s.zoom(`mouse`,n(b(y(c,l),s.mouse[0],s.mouse[1]),s.extent,o));function d(){s.wheel=null,s.end()}}function E(t,...r){if(f||!e.apply(this,arguments))return;var i=t.currentTarget,a=C(this,r,!0).event(t),s=Nt(t.view).on(`mousemove.zoom`,d,!0).on(`mouseup.zoom`,p,!0),c=Ft(t,i),l=t.clientX,u=t.clientY;Bt(t.view),ia(t),a.mouse=[c,this.__zoom.invert(c)],Vr(this),a.start();function d(e){if(aa(e),!a.moved){var t=e.clientX-l,r=e.clientY-u;a.moved=t*t+r*r>h}a.event(e).zoom(`mouse`,n(b(a.that.__zoom,a.mouse[0]=Ft(e,i),a.mouse[1]),a.extent,o))}function p(e){s.on(`mousemove.zoom mouseup.zoom`,null),Vt(e.view,a.moved),aa(e),a.event(e).end()}}function D(r,...i){if(e.apply(this,arguments)){var a=this.__zoom,c=Ft(r.changedTouches?r.changedTouches[0]:r,this),l=a.invert(c),u=a.k*(r.shiftKey?.5:2),d=n(b(y(a,u),c,l),t.apply(this,i),o);aa(r),s>0?Nt(this).transition().duration(s).call(S,d,c,r):Nt(this).call(v.transform,d,c,r)}}function O(t,...n){if(e.apply(this,arguments)){var r=t.touches,i=r.length,a=C(this,n,t.changedTouches.length===i).event(t),o,s,c,l;for(ia(t),s=0;s`Seems like you have not used zustand provider as an ancestor. Help: https://${e}flow.dev/error#001`,error002:()=>`It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.`,error003:e=>`Node type "${e}" not found. Using fallback type "default".`,error004:()=>`The parent container needs a width and a height to render the graph.`,error005:()=>`Only child nodes can use a parent extent.`,error006:()=>`Can't create edge. An edge needs a source and a target.`,error007:e=>`The old edge with id=${e} does not exist.`,error009:e=>`Marker type "${e}" doesn't exist.`,error008:(e,{id:t,sourceHandle:n,targetHandle:r})=>`Couldn't create edge for ${e} handle id: "${e===`source`?n:r}", edge id: ${t}.`,error010:()=>`Handle: No node id found. Make sure to only use a Handle inside a custom Node.`,error011:e=>`Edge type "${e}" not found. Using fallback type "default".`,error012:e=>`Node with id "${e}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`,error013:(e=`react`)=>`It seems that you haven't loaded the styles. Please import '@xyflow/${e}/dist/style.css' or base.css to make sure everything is working properly.`,error014:()=>`useNodeConnections: No node ID found. Call useNodeConnections inside a custom Node or provide a node ID.`,error015:()=>`It seems that you are trying to drag a node that is not initialized. Please use onNodesChange as explained in the docs.`,error016:e=>`Edge with id "${e}" does not exist, it may have been removed. This can happen when an edge is deleted before the "onEdgeClick" handler is called.`},ma=[[-1/0,-1/0],[1/0,1/0]],ha=[`Enter`,` `,`Escape`],ga={"node.a11yDescription.default":`Press enter or space to select a node. Press delete to remove it and escape to cancel.`,"node.a11yDescription.keyboardDisabled":`Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.`,"node.a11yDescription.ariaLiveMessage":({direction:e,x:t,y:n})=>`Moved selected node ${e}. New position, x: ${t}, y: ${n}`,"edge.a11yDescription.default":`Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.`,"controls.ariaLabel":`Control Panel`,"controls.zoomIn.ariaLabel":`Zoom In`,"controls.zoomOut.ariaLabel":`Zoom Out`,"controls.fitView.ariaLabel":`Fit View`,"controls.interactive.ariaLabel":`Toggle Interactivity`,"minimap.ariaLabel":`Mini Map`,"handle.ariaLabel":`Handle`},_a;(function(e){e.Strict=`strict`,e.Loose=`loose`})(_a||={});var va;(function(e){e.Free=`free`,e.Vertical=`vertical`,e.Horizontal=`horizontal`})(va||={});var ya;(function(e){e.Partial=`partial`,e.Full=`full`})(ya||={});var ba={inProgress:!1,isValid:null,from:null,fromHandle:null,fromPosition:null,fromNode:null,to:null,toHandle:null,toPosition:null,toNode:null,pointer:null},xa;(function(e){e.Bezier=`default`,e.Straight=`straight`,e.Step=`step`,e.SmoothStep=`smoothstep`,e.SimpleBezier=`simplebezier`})(xa||={});var Sa;(function(e){e.Arrow=`arrow`,e.ArrowClosed=`arrowclosed`})(Sa||={});var B;(function(e){e.Left=`left`,e.Top=`top`,e.Right=`right`,e.Bottom=`bottom`})(B||={});var Ca={[B.Left]:B.Right,[B.Right]:B.Left,[B.Top]:B.Bottom,[B.Bottom]:B.Top};function wa(e){return e===null?null:e?`valid`:`invalid`}var Ta=e=>`id`in e&&`source`in e&&`target`in e,Ea=e=>`id`in e&&`position`in e&&!(`source`in e)&&!(`target`in e),Da=e=>`id`in e&&`internals`in e&&!(`source`in e)&&!(`target`in e),Oa=(e,t,n)=>{if(!e.id)return[];let r=new Set;return n.forEach(t=>{t.source===e.id&&r.add(t.target)}),t.filter(e=>r.has(e.id))},ka=(e,t,n)=>{if(!e.id)return[];let r=new Set;return n.forEach(t=>{t.target===e.id&&r.add(t.source)}),t.filter(e=>r.has(e.id))},Aa=(e,t=[0,0])=>{let{width:n,height:r}=lo(e),i=e.origin??t,a=n*i[0],o=r*i[1];return{x:e.position.x-a,y:e.position.y-o}},ja=(e,t={nodeOrigin:[0,0]})=>e.length===0?{x:0,y:0,width:0,height:0}:Ka(e.reduce((e,n)=>{let r=typeof n==`string`,i=!t.nodeLookup&&!r?n:void 0;return t.nodeLookup&&(i=r?t.nodeLookup.get(n):Da(n)?n:t.nodeLookup.get(n.id)),Wa(e,i?Ja(i,t.nodeOrigin):{x:0,y:0,x2:0,y2:0})},{x:1/0,y:1/0,x2:-1/0,y2:-1/0})),Ma=(e,t={})=>{let n={x:1/0,y:1/0,x2:-1/0,y2:-1/0},r=!1;return e.forEach(e=>{(t.filter===void 0||t.filter(e))&&(n=Wa(n,Ja(e)),r=!0)}),r?Ka(n):{x:0,y:0,width:0,height:0}},Na=(e,t,[n,r,i]=[0,0,1],a=!1,o=!1)=>{let s={...to(t,[n,r,i]),width:t.width/i,height:t.height/i},c=[];for(let t of e.values()){let{measured:e,selectable:n=!0,hidden:r=!1}=t;if(o&&!n||r)continue;let i=e.width??t.width??t.initialWidth??null,l=e.height??t.height??t.initialHeight??null,u=Xa(s,qa(t)),d=(i??0)*(l??0),f=a&&u>0;(!t.internals.handleBounds||f||u>=d||t.dragging)&&c.push(t)}return c},Pa=(e,t)=>{let n=new Set;return e.forEach(e=>{n.add(e.id)}),t.filter(e=>n.has(e.source)||n.has(e.target))};function Fa(e,t){let n=new Map,r=t?.nodes?new Set(t.nodes.map(e=>e.id)):null;return e.forEach(e=>{e.measured.width&&e.measured.height&&(t?.includeHiddenNodes||!e.hidden)&&(!r||r.has(e.id))&&n.set(e.id,e)}),n}async function Ia({nodes:e,width:t,height:n,panZoom:r,minZoom:i,maxZoom:a},o){if(e.size===0)return!0;let s=oo(Ma(Fa(e,o)),t,n,o?.minZoom??i,o?.maxZoom??a,o?.padding??.1);return await r.setViewport(s,{duration:o?.duration,ease:o?.ease,interpolate:o?.interpolate}),!0}function La({nodeId:e,nextPosition:t,nodeLookup:n,nodeOrigin:r=[0,0],nodeExtent:i,onError:a}){let o=n.get(e),s=o.parentId?n.get(o.parentId):void 0,{x:c,y:l}=s?s.internals.positionAbsolute:{x:0,y:0},u=o.origin??r,d=o.extent||i;if(o.extent===`parent`&&!o.expandParent)if(!s)a?.(`005`,pa.error005());else{let e=s.measured.width,t=s.measured.height;e&&t&&(d=[[c,l],[c+e,l+t]])}else s&&co(o.extent)&&(d=[[o.extent[0][0]+c,o.extent[0][1]+l],[o.extent[1][0]+c,o.extent[1][1]+l]]);let f=co(d)?Ba(t,d,o.measured):t;return(o.measured.width===void 0||o.measured.height===void 0)&&a?.(`015`,pa.error015()),{position:{x:f.x-c+(o.measured.width??0)*u[0],y:f.y-l+(o.measured.height??0)*u[1]},positionAbsolute:f}}async function Ra({nodesToRemove:e=[],edgesToRemove:t=[],nodes:n,edges:r,onBeforeDelete:i}){let a=new Set(e.map(e=>e.id)),o=[];for(let e of n){if(e.deletable===!1)continue;let t=a.has(e.id),n=!t&&e.parentId&&o.find(t=>t.id===e.parentId);(t||n)&&o.push(e)}let s=new Set(t.map(e=>e.id)),c=r.filter(e=>e.deletable!==!1),l=Pa(o,c);for(let e of c)s.has(e.id)&&!l.find(t=>t.id===e.id)&&l.push(e);if(!i)return{edges:l,nodes:o};let u=await i({nodes:o,edges:l});return typeof u==`boolean`?u?{edges:l,nodes:o}:{edges:[],nodes:[]}:u}var za=(e,t=0,n=1)=>Math.min(Math.max(e,t),n),Ba=(e={x:0,y:0},t,n)=>({x:za(e.x,t[0][0],t[1][0]-(n?.width??0)),y:za(e.y,t[0][1],t[1][1]-(n?.height??0))});function Va(e,t,n){let{width:r,height:i}=lo(n),{x:a,y:o}=n.internals.positionAbsolute;return Ba(e,[[a,o],[a+r,o+i]],t)}var Ha=(e,t,n)=>en?-za(Math.abs(e-n),1,t)/t:0,Ua=(e,t,n=15,r=40)=>[Ha(e.x,r,t.width-r)*n,Ha(e.y,r,t.height-r)*n],Wa=(e,t)=>({x:Math.min(e.x,t.x),y:Math.min(e.y,t.y),x2:Math.max(e.x2,t.x2),y2:Math.max(e.y2,t.y2)}),Ga=({x:e,y:t,width:n,height:r})=>({x:e,y:t,x2:e+n,y2:t+r}),Ka=({x:e,y:t,x2:n,y2:r})=>({x:e,y:t,width:n-e,height:r-t}),qa=(e,t=[0,0])=>{let{x:n,y:r}=Da(e)?e.internals.positionAbsolute:Aa(e,t);return{x:n,y:r,width:e.measured?.width??e.width??e.initialWidth??0,height:e.measured?.height??e.height??e.initialHeight??0}},Ja=(e,t=[0,0])=>{let{x:n,y:r}=Da(e)?e.internals.positionAbsolute:Aa(e,t);return{x:n,y:r,x2:n+(e.measured?.width??e.width??e.initialWidth??0),y2:r+(e.measured?.height??e.height??e.initialHeight??0)}},Ya=(e,t)=>Ka(Wa(Ga(e),Ga(t))),Xa=(e,t)=>{let n=Math.max(0,Math.min(e.x+e.width,t.x+t.width)-Math.max(e.x,t.x)),r=Math.max(0,Math.min(e.y+e.height,t.y+t.height)-Math.max(e.y,t.y));return Math.ceil(n*r)},Za=e=>Qa(e.width)&&Qa(e.height)&&Qa(e.x)&&Qa(e.y),Qa=e=>!isNaN(e)&&isFinite(e),$a=(e,t)=>(e,t)=>{},eo=(e,t=[1,1])=>({x:t[0]*Math.round(e.x/t[0]),y:t[1]*Math.round(e.y/t[1])}),to=({x:e,y:t},[n,r,i],a=!1,o=[1,1])=>{let s={x:(e-n)/i,y:(t-r)/i};return a?eo(s,o):s},no=({x:e,y:t},[n,r,i])=>({x:e*i+n,y:t*i+r});function ro(e,t){if(typeof e==`number`)return Math.floor((t-t/(1+e))*.5);if(typeof e==`string`&&e.endsWith(`px`)){let t=parseFloat(e);if(!Number.isNaN(t))return Math.floor(t)}if(typeof e==`string`&&e.endsWith(`%`)){let n=parseFloat(e);if(!Number.isNaN(n))return Math.floor(t*n*.01)}return console.error(`The padding value "${e}" is invalid. Please provide a number or a string with a valid unit (px or %).`),0}function io(e,t,n){if(typeof e==`string`||typeof e==`number`){let r=ro(e,n),i=ro(e,t);return{top:r,right:i,bottom:r,left:i,x:i*2,y:r*2}}if(typeof e==`object`){let r=ro(e.top??e.y??0,n),i=ro(e.bottom??e.y??0,n),a=ro(e.left??e.x??0,t),o=ro(e.right??e.x??0,t);return{top:r,right:o,bottom:i,left:a,x:a+o,y:r+i}}return{top:0,right:0,bottom:0,left:0,x:0,y:0}}function ao(e,t,n,r,i,a){let{x:o,y:s}=no(e,[t,n,r]),{x:c,y:l}=no({x:e.x+e.width,y:e.y+e.height},[t,n,r]),u=i-c,d=a-l;return{left:Math.floor(o),top:Math.floor(s),right:Math.floor(u),bottom:Math.floor(d)}}var oo=(e,t,n,r,i,a)=>{let o=io(a,t,n),s=(t-o.x)/e.width,c=(n-o.y)/e.height,l=za(Math.min(s,c),r,i),u=e.x+e.width/2,d=e.y+e.height/2,f=t/2-u*l,p=n/2-d*l,m=ao(e,f,p,l,t,n),h={left:Math.min(m.left-o.left,0),top:Math.min(m.top-o.top,0),right:Math.min(m.right-o.right,0),bottom:Math.min(m.bottom-o.bottom,0)};return{x:f-h.left+h.right,y:p-h.top+h.bottom,zoom:l}},so=()=>typeof navigator<`u`&&navigator?.userAgent?.indexOf(`Mac`)>=0;function co(e){return e!=null&&e!==`parent`}function lo(e){return{width:e.measured?.width??e.width??e.initialWidth??0,height:e.measured?.height??e.height??e.initialHeight??0}}function uo(e){return(e.measured?.width??e.width??e.initialWidth)!==void 0&&(e.measured?.height??e.height??e.initialHeight)!==void 0}function fo(e,t={width:0,height:0},n,r,i){let a={...e},o=r.get(n);if(o){let e=o.origin||i;a.x+=o.internals.positionAbsolute.x-(t.width??0)*e[0],a.y+=o.internals.positionAbsolute.y-(t.height??0)*e[1]}return a}function V(e,t){if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}function po(){let e,t;return{promise:new Promise((n,r)=>{e=n,t=r}),resolve:e,reject:t}}function mo(e){return{...ga,...e||{}}}function ho(e,{snapGrid:t=[0,0],snapToGrid:n=!1,transform:r,containerBounds:i}){let{x:a,y:o}=xo(e),s=to({x:a-(i?.left??0),y:o-(i?.top??0)},r),{x:c,y:l}=n?eo(s,t):s;return{xSnapped:c,ySnapped:l,...s}}var go=e=>({width:e.offsetWidth,height:e.offsetHeight}),_o=e=>e?.getRootNode?.()||window?.document,vo=[`INPUT`,`SELECT`,`TEXTAREA`];function yo(e){let t=e.composedPath?.()?.[0]||e.target;return t?.nodeType===1?vo.includes(t.nodeName)||t.hasAttribute(`contenteditable`)||!!t.closest(`.nokey`):!1}var bo=e=>`clientX`in e,xo=(e,t)=>{let n=bo(e),r=n?e.clientX:e.touches?.[0].clientX,i=n?e.clientY:e.touches?.[0].clientY;return{x:r-(t?.left??0),y:i-(t?.top??0)}},So=(e,t,n,r,i)=>{let a=t.querySelectorAll(`.${e}`);return!a||!a.length?null:Array.from(a).map(t=>{let a=t.getBoundingClientRect();return{id:t.getAttribute(`data-handleid`),type:e,nodeId:i,position:t.getAttribute(`data-handlepos`),x:(a.left-n.left)/r,y:(a.top-n.top)/r,...go(t)}})};function Co({sourceX:e,sourceY:t,targetX:n,targetY:r,sourceControlX:i,sourceControlY:a,targetControlX:o,targetControlY:s}){let c=e*.125+i*.375+o*.375+n*.125,l=t*.125+a*.375+s*.375+r*.125;return[c,l,Math.abs(c-e),Math.abs(l-t)]}function wo(e,t){return e>=0?.5*e:t*25*Math.sqrt(-e)}function To({pos:e,x1:t,y1:n,x2:r,y2:i,c:a}){switch(e){case B.Left:return[t-wo(t-r,a),n];case B.Right:return[t+wo(r-t,a),n];case B.Top:return[t,n-wo(n-i,a)];case B.Bottom:return[t,n+wo(i-n,a)]}}function Eo({sourceX:e,sourceY:t,sourcePosition:n=B.Bottom,targetX:r,targetY:i,targetPosition:a=B.Top,curvature:o=.25}){let[s,c]=To({pos:n,x1:e,y1:t,x2:r,y2:i,c:o}),[l,u]=To({pos:a,x1:r,y1:i,x2:e,y2:t,c:o}),[d,f,p,m]=Co({sourceX:e,sourceY:t,targetX:r,targetY:i,sourceControlX:s,sourceControlY:c,targetControlX:l,targetControlY:u});return[`M${e},${t} C${s},${c} ${l},${u} ${r},${i}`,d,f,p,m]}function Do({sourceX:e,sourceY:t,targetX:n,targetY:r}){let i=Math.abs(n-e)/2,a=n0}var Ao=({source:e,sourceHandle:t,target:n,targetHandle:r})=>`xy-edge__${e}${t||``}-${n}${r||``}`,jo=(e,t)=>t.some(t=>t.source===e.source&&t.target===e.target&&(t.sourceHandle===e.sourceHandle||!t.sourceHandle&&!e.sourceHandle)&&(t.targetHandle===e.targetHandle||!t.targetHandle&&!e.targetHandle)),Mo=(e,t,n={})=>{if(!e.source||!e.target)return n.onError?.(`006`,pa.error006()),t;let r=n.getEdgeId||Ao,i;return i=Ta(e)?{...e}:{...e,id:r(e)},jo(i,t)?t:(i.sourceHandle===null&&delete i.sourceHandle,i.targetHandle===null&&delete i.targetHandle,t.concat(i))};function No({sourceX:e,sourceY:t,targetX:n,targetY:r}){let[i,a,o,s]=Do({sourceX:e,sourceY:t,targetX:n,targetY:r});return[`M ${e},${t}L ${n},${r}`,i,a,o,s]}var Po={[B.Left]:{x:-1,y:0},[B.Right]:{x:1,y:0},[B.Top]:{x:0,y:-1},[B.Bottom]:{x:0,y:1}},Fo=({source:e,sourcePosition:t=B.Bottom,target:n})=>t===B.Left||t===B.Right?e.xMath.sqrt((t.x-e.x)**2+(t.y-e.y)**2);function Lo({source:e,sourcePosition:t=B.Bottom,target:n,targetPosition:r=B.Top,center:i,offset:a,stepPosition:o}){let s=Po[t],c=Po[r],l={x:e.x+s.x*a,y:e.y+s.y*a},u={x:n.x+c.x*a,y:n.y+c.y*a},d=Fo({source:l,sourcePosition:t,target:u}),f=d.x===0?`y`:`x`,p=d[f],m=[],h,g,_={x:0,y:0},v={x:0,y:0},[,,y,b]=Do({sourceX:e.x,sourceY:e.y,targetX:n.x,targetY:n.y});if(s[f]*c[f]===-1){f===`x`?(h=i.x??l.x+(u.x-l.x)*o,g=i.y??(l.y+u.y)/2):(h=i.x??(l.x+u.x)/2,g=i.y??l.y+(u.y-l.y)*o);let e=[{x:h,y:l.y},{x:h,y:u.y}],t=[{x:l.x,y:g},{x:u.x,y:g}];m=s[f]===p?f===`x`?e:t:f===`x`?t:e}else{let i=[{x:l.x,y:u.y}],o=[{x:u.x,y:l.y}];if(m=f===`x`?s.x===p?o:i:s.y===p?i:o,t===r){let t=Math.abs(e[f]-n[f]);if(t<=a){let r=Math.min(a-1,a-t);s[f]===p?_[f]=(l[f]>e[f]?-1:1)*r:v[f]=(u[f]>n[f]?-1:1)*r}}if(t!==r){let e=f===`x`?`y`:`x`,t=s[f]===c[e],n=l[e]>u[e],r=l[e]=Math.max(Math.abs(d.y-m[0].y),Math.abs(y.y-m[0].y))?(h=(d.x+y.x)/2,g=m[0].y):(h=m[0].x,g=(d.y+y.y)/2)}let x={x:l.x+_.x,y:l.y+_.y},S={x:u.x+v.x,y:u.y+v.y};return[[e,...x.x!==m[0].x||x.y!==m[0].y?[x]:[],...m,...S.x!==m[m.length-1].x||S.y!==m[m.length-1].y?[S]:[],n],h,g,y,b]}function Ro(e,t,n,r){let i=Math.min(Io(e,t)/2,Io(t,n)/2,r),{x:a,y:o}=t;if(e.x===a&&a===n.x||e.y===o&&o===n.y)return`L${a} ${o}`;if(e.y===o){let t=e.xe.id===t):e[0])||null}function Go(e,t){return e?typeof e==`string`?e:`${t?`${t}__`:``}${Object.keys(e).sort().map(t=>`${t}=${e[t]}`).join(`&`)}`:``}function Ko(e,{id:t,defaultColor:n,defaultMarkerStart:r,defaultMarkerEnd:i}){let a=new Set;return e.reduce((e,o)=>([o.markerStart||r,o.markerEnd||i].forEach(r=>{if(r&&typeof r==`object`){let i=Go(r,t);a.has(i)||(e.push({id:i,color:r.color||n,...r}),a.add(i))}}),e),[]).sort((e,t)=>e.id.localeCompare(t.id))}var qo=1e3,Jo=10,Yo={nodeOrigin:[0,0],nodeExtent:ma,elevateNodesOnSelect:!0,zIndexMode:`basic`,defaults:{}},Xo={...Yo,checkEquality:!0};function Zo(e,t){let n={...e};for(let e in t)t[e]!==void 0&&(n[e]=t[e]);return n}function Qo(e,t,n){let r=Zo(Yo,n);for(let n of e.values())if(n.parentId)rs(n,e,t,r);else{let e=Ba(Aa(n,r.nodeOrigin),co(n.extent)?n.extent:r.nodeExtent,lo(n));n.internals.positionAbsolute=e}}function $o(e,t){if(!e.handles)return e.measured?t?.internals.handleBounds:void 0;let n=[],r=[];for(let t of e.handles){let i={id:t.id,width:t.width??1,height:t.height??1,nodeId:e.id,x:t.x,y:t.y,position:t.position,type:t.type};t.type===`source`?n.push(i):t.type===`target`&&r.push(i)}return{source:n,target:r}}function es(e){return e===`manual`}function ts(e,t,n,r={}){let i=Zo(Xo,r),a={i:0},o=new Map(t),s=i?.elevateNodesOnSelect&&!es(i.zIndexMode)?qo:0,c=e.length>0,l=!1;t.clear(),n.clear();for(let u of e){let e=o.get(u.id);if(i.checkEquality&&u===e?.internals.userNode)t.set(u.id,e);else{let n=Ba(Aa(u,i.nodeOrigin),co(u.extent)?u.extent:i.nodeExtent,lo(u));e={...i.defaults,...u,measured:{width:u.measured?.width,height:u.measured?.height},internals:{positionAbsolute:n,handleBounds:$o(u,e),z:is(u,s,i.zIndexMode),userNode:u}},t.set(u.id,e)}(e.measured===void 0||e.measured.width===void 0||e.measured.height===void 0)&&!e.hidden&&(c=!1),u.parentId&&rs(e,t,n,r,a),l||=u.selected??!1}return{nodesInitialized:c,hasSelectedNodes:l}}function ns(e,t){if(!e.parentId)return;let n=t.get(e.parentId);n?n.set(e.id,e):t.set(e.parentId,new Map([[e.id,e]]))}function rs(e,t,n,r,i){let{elevateNodesOnSelect:a,nodeOrigin:o,nodeExtent:s,zIndexMode:c}=Zo(Yo,r),l=e.parentId,u=t.get(l);if(!u){console.warn(`Parent node ${l} not found. Please make sure that parent nodes are in front of their child nodes in the nodes array.`);return}ns(e,n),i&&!u.parentId&&u.internals.rootParentIndex===void 0&&c===`auto`&&(u.internals.rootParentIndex=++i.i,u.internals.z=u.internals.z+i.i*Jo),i&&u.internals.rootParentIndex!==void 0&&(i.i=u.internals.rootParentIndex);let{x:d,y:f,z:p}=as(e,u,o,s,a&&!es(c)?qo:0,c),{positionAbsolute:m}=e.internals,h=d!==m.x||f!==m.y;(h||p!==e.internals.z)&&t.set(e.id,{...e,internals:{...e.internals,positionAbsolute:h?{x:d,y:f}:m,z:p}})}function is(e,t,n){let r=Qa(e.zIndex)?e.zIndex:0;return es(n)?r:r+(e.selected?t:0)}function as(e,t,n,r,i,a){let{x:o,y:s}=t.internals.positionAbsolute,c=lo(e),l=Aa(e,n),u=co(e.extent)?Ba(l,e.extent,c):l,d=Ba({x:o+u.x,y:s+u.y},r,c);e.extent===`parent`&&(d=Va(d,c,t));let f=is(e,i,a),p=t.internals.z??0;return{x:d.x,y:d.y,z:p>=f?p+1:f}}function os(e,t,n,r=[0,0]){let i=[],a=new Map;for(let n of e){let e=t.get(n.parentId);if(!e)continue;let r=Ya(a.get(n.parentId)?.expandedRect??qa(e),n.rect);a.set(n.parentId,{expandedRect:r,parent:e})}return a.size>0&&a.forEach(({expandedRect:t,parent:a},o)=>{let s=a.internals.positionAbsolute,c=lo(a),l=a.origin??r,u=t.x0||d>0||m||h)&&(i.push({id:o,type:`position`,position:{x:a.position.x-u+m,y:a.position.y-d+h}}),n.get(o)?.forEach(t=>{e.some(e=>e.id===t.id)||i.push({id:t.id,type:`position`,position:{x:t.position.x+u,y:t.position.y+d}})})),(c.width0){let e=os(f,t,n,i);l.push(...e)}return{changes:l,updatedInternals:c}}async function cs({delta:e,panZoom:t,transform:n,translateExtent:r,width:i,height:a}){if(!t||!e.x&&!e.y)return!1;let o=await t.setViewportConstrained({x:n[0]+e.x,y:n[1]+e.y,zoom:n[2]},[[0,0],[i,a]],r);return!!o&&(o.x!==n[0]||o.y!==n[1]||o.k!==n[2])}function ls(e,t,n,r,i,a){let o=i,s=r.get(o)||new Map;r.set(o,s.set(n,t)),o=`${i}-${e}`;let c=r.get(o)||new Map;if(r.set(o,c.set(n,t)),a){o=`${i}-${e}-${a}`;let s=r.get(o)||new Map;r.set(o,s.set(n,t))}}function us(e,t,n){e.clear(),t.clear();for(let r of n){let{source:n,target:i,sourceHandle:a=null,targetHandle:o=null}=r,s={edgeId:r.id,source:n,target:i,sourceHandle:a,targetHandle:o},c=`${n}-${a}--${i}-${o}`;ls(`source`,s,`${i}-${o}--${n}-${a}`,e,n,a),ls(`target`,s,c,e,i,o),t.set(r.id,r)}}function ds(e,t){if(!e.parentId)return!1;let n=t.get(e.parentId);return n?n.selected?!0:ds(n,t):!1}function fs(e,t,n){let r=e;do{if(r?.matches?.(t))return!0;if(r===n)return!1;r=r?.parentElement}while(r);return!1}function ps(e,t,n,r){let i=new Map;for(let[a,o]of e)if((o.selected||o.id===r)&&(!o.parentId||!ds(o,e))&&(o.draggable||t&&o.draggable===void 0)){let t=e.get(a);t&&i.set(a,{id:a,position:t.position||{x:0,y:0},distance:{x:n.x-t.internals.positionAbsolute.x,y:n.y-t.internals.positionAbsolute.y},extent:t.extent,parentId:t.parentId,origin:t.origin,expandParent:t.expandParent,internals:{positionAbsolute:t.internals.positionAbsolute||{x:0,y:0}},measured:{width:t.measured.width??0,height:t.measured.height??0}})}return i}function ms({nodeId:e,dragItems:t,nodeLookup:n,dragging:r=!0}){let i=[];for(let[e,a]of t){let t=n.get(e)?.internals.userNode;t&&i.push({...t,position:a.position,dragging:r})}if(!e)return[i[0],i];let a=n.get(e)?.internals.userNode;return[a?{...a,position:t.get(e)?.position||a.position,dragging:r}:i[0],i]}function hs({dragItems:e,snapGrid:t,x:n,y:r}){let i=e.values().next().value;if(!i)return null;let a={x:n-i.distance.x,y:r-i.distance.y},o=eo(a,t);return{x:o.x-a.x,y:o.y-a.y}}function gs({onNodeMouseDown:e,getStoreItems:t,onDragStart:n,onDrag:r,onDragStop:i}){let a={x:null,y:null},o=0,s=new Map,c=!1,l={x:0,y:0},u=null,d=!1,f=null,p=!1,m=!1,h=null;function g({noDragClassName:g,handleSelector:_,domNode:v,isSelectable:y,nodeId:b,nodeClickDistance:x=0}){f=Nt(v);function S({x:e,y:n}){let{nodeLookup:i,nodeExtent:o,snapGrid:c,snapToGrid:l,nodeOrigin:u,onNodeDrag:d,onSelectionDrag:f,onError:p,updateNodePositions:g}=t();a={x:e,y:n};let _=!1,v=s.size>1,y=v&&o?Ga(Ma(s)):null,x=v&&l?hs({dragItems:s,snapGrid:c,x:e,y:n}):null;for(let[t,r]of s){if(!i.has(t))continue;let a={x:e-r.distance.x,y:n-r.distance.y};l&&(a=x?{x:Math.round(a.x+x.x),y:Math.round(a.y+x.y)}:eo(a,c));let s=null;if(v&&o&&!r.extent&&y){let{positionAbsolute:e}=r.internals,t=e.x-y.x+o[0][0],n=e.x+r.measured.width-y.x2+o[1][0],i=e.y-y.y+o[0][1],a=e.y+r.measured.height-y.y2+o[1][1];s=[[t,i],[n,a]]}let{position:d,positionAbsolute:f}=La({nodeId:t,nextPosition:a,nodeLookup:i,nodeExtent:s||o,nodeOrigin:u,onError:p});_=_||r.position.x!==d.x||r.position.y!==d.y,r.position=d,r.internals.positionAbsolute=f}if(m||=_,_&&(g(s,!0),h&&(r||d||!b&&f))){let[e,t]=ms({nodeId:b,dragItems:s,nodeLookup:i});r?.(h,s,e,t),d?.(h,e,t),b||f?.(h,t)}}async function C(){if(!u)return;let{transform:e,panBy:n,autoPanSpeed:r,autoPanOnNodeDrag:i}=t();if(!i){c=!1,cancelAnimationFrame(o);return}let[s,d]=Ua(l,u,r);(s!==0||d!==0)&&(a.x=(a.x??0)-s/e[2],a.y=(a.y??0)-d/e[2],await n({x:s,y:d})&&S(a)),o=requestAnimationFrame(C)}function w(r){let{nodeLookup:i,multiSelectionActive:o,nodesDraggable:c,transform:l,snapGrid:f,snapToGrid:p,selectNodesOnDrag:m,onNodeDragStart:h,onSelectionDragStart:g,unselectNodesAndEdges:_}=t();d=!0,(!m||!y)&&!o&&b&&(i.get(b)?.selected||_()),y&&m&&b&&e?.(b);let v=ho(r.sourceEvent,{transform:l,snapGrid:f,snapToGrid:p,containerBounds:u});if(a=v,s=ps(i,c,v,b),s.size>0&&(n||h||!b&&g)){let[e,t]=ms({nodeId:b,dragItems:s,nodeLookup:i});n?.(r.sourceEvent,s,e,t),h?.(r.sourceEvent,e,t),b||g?.(r.sourceEvent,t)}}let T=Jt().clickDistance(x).on(`start`,e=>{let{domNode:n,nodeDragThreshold:r,transform:i,snapGrid:o,snapToGrid:s}=t();u=n?.getBoundingClientRect()||null,p=!1,m=!1,h=e.sourceEvent,r===0&&w(e),a=ho(e.sourceEvent,{transform:i,snapGrid:o,snapToGrid:s,containerBounds:u}),l=xo(e.sourceEvent,u)}).on(`drag`,e=>{let{autoPanOnNodeDrag:n,transform:r,snapGrid:i,snapToGrid:o,nodeDragThreshold:f,nodeLookup:m}=t(),g=ho(e.sourceEvent,{transform:r,snapGrid:i,snapToGrid:o,containerBounds:u});if(h=e.sourceEvent,(e.sourceEvent.type===`touchmove`&&e.sourceEvent.touches.length>1||b&&!m.has(b))&&(p=!0),!p){if(!c&&n&&d&&(c=!0,C()),!d){let t=xo(e.sourceEvent,u),n=t.x-l.x,r=t.y-l.y;Math.sqrt(n*n+r*r)>f&&w(e)}(a.x!==g.xSnapped||a.y!==g.ySnapped)&&s&&d&&(l=xo(e.sourceEvent,u),S(g))}}).on(`end`,e=>{if(!d||p){p&&s.size>0&&t().updateNodePositions(s,!1);return}if(c=!1,d=!1,cancelAnimationFrame(o),s.size>0){let{nodeLookup:n,updateNodePositions:r,onNodeDragStop:a,onSelectionDragStop:o}=t();if(m&&=(r(s,!1),!1),i||a||!b&&o){let[t,r]=ms({nodeId:b,dragItems:s,nodeLookup:n,dragging:!1});i?.(e.sourceEvent,s,t,r),a?.(e.sourceEvent,t,r),b||o?.(e.sourceEvent,r)}}}).filter(e=>{let t=e.target;return!e.button&&(!g||!fs(t,`.${g}`,v))&&(!_||fs(t,_,v))});f.call(T)}function _(){f?.on(`.drag`,null)}return{update:g,destroy:_}}function _s(e,t,n){let r=[],i={x:e.x-n,y:e.y-n,width:n*2,height:n*2};for(let e of t.values())Xa(i,qa(e))>0&&r.push(e);return r}var vs=250;function ys(e,t,n,r){let i=[],a=1/0,o=_s(e,n,t+vs);for(let n of o){let o=[...n.internals.handleBounds?.source??[],...n.internals.handleBounds?.target??[]];for(let s of o){if(r.nodeId===s.nodeId&&r.type===s.type&&r.id===s.id)continue;let{x:o,y:c}=Uo(n,s,s.position,!0),l=Math.sqrt((o-e.x)**2+(c-e.y)**2);l>t||(l1){let e=r.type===`source`?`target`:`source`;return i.find(t=>t.type===e)??i[0]}return i[0]}function bs(e,t,n,r,i,a=!1){let o=r.get(e);if(!o)return null;let s=i===`strict`?o.internals.handleBounds?.[t]:[...o.internals.handleBounds?.source??[],...o.internals.handleBounds?.target??[]],c=(n?s?.find(e=>e.id===n):s?.[0])??null;return c&&a?{...c,...Uo(o,c,c.position,!0)}:c}function xs(e,t){return e||(t?.classList.contains(`target`)?`target`:t?.classList.contains(`source`)?`source`:null)}function Ss(e,t){let n=null;return t?n=!0:e&&!t&&(n=!1),n}var Cs=()=>!0;function ws(e,{connectionMode:t,connectionRadius:n,handleId:r,nodeId:i,edgeUpdaterType:a,isTarget:o,domNode:s,nodeLookup:c,lib:l,autoPanOnConnect:u,flowId:d,panBy:f,cancelConnection:p,onConnectStart:m,onConnect:h,onConnectEnd:g,isValidConnection:_=Cs,onReconnectEnd:v,updateConnection:y,getTransform:b,getFromHandle:x,autoPanSpeed:S,dragThreshold:C=1,handleDomNode:w}){let T=_o(e.target),E=0,D,{x:O,y:k}=xo(e),A=xs(a,w),j=s?.getBoundingClientRect(),M=!1;if(!j||!A)return;let N=bs(i,A,r,c,t);if(!N)return;let P=xo(e,j),F=!1,I=null,L=!1,R=null;function ee(){if(!u||!j)return;let[e,t]=Ua(P,j,S);f({x:e,y:t}),E=requestAnimationFrame(ee)}let te={...N,nodeId:i,type:A,position:N.position},ne=c.get(i),z={inProgress:!0,isValid:null,from:Uo(ne,te,B.Left,!0),fromHandle:te,fromPosition:te.position,fromNode:ne,to:P,toHandle:null,toPosition:Ca[te.position],toNode:null,pointer:P};function re(){M=!0,y(z),m?.(e,{nodeId:i,handleId:r,handleType:A})}C===0&&re();function ie(e){if(!M){let{x:t,y:n}=xo(e),r=t-O,i=n-k;if(!(r*r+i*i>C*C))return;re()}if(!x()||!te){ae(e);return}let a=b();P=xo(e,j),D=ys(to(P,a,!1,[1,1]),n,c,te),F||=(ee(),!0);let s=Ts(e,{handle:D,connectionMode:t,fromNodeId:i,fromHandleId:r,fromType:o?`target`:`source`,isValidConnection:_,doc:T,lib:l,flowId:d,nodeLookup:c});R=s.handleDomNode,I=s.connection,L=Ss(!!D,s.isValid);let u=c.get(i),f=u?Uo(u,te,B.Left,!0):z.from,p={...z,from:f,isValid:L,to:s.toHandle&&L?no({x:s.toHandle.x,y:s.toHandle.y},a):P,toHandle:s.toHandle,toPosition:L&&s.toHandle?s.toHandle.position:Ca[te.position],toNode:s.toHandle?c.get(s.toHandle.nodeId):null,pointer:P};y(p),z=p}function ae(e){if(!(`touches`in e&&e.touches.length>0)){if(M){(D||R)&&I&&L&&h?.(I);let{inProgress:t,...n}=z,r={...n,toPosition:z.toHandle?z.toPosition:null};g?.(e,r),a&&v?.(e,r)}p(),cancelAnimationFrame(E),F=!1,L=!1,I=null,R=null,T.removeEventListener(`mousemove`,ie),T.removeEventListener(`mouseup`,ae),T.removeEventListener(`touchmove`,ie),T.removeEventListener(`touchend`,ae)}}T.addEventListener(`mousemove`,ie),T.addEventListener(`mouseup`,ae),T.addEventListener(`touchmove`,ie),T.addEventListener(`touchend`,ae)}function Ts(e,{handle:t,connectionMode:n,fromNodeId:r,fromHandleId:i,fromType:a,doc:o,lib:s,flowId:c,isValidConnection:l=Cs,nodeLookup:u}){let d=a===`target`,f=t?o.querySelector(`.${s}-flow__handle[data-id="${c}-${t?.nodeId}-${t?.id}-${t?.type}"]`):null,{x:p,y:m}=xo(e),h=o.elementFromPoint(p,m),g=h?.classList.contains(`${s}-flow__handle`)?h:f,_={handleDomNode:g,isValid:!1,connection:null,toHandle:null};if(g){let e=xs(void 0,g),t=g.getAttribute(`data-nodeid`),a=g.getAttribute(`data-handleid`),o=g.classList.contains(`connectable`),s=g.classList.contains(`connectableend`);if(!t||!e)return _;let c={source:d?t:r,sourceHandle:d?a:i,target:d?r:t,targetHandle:d?i:a};_.connection=c,_.isValid=o&&s&&(n===_a.Strict?d&&e===`source`||!d&&e===`target`:t!==r||a!==i)&&l(c),_.toHandle=bs(t,e,a,u,n,!0)}return _}var Es={onPointerDown:ws,isValid:Ts};function Ds({domNode:e,panZoom:t,getTransform:n,getViewScale:r}){let i=Nt(e);function a({translateExtent:e,width:a,height:o,zoomStep:s=1,pannable:c=!0,zoomable:l=!0,inversePan:u=!1}){let d=e=>{if(e.sourceEvent.type!==`wheel`||!t)return;let r=n(),i=e.sourceEvent.ctrlKey&&so()?10:1,a=-e.sourceEvent.deltaY*(e.sourceEvent.deltaMode===1?.05:e.sourceEvent.deltaMode?1:.002)*s,o=r[2]*2**(a*i);t.scaleTo(o)},f=[0,0],p=fa().on(`start`,e=>{(e.sourceEvent.type===`mousedown`||e.sourceEvent.type===`touchstart`)&&(f=[e.sourceEvent.clientX??e.sourceEvent.touches[0].clientX,e.sourceEvent.clientY??e.sourceEvent.touches[0].clientY])}).on(`zoom`,c?i=>{let s=n();if(i.sourceEvent.type!==`mousemove`&&i.sourceEvent.type!==`touchmove`||!t)return;let c=[i.sourceEvent.clientX??i.sourceEvent.touches[0].clientX,i.sourceEvent.clientY??i.sourceEvent.touches[0].clientY],l=[c[0]-f[0],c[1]-f[1]];f=c;let d=r()*Math.max(s[2],Math.log(s[2]))*(u?-1:1),p={x:s[0]-l[0]*d,y:s[1]-l[1]*d},m=[[0,0],[a,o]];t.setViewportConstrained({x:p.x,y:p.y,zoom:s[2]},m,e)}:null).on(`zoom.wheel`,l?d:null);i.call(p,{})}function o(){i.on(`zoom`,null)}return{update:a,destroy:o,pointer:Ft}}var Os=e=>({x:e.x,y:e.y,zoom:e.k}),ks=({x:e,y:t,zoom:n})=>na.translate(e,t).scale(n),As=(e,t)=>e.target.closest(`.${t}`),js=(e,t)=>t===2&&Array.isArray(e)&&e.includes(2),Ms=e=>((e*=2)<=1?e*e*e:(e-=2)*e*e+2)/2,Ns=(e,t=0,n=Ms,r=()=>{})=>{let i=typeof t==`number`&&t>0;return i||r(),i?e.transition().duration(t).ease(n).on(`end`,r):e},Ps=e=>{let t=e.ctrlKey&&so()?10:1;return-e.deltaY*(e.deltaMode===1?.05:e.deltaMode?1:.002)*t};function Fs({zoomPanValues:e,noWheelClassName:t,d3Selection:n,d3Zoom:r,panOnScrollMode:i,panOnScrollSpeed:a,zoomOnPinch:o,onPanZoomStart:s,onPanZoom:c,onPanZoomEnd:l}){return u=>{if(As(u,t))return u.ctrlKey&&u.preventDefault(),!1;u.preventDefault(),u.stopImmediatePropagation();let d=n.property(`__zoom`).k||1;if(u.ctrlKey&&o){let e=Ft(u),t=d*2**Ps(u);r.scaleTo(n,t,e,u);return}let f=u.deltaMode===1?20:1,p=i===va.Vertical?0:u.deltaX*f,m=i===va.Horizontal?0:u.deltaY*f;!so()&&u.shiftKey&&i!==va.Vertical&&(p=u.deltaY*f,m=0),r.translateBy(n,-(p/d)*a,-(m/d)*a,{internal:!0});let h=Os(n.property(`__zoom`));clearTimeout(e.panScrollTimeout),e.isPanScrolling?(c?.(u,h),e.panScrollTimeout=setTimeout(()=>{l?.(u,h),e.isPanScrolling=!1},150)):(e.isPanScrolling=!0,s?.(u,h))}}function Is({noWheelClassName:e,preventScrolling:t,d3ZoomHandler:n}){return function(r,i){let a=r.type===`wheel`,o=!t&&a&&!r.ctrlKey,s=As(r,e);if(r.ctrlKey&&a&&s&&r.preventDefault(),o||s)return null;r.preventDefault(),n.call(this,r,i)}}function Ls({zoomPanValues:e,onDraggingChange:t,onPanZoomStart:n}){return r=>{if(r.sourceEvent?.internal)return;let i=Os(r.transform);e.mouseButton=r.sourceEvent?.button||0,e.isZoomingOrPanning=!0,e.prevViewport=i,r.sourceEvent?.type===`mousedown`&&t(!0),n&&n?.(r.sourceEvent,i)}}function Rs({zoomPanValues:e,panOnDrag:t,onPaneContextMenu:n,onTransformChange:r,onPanZoom:i}){return a=>{e.usedRightMouseButton=!!(n&&js(t,e.mouseButton??0)),a.sourceEvent?.sync||r([a.transform.x,a.transform.y,a.transform.k]),i&&!a.sourceEvent?.internal&&i?.(a.sourceEvent,Os(a.transform))}}function zs({zoomPanValues:e,panOnDrag:t,panOnScroll:n,onDraggingChange:r,onPanZoomEnd:i,onPaneContextMenu:a}){return o=>{if(!o.sourceEvent?.internal&&(e.isZoomingOrPanning=!1,a&&js(t,e.mouseButton??0)&&!e.usedRightMouseButton&&o.sourceEvent&&a(o.sourceEvent),e.usedRightMouseButton=!1,r(!1),i)){let t=Os(o.transform);e.prevViewport=t,clearTimeout(e.timerId),e.timerId=setTimeout(()=>{i?.(o.sourceEvent,t)},n?150:0)}}}function Bs({zoomActivationKeyPressed:e,zoomOnScroll:t,zoomOnPinch:n,panOnDrag:r,panOnScroll:i,zoomOnDoubleClick:a,userSelectionActive:o,noWheelClassName:s,noPanClassName:c,lib:l,connectionInProgress:u}){return d=>{let f=e||t,p=n&&d.ctrlKey,m=d.type===`wheel`;if(d.button===1&&d.type===`mousedown`&&(As(d,`${l}-flow__node`)||As(d,`${l}-flow__edge`)))return!0;if(!r&&!f&&!i&&!a&&!n||o||u&&!m||As(d,s)&&m||As(d,c)&&(!m||i&&m&&!e)||!n&&d.ctrlKey&&m)return!1;if(!n&&d.type===`touchstart`&&d.touches?.length>1)return d.preventDefault(),!1;if(!f&&!i&&!p&&m||!r&&(d.type===`mousedown`||d.type===`touchstart`)||Array.isArray(r)&&!r.includes(d.button)&&d.type===`mousedown`)return!1;let h=Array.isArray(r)&&r.includes(d.button)||!d.button||d.button<=1;return(!d.ctrlKey||m)&&h}}function Vs({domNode:e,minZoom:t,maxZoom:n,translateExtent:r,viewport:i,onPanZoom:a,onPanZoomStart:o,onPanZoomEnd:s,onDraggingChange:c}){let l={isZoomingOrPanning:!1,usedRightMouseButton:!1,prevViewport:{},mouseButton:0,timerId:void 0,panScrollTimeout:void 0,isPanScrolling:!1},u=e.getBoundingClientRect(),d=fa().scaleExtent([t,n]).translateExtent(r),f=Nt(e).call(d);v({x:i.x,y:i.y,zoom:za(i.zoom,t,n)},[[0,0],[u.width,u.height]],r);let p=f.on(`wheel.zoom`),m=f.on(`dblclick.zoom`);d.wheelDelta(Ps);async function h(e,t){return f?new Promise(n=>{d?.interpolate(t?.interpolate===`linear`?Qn:fr).transform(Ns(f,t?.duration,t?.ease,()=>n(!0)),e)}):!1}function g({noWheelClassName:e,noPanClassName:t,onPaneContextMenu:n,userSelectionActive:r,panOnScroll:i,panOnDrag:u,panOnScrollMode:h,panOnScrollSpeed:g,preventScrolling:v,zoomOnPinch:y,zoomOnScroll:b,zoomOnDoubleClick:x,zoomActivationKeyPressed:S,lib:C,onTransformChange:w,connectionInProgress:T,paneClickDistance:E,selectionOnDrag:D}){r&&!l.isZoomingOrPanning&&_();let O=i&&!S&&!r;d.clickDistance(D?1/0:!Qa(E)||E<0?0:E);let k=O?Fs({zoomPanValues:l,noWheelClassName:e,d3Selection:f,d3Zoom:d,panOnScrollMode:h,panOnScrollSpeed:g,zoomOnPinch:y,onPanZoomStart:o,onPanZoom:a,onPanZoomEnd:s}):Is({noWheelClassName:e,preventScrolling:v,d3ZoomHandler:p});f.on(`wheel.zoom`,k,{passive:!1});let A=Ls({zoomPanValues:l,onDraggingChange:c,onPanZoomStart:o});d.on(`start`,A);let j=Rs({zoomPanValues:l,panOnDrag:u,onPaneContextMenu:!!n,onPanZoom:a,onTransformChange:w});d.on(`zoom`,j);let M=zs({zoomPanValues:l,panOnDrag:u,panOnScroll:i,onPaneContextMenu:n,onPanZoomEnd:s,onDraggingChange:c});d.on(`end`,M);let N=Bs({zoomActivationKeyPressed:S,panOnDrag:u,zoomOnScroll:b,panOnScroll:i,zoomOnDoubleClick:x,zoomOnPinch:y,userSelectionActive:r,noPanClassName:t,noWheelClassName:e,lib:C,connectionInProgress:T});d.filter(N),x?f.on(`dblclick.zoom`,m):f.on(`dblclick.zoom`,null)}function _(){d.on(`zoom`,null)}async function v(e,t,n){let r=ks(e),i=d?.constrain()(r,t,n);return i&&await h(i),i}async function y(e,t){let n=ks(e);return await h(n,t),n}function b(e){if(f){let t=ks(e),n=f.property(`__zoom`);(n.k!==e.zoom||n.x!==e.x||n.y!==e.y)&&d?.transform(f,t,null,{sync:!0})}}function x(){let e=f?ra(f.node()):{x:0,y:0,k:1};return{x:e.x,y:e.y,zoom:e.k}}async function S(e,t){return f?new Promise(n=>{d?.interpolate(t?.interpolate===`linear`?Qn:fr).scaleTo(Ns(f,t?.duration,t?.ease,()=>n(!0)),e)}):!1}async function C(e,t){return f?new Promise(n=>{d?.interpolate(t?.interpolate===`linear`?Qn:fr).scaleBy(Ns(f,t?.duration,t?.ease,()=>n(!0)),e)}):!1}function w(e){d?.scaleExtent(e)}function T(e){d?.translateExtent(e)}function E(e){let t=!Qa(e)||e<0?0:e;d?.clickDistance(t)}return{update:g,destroy:_,setViewport:y,setViewportConstrained:v,getViewport:x,scaleTo:S,scaleBy:C,setScaleExtent:w,setTranslateExtent:T,syncViewport:b,setClickDistance:E}}var Hs;(function(e){e.Line=`line`,e.Handle=`handle`})(Hs||={});function Us({width:e,prevWidth:t,height:n,prevHeight:r,affectsX:i,affectsY:a}){let o=e-t,s=n-r,c=[o>0?1:o<0?-1:0,s>0?1:s<0?-1:0];return o&&i&&(c[0]*=-1),s&&a&&(c[1]*=-1),c}function Ws(e){return{isHorizontal:e.includes(`right`)||e.includes(`left`),isVertical:e.includes(`bottom`)||e.includes(`top`),affectsX:e.includes(`left`),affectsY:e.includes(`top`)}}function Gs(e,t){return Math.max(0,t-e)}function Ks(e,t){return Math.max(0,e-t)}function qs(e,t,n){return Math.max(0,t-e,e-n)}function Js(e,t){return e?!t:t}function Ys(e,t,n,r,i,a,o,s){let{affectsX:c,affectsY:l}=t,{isHorizontal:u,isVertical:d}=t,f=u&&d,{xSnapped:p,ySnapped:m}=n,{minWidth:h,maxWidth:g,minHeight:_,maxHeight:v}=r,{x:y,y:b,width:x,height:S,aspectRatio:C}=e,w=Math.floor(u?p-e.pointerX:0),T=Math.floor(d?m-e.pointerY:0),E=x+(c?-w:w),D=S+(l?-T:T),O=-a[0]*x,k=-a[1]*S,A=qs(E,h,g),j=qs(D,_,v);if(o){let e=0,t=0;c&&w<0?e=Gs(y+w+O,o[0][0]):!c&&w>0&&(e=Ks(y+E+O,o[1][0])),l&&T<0?t=Gs(b+T+k,o[0][1]):!l&&T>0&&(t=Ks(b+D+k,o[1][1])),A=Math.max(A,e),j=Math.max(j,t)}if(s){let e=0,t=0;c&&w>0?e=Ks(y+w,s[0][0]):!c&&w<0&&(e=Gs(y+E,s[1][0])),l&&T>0?t=Ks(b+T,s[0][1]):!l&&T<0&&(t=Gs(b+D,s[1][1])),A=Math.max(A,e),j=Math.max(j,t)}if(i){if(u){let e=qs(E/C,_,v)*C;if(A=Math.max(A,e),o){let e=0;e=!c&&!l||c&&!l&&f?Ks(b+k+E/C,o[1][1])*C:Gs(b+k+(c?w:-w)/C,o[0][1])*C,A=Math.max(A,e)}if(s){let e=0;e=!c&&!l||c&&!l&&f?Gs(b+E/C,s[1][1])*C:Ks(b+(c?w:-w)/C,s[0][1])*C,A=Math.max(A,e)}}if(d){let e=qs(D*C,h,g)/C;if(j=Math.max(j,e),o){let e=0;e=!c&&!l||l&&!c&&f?Ks(y+D*C+O,o[1][0])/C:Gs(y+(l?T:-T)*C+O,o[0][0])/C,j=Math.max(j,e)}if(s){let e=0;e=!c&&!l||l&&!c&&f?Gs(y+D*C,s[1][0])/C:Ks(y+(l?T:-T)*C,s[0][0])/C,j=Math.max(j,e)}}}T+=T<0?j:-j,w+=w<0?A:-A,i&&(f?E>D*C?T=(Js(c,l)?-w:w)/C:w=(Js(c,l)?-T:T)*C:u?(T=w/C,l=c):(w=T*C,c=l));let M=c?y+w:y,N=l?b+T:b;return{width:x+(c?-w:w),height:S+(l?-T:T),x:a[0]*w*(c?-1:1)+M,y:a[1]*T*(l?-1:1)+N}}var Xs={width:0,height:0,x:0,y:0},Zs={...Xs,pointerX:0,pointerY:0,aspectRatio:1};function Qs(e,t,n){let r=t.position.x+e.position.x,i=t.position.y+e.position.y,a=e.measured.width??0,o=e.measured.height??0,s=n[0]*a,c=n[1]*o;return[[r-s,i-c],[r+a-s,i+o-c]]}function $s({domNode:e,nodeId:t,getStoreItems:n,onChange:r,onEnd:i}){let a=Nt(e),o={controlDirection:Ws(`bottom-right`),boundaries:{minWidth:0,minHeight:0,maxWidth:Number.MAX_VALUE,maxHeight:Number.MAX_VALUE},resizeDirection:void 0,keepAspectRatio:!1};function s({controlPosition:e,boundaries:s,keepAspectRatio:c,resizeDirection:l,onResizeStart:u,onResize:d,onResizeEnd:f,shouldResize:p}){let m={...Xs},h={...Zs};o={boundaries:s,resizeDirection:l,keepAspectRatio:c,controlDirection:Ws(e)};let g,_=null,v=[],y,b,x,S=!1,C=Jt().on(`start`,e=>{let{nodeLookup:r,transform:i,snapGrid:a,snapToGrid:o,nodeOrigin:s,paneDomNode:c}=n();if(g=r.get(t),!g)return;_=c?.getBoundingClientRect()??null;let{xSnapped:l,ySnapped:d}=ho(e.sourceEvent,{transform:i,snapGrid:a,snapToGrid:o,containerBounds:_});m={width:g.measured.width??0,height:g.measured.height??0,x:g.position.x??0,y:g.position.y??0},h={...m,pointerX:l,pointerY:d,aspectRatio:m.width/m.height},y=void 0,b=co(g.extent)?g.extent:void 0,g.parentId&&(g.extent===`parent`||g.expandParent)&&(y=r.get(g.parentId)),y&&g.extent===`parent`&&(b=[[0,0],[y.measured.width,y.measured.height]]),v=[],x=void 0;for(let[e,n]of r)if(n.parentId===t&&(v.push({id:e,position:{...n.position},extent:n.extent}),n.extent===`parent`||n.expandParent)){let e=Qs(n,g,n.origin??s);x=x?[[Math.min(e[0][0],x[0][0]),Math.min(e[0][1],x[0][1])],[Math.max(e[1][0],x[1][0]),Math.max(e[1][1],x[1][1])]]:e}u?.(e,{...m})}).on(`drag`,e=>{let{transform:t,snapGrid:i,snapToGrid:a,nodeOrigin:s}=n(),c=ho(e.sourceEvent,{transform:t,snapGrid:i,snapToGrid:a,containerBounds:_}),l=[];if(!g)return;let{x:u,y:f,width:C,height:w}=m,T={},E=g.origin??s,{width:D,height:O,x:k,y:A}=Ys(h,o.controlDirection,c,o.boundaries,o.keepAspectRatio,E,b,x),j=D!==C,M=O!==w,N=k!==u&&j,P=A!==f&&M;if(!N&&!P&&!j&&!M)return;if((N||P||E[0]===1||E[1]===1)&&(T.x=N?k:m.x,T.y=P?A:m.y,m.x=T.x,m.y=T.y,v.length>0)){let e=k-u,t=A-f;for(let n of v)n.position={x:n.position.x-e+E[0]*(D-C),y:n.position.y-t+E[1]*(O-w)},l.push(n)}if((j||M)&&(T.width=j&&(!o.resizeDirection||o.resizeDirection===`horizontal`)?D:m.width,T.height=M&&(!o.resizeDirection||o.resizeDirection===`vertical`)?O:m.height,m.width=T.width,m.height=T.height),y&&g.expandParent){let e=E[0]*(T.width??0);T.x&&T.x{S&&=(f?.(e,{...m}),i?.({...m}),!1)});a.call(C)}function c(){a.on(`.drag`,null)}return{update:s,destroy:c}}var ec=t((e=>{var t=r();function n(e,t){return e===t&&(e!==0||1/e==1/t)||e!==e&&t!==t}var i=typeof Object.is==`function`?Object.is:n,a=t.useState,o=t.useEffect,s=t.useLayoutEffect,c=t.useDebugValue;function l(e,t){var n=t(),r=a({inst:{value:n,getSnapshot:t}}),i=r[0].inst,l=r[1];return s(function(){i.value=n,i.getSnapshot=t,u(i)&&l({inst:i})},[e,n,t]),o(function(){return u(i)&&l({inst:i}),e(function(){u(i)&&l({inst:i})})},[e]),c(n),n}function u(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!i(e,n)}catch{return!0}}function d(e,t){return t()}var f=typeof window>`u`||window.document===void 0||window.document.createElement===void 0?d:l;e.useSyncExternalStore=t.useSyncExternalStore===void 0?f:t.useSyncExternalStore})),tc=t(((e,t)=>{t.exports=ec()})),nc=t((e=>{var t=r(),n=tc();function i(e,t){return e===t&&(e!==0||1/e==1/t)||e!==e&&t!==t}var a=typeof Object.is==`function`?Object.is:i,o=n.useSyncExternalStore,s=t.useRef,c=t.useEffect,l=t.useMemo,u=t.useDebugValue;e.useSyncExternalStoreWithSelector=function(e,t,n,r,i){var d=s(null);if(d.current===null){var f={hasValue:!1,value:null};d.current=f}else f=d.current;d=l(function(){function e(e){if(!o){if(o=!0,s=e,e=r(e),i!==void 0&&f.hasValue){var t=f.value;if(i(t,e))return c=t}return c=e}if(t=c,a(s,e))return t;var n=r(e);return i!==void 0&&i(t,n)?(s=e,t):(s=e,c=n)}var o=!1,s,c,l=n===void 0?null:n;return[function(){return e(t())},l===null?void 0:function(){return e(l())}]},[t,n,r,i]);var p=o(e,d[0],d[1]);return c(function(){f.hasValue=!0,f.value=p},[p]),u(p),p}})),rc=e(t(((e,t)=>{t.exports=nc()}))(),1),ic=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e)),destroy:()=>{n.clear()}},o=t=e(r,i,a);return a},ac=e=>e?ic(e):ic,{useDebugValue:oc}=f.default,{useSyncExternalStoreWithSelector:sc}=rc.default,cc=e=>e;function lc(e,t=cc,n){let r=sc(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,n);return oc(r),r}var uc=(e,t)=>{let n=ac(e),r=(e,r=t)=>lc(n,e,r);return Object.assign(r,n),r},dc=(e,t)=>e?uc(e,t):uc;function fc(e,t){if(Object.is(e,t))return!0;if(typeof e!=`object`||!e||typeof t!=`object`||!t)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(let[n,r]of e)if(!Object.is(r,t.get(n)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}let n=Object.keys(e);if(n.length!==Object.keys(t).length)return!1;for(let r of n)if(!Object.prototype.hasOwnProperty.call(t,r)||!Object.is(e[r],t[r]))return!1;return!0}var pc=e(s()),mc=(0,f.createContext)(null),hc=mc.Provider,gc=pa.error001(`react`);function H(e,t){let n=(0,f.useContext)(mc);if(n===null)throw Error(gc);return lc(n,e,t)}function _c(){let e=(0,f.useContext)(mc);if(e===null)throw Error(gc);return(0,f.useMemo)(()=>({getState:e.getState,setState:e.setState,subscribe:e.subscribe}),[e])}var vc={display:`none`},yc={position:`absolute`,width:1,height:1,margin:-1,border:0,padding:0,overflow:`hidden`,clip:`rect(0px, 0px, 0px, 0px)`,clipPath:`inset(100%)`},bc=`react-flow__node-desc`,xc=`react-flow__edge-desc`,Sc=`react-flow__aria-live`,Cc=e=>e.ariaLiveMessage,wc=e=>e.ariaLabelConfig;function Tc({rfId:e}){let t=H(Cc);return(0,p.jsx)(`div`,{id:`${Sc}-${e}`,"aria-live":`assertive`,"aria-atomic":`true`,style:yc,children:t})}function Ec({rfId:e,disableKeyboardA11y:t}){let n=H(wc);return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(`div`,{id:`${bc}-${e}`,style:vc,children:t?n[`node.a11yDescription.default`]:n[`node.a11yDescription.keyboardDisabled`]}),(0,p.jsx)(`div`,{id:`${xc}-${e}`,style:vc,children:n[`edge.a11yDescription.default`]}),!t&&(0,p.jsx)(Tc,{rfId:e})]})}var Dc=(0,f.forwardRef)(({position:e=`top-left`,children:t,className:n,style:r,...i},a)=>(0,p.jsx)(`div`,{className:m([`react-flow__panel`,n,...`${e}`.split(`-`)]),style:r,ref:a,...i,children:t}));Dc.displayName=`Panel`;function Oc({proOptions:e,position:t=`bottom-right`}){return e?.hideAttribution?null:(0,p.jsx)(Dc,{position:t,className:`react-flow__attribution`,"data-message":`Please only hide this attribution when you are subscribed to React Flow Pro: https://pro.reactflow.dev`,children:(0,p.jsx)(`a`,{href:`https://reactflow.dev`,target:`_blank`,rel:`noopener noreferrer`,"aria-label":`React Flow attribution`,children:`React Flow`})})}var kc=e=>{let t=[],n=[];for(let[,n]of e.nodeLookup)n.selected&&t.push(n.internals.userNode);for(let[,t]of e.edgeLookup)t.selected&&n.push(t);return{selectedNodes:t,selectedEdges:n}},Ac=e=>e.id;function jc(e,t){return fc(e.selectedNodes.map(Ac),t.selectedNodes.map(Ac))&&fc(e.selectedEdges.map(Ac),t.selectedEdges.map(Ac))}function Mc({onSelectionChange:e}){let t=_c(),{selectedNodes:n,selectedEdges:r}=H(kc,jc);return(0,f.useEffect)(()=>{let i={nodes:n,edges:r};e?.(i),t.getState().onSelectionChangeHandlers.forEach(e=>e(i))},[n,r,e]),null}var Nc=e=>!!e.onSelectionChangeHandlers;function Pc({onSelectionChange:e}){let t=H(Nc);return e||t?(0,p.jsx)(Mc,{onSelectionChange:e}):null}var Fc=[0,0],Ic={x:0,y:0,zoom:1},Lc=[...`nodes.edges.defaultNodes.defaultEdges.onConnect.onConnectStart.onConnectEnd.onClickConnectStart.onClickConnectEnd.nodesDraggable.autoPanOnNodeFocus.nodesConnectable.nodesFocusable.edgesFocusable.edgesReconnectable.elevateNodesOnSelect.elevateEdgesOnSelect.minZoom.maxZoom.nodeExtent.onNodesChange.onEdgesChange.elementsSelectable.connectionMode.snapGrid.snapToGrid.translateExtent.connectOnClick.defaultEdgeOptions.fitView.fitViewOptions.onNodesDelete.onEdgesDelete.onDelete.onNodeDrag.onNodeDragStart.onNodeDragStop.onSelectionDrag.onSelectionDragStart.onSelectionDragStop.onMoveStart.onMove.onMoveEnd.noPanClassName.nodeOrigin.autoPanOnConnect.autoPanOnNodeDrag.onError.connectionRadius.isValidConnection.selectNodesOnDrag.nodeDragThreshold.connectionDragThreshold.onBeforeDelete.debug.autoPanSpeed.ariaLabelConfig.zIndexMode`.split(`.`),`rfId`],Rc=e=>({setNodes:e.setNodes,setEdges:e.setEdges,setMinZoom:e.setMinZoom,setMaxZoom:e.setMaxZoom,setTranslateExtent:e.setTranslateExtent,setNodeExtent:e.setNodeExtent,reset:e.reset,setDefaultNodesAndEdges:e.setDefaultNodesAndEdges}),zc={translateExtent:ma,nodeOrigin:Fc,minZoom:.5,maxZoom:2,elementsSelectable:!0,noPanClassName:`nopan`,rfId:`1`};function Bc(e){let{setNodes:t,setEdges:n,setMinZoom:r,setMaxZoom:i,setTranslateExtent:a,setNodeExtent:o,reset:s,setDefaultNodesAndEdges:c}=H(Rc,fc),l=_c();(0,f.useEffect)(()=>(c(e.defaultNodes,e.defaultEdges),()=>{u.current=zc,s()}),[]);let u=(0,f.useRef)(zc);return(0,f.useEffect)(()=>{for(let s of Lc){let c=e[s];c!==u.current[s]&&e[s]!==void 0&&(s===`nodes`?t(c):s===`edges`?n(c):s===`minZoom`?r(c):s===`maxZoom`?i(c):s===`translateExtent`?a(c):s===`nodeExtent`?o(c):s===`ariaLabelConfig`?l.setState({ariaLabelConfig:mo(c)}):s===`fitView`?l.setState({fitViewQueued:c}):s===`fitViewOptions`?l.setState({fitViewOptions:c}):l.setState({[s]:c}))}u.current=e},Lc.map(t=>e[t])),null}function Vc(){return typeof window>`u`||!window.matchMedia?null:window.matchMedia(`(prefers-color-scheme: dark)`)}function Hc(e){let[t,n]=(0,f.useState)(e===`system`?null:e);return(0,f.useEffect)(()=>{if(e!==`system`){n(e);return}let t=Vc(),r=()=>n(t?.matches?`dark`:`light`);return r(),t?.addEventListener(`change`,r),()=>{t?.removeEventListener(`change`,r)}},[e]),t===null?Vc()?.matches?`dark`:`light`:t}var Uc=typeof document<`u`?document:null;function Wc(e=null,t={target:Uc,actInsideInputWithModifier:!0}){let[n,r]=(0,f.useState)(!1),i=(0,f.useRef)(!1),a=(0,f.useRef)(new Set([])),[o,s]=(0,f.useMemo)(()=>{if(e!==null){let t=(Array.isArray(e)?e:[e]).filter(e=>typeof e==`string`).map(e=>e.replace(`+`,` +`).replace(` + +`,` ++`).split(` +`));return[t,t.reduce((e,t)=>e.concat(...t),[])]}return[[],[]]},[e]);return(0,f.useEffect)(()=>{let n=t?.target??Uc,c=t?.actInsideInputWithModifier??!0;if(e!==null){let e=e=>{if(i.current=e.ctrlKey||e.metaKey||e.shiftKey||e.altKey,(!i.current||i.current&&!c)&&yo(e))return!1;let n=Kc(e.code,s);if(a.current.add(e[n]),Gc(o,a.current,!1)){let n=e.composedPath?.()?.[0]||e.target,a=n?.nodeName===`BUTTON`||n?.nodeName===`A`;t.preventDefault!==!1&&(i.current||!a)&&e.preventDefault(),r(!0)}},l=e=>{let t=Kc(e.code,s);Gc(o,a.current,!0)?(r(!1),a.current.clear()):a.current.delete(e[t]),e.key===`Meta`&&a.current.clear(),i.current=!1},u=()=>{a.current.clear(),r(!1)};return n?.addEventListener(`keydown`,e),n?.addEventListener(`keyup`,l),window.addEventListener(`blur`,u),window.addEventListener(`contextmenu`,u),()=>{n?.removeEventListener(`keydown`,e),n?.removeEventListener(`keyup`,l),window.removeEventListener(`blur`,u),window.removeEventListener(`contextmenu`,u)}}},[e,r]),n}function Gc(e,t,n){return e.filter(e=>n||e.length===t.size).some(e=>e.every(e=>t.has(e)))}function Kc(e,t){return t.includes(e)?`code`:`key`}var qc=()=>{let e=_c();return(0,f.useMemo)(()=>({zoomIn:async t=>{let{panZoom:n}=e.getState();return n?n.scaleBy(1.2,t):!1},zoomOut:async t=>{let{panZoom:n}=e.getState();return n?n.scaleBy(1/1.2,t):!1},zoomTo:async(t,n)=>{let{panZoom:r}=e.getState();return r?r.scaleTo(t,n):!1},getZoom:()=>e.getState().transform[2],setViewport:async(t,n)=>{let{transform:[r,i,a],panZoom:o}=e.getState();return o?(await o.setViewport({x:t.x??r,y:t.y??i,zoom:t.zoom??a},n),!0):!1},getViewport:()=>{let[t,n,r]=e.getState().transform;return{x:t,y:n,zoom:r}},setCenter:async(t,n,r)=>e.getState().setCenter(t,n,r),fitBounds:async(t,n)=>{let{width:r,height:i,minZoom:a,maxZoom:o,panZoom:s}=e.getState(),c=oo(t,r,i,a,o,n?.padding??.1);return s?(await s.setViewport(c,{duration:n?.duration,ease:n?.ease,interpolate:n?.interpolate}),!0):!1},screenToFlowPosition:(t,n={})=>{let{transform:r,snapGrid:i,snapToGrid:a,domNode:o}=e.getState();if(!o)return t;let{x:s,y:c}=o.getBoundingClientRect(),l={x:t.x-s,y:t.y-c},u=n.snapGrid??i;return to(l,r,n.snapToGrid??a,u)},flowToScreenPosition:t=>{let{transform:n,domNode:r}=e.getState();if(!r)return t;let{x:i,y:a}=r.getBoundingClientRect(),o=no(t,n);return{x:o.x+i,y:o.y+a}}}),[])};function Jc(e,t){let n=[],r=new Map,i=[];for(let t of e)if(t.type===`add`){i.push(t);continue}else if(t.type===`remove`||t.type===`replace`)r.set(t.id,[t]);else{let e=r.get(t.id);e?e.push(t):r.set(t.id,[t])}for(let e of t){let t=r.get(e.id);if(!t){n.push(e);continue}if(t[0].type===`remove`)continue;if(t[0].type===`replace`){n.push({...t[0].item});continue}let i={...e};for(let e of t)Yc(e,i);n.push(i)}return i.length&&i.forEach(e=>{e.index===void 0?n.push({...e.item}):n.splice(e.index,0,{...e.item})}),n}function Yc(e,t){switch(e.type){case`select`:t.selected=e.selected;break;case`position`:e.position!==void 0&&(t.position=e.position),e.dragging!==void 0&&(t.dragging=e.dragging);break;case`dimensions`:e.dimensions!==void 0&&(t.measured={...e.dimensions},e.setAttributes&&((e.setAttributes===!0||e.setAttributes===`width`)&&(t.width=e.dimensions.width),(e.setAttributes===!0||e.setAttributes===`height`)&&(t.height=e.dimensions.height))),typeof e.resizing==`boolean`&&(t.resizing=e.resizing);break}}function Xc(e,t){return Jc(e,t)}function Zc(e,t){return Jc(e,t)}function Qc(e,t){return{id:e,type:`select`,selected:t}}function $c(e,t=new Set,n=!1){let r=[];for(let[i,a]of e){let e=t.has(i);!(a.selected===void 0&&!e)&&a.selected!==e&&(n&&(a.selected=e),r.push(Qc(a.id,e)))}return r}function el({items:e=[],lookup:t}){let n=[],r=new Map(e.map(e=>[e.id,e]));for(let[r,i]of e.entries()){let e=t.get(i.id),a=e?.internals?.userNode??e;a!==void 0&&a!==i&&n.push({id:i.id,item:i,type:`replace`}),a===void 0&&n.push({item:i,type:`add`,index:r})}for(let[e]of t)r.get(e)===void 0&&n.push({id:e,type:`remove`});return n}function tl(e){return{id:e.id,type:`remove`}}var nl=$a(`React Flow`,`https://reactflow.dev/`);function rl(e,t,n={}){return Mo(e,t,{...n,onError:n.onError??nl})}var il=e=>Ea(e),al=e=>Ta(e);function ol(e){return(0,f.forwardRef)(e)}var sl=typeof window<`u`?f.useLayoutEffect:f.useEffect;function cl(e){let[t,n]=(0,f.useState)(BigInt(0)),[r]=(0,f.useState)(()=>ll(()=>n(e=>e+BigInt(1))));return sl(()=>{let t=r.get();t.length&&(e(t),r.reset())},[t]),r}function ll(e){let t=[];return{get:()=>t,reset:()=>{t=[]},push:n=>{t.push(n),e()}}}var ul=(0,f.createContext)(null);function dl({children:e}){let t=_c(),n=cl((0,f.useCallback)(e=>{let{nodes:n=[],setNodes:r,hasDefaultNodes:i,onNodesChange:a,nodeLookup:o,fitViewQueued:s,onNodesChangeMiddlewareMap:c}=t.getState(),l=n;for(let t of e)l=typeof t==`function`?t(l):t;let u=el({items:l,lookup:o});for(let e of c.values())u=e(u);i&&r(l),u.length>0?a?.(u):s&&window.requestAnimationFrame(()=>{let{fitViewQueued:e,nodes:n,setNodes:r}=t.getState();e&&r(n)})},[])),r=cl((0,f.useCallback)(e=>{let{edges:n=[],setEdges:r,hasDefaultEdges:i,onEdgesChange:a,edgeLookup:o}=t.getState(),s=n;for(let t of e)s=typeof t==`function`?t(s):t;i?r(s):a&&a(el({items:s,lookup:o}))},[])),i=(0,f.useMemo)(()=>({nodeQueue:n,edgeQueue:r}),[]);return(0,p.jsx)(ul.Provider,{value:i,children:e})}function fl(){let e=(0,f.useContext)(ul);if(!e)throw Error(`useBatchContext must be used within a BatchProvider`);return e}var pl=e=>!!e.panZoom;function ml(){let e=qc(),t=_c(),n=fl(),r=H(pl),i=(0,f.useMemo)(()=>{let e=e=>t.getState().nodeLookup.get(e),r=e=>{n.nodeQueue.push(e)},i=e=>{n.edgeQueue.push(e)},a=e=>{let{nodeLookup:n,nodeOrigin:r}=t.getState(),i=il(e)?e:n.get(e.id),a=i.parentId?fo(i.position,i.measured,i.parentId,n,r):i.position;return qa({...i,position:a,width:i.measured?.width??i.width,height:i.measured?.height??i.height})},o=(e,t,n={replace:!1})=>{r(r=>r.map(r=>{if(r.id===e){let e=typeof t==`function`?t(r):t;return n.replace&&il(e)?e:{...r,...e}}return r}))},s=(e,t,n={replace:!1})=>{i(r=>r.map(r=>{if(r.id===e){let e=typeof t==`function`?t(r):t;return n.replace&&al(e)?e:{...r,...e}}return r}))};return{getNodes:()=>t.getState().nodes.map(e=>({...e})),getNode:t=>e(t)?.internals.userNode,getInternalNode:e,getEdges:()=>{let{edges:e=[]}=t.getState();return e.map(e=>({...e}))},getEdge:e=>t.getState().edgeLookup.get(e),setNodes:r,setEdges:i,addNodes:e=>{let t=Array.isArray(e)?e:[e];n.nodeQueue.push(e=>[...e,...t])},addEdges:e=>{let t=Array.isArray(e)?e:[e];n.edgeQueue.push(e=>[...e,...t])},toObject:()=>{let{nodes:e=[],edges:n=[],transform:r}=t.getState(),[i,a,o]=r;return{nodes:e.map(e=>({...e})),edges:n.map(e=>({...e})),viewport:{x:i,y:a,zoom:o}}},deleteElements:async({nodes:e=[],edges:n=[]})=>{let{nodes:r,edges:i,onNodesDelete:a,onEdgesDelete:o,triggerNodeChanges:s,triggerEdgeChanges:c,onDelete:l,onBeforeDelete:u}=t.getState(),{nodes:d,edges:f}=await Ra({nodesToRemove:e,edgesToRemove:n,nodes:r,edges:i,onBeforeDelete:u}),p=f.length>0,m=d.length>0;if(p){let e=f.map(tl);o?.(f),c(e)}if(m){let e=d.map(tl);a?.(d),s(e)}return(m||p)&&l?.({nodes:d,edges:f}),{deletedNodes:d,deletedEdges:f}},getIntersectingNodes:(e,n=!0,r)=>{let i=Za(e),o=i?e:a(e),s=r!==void 0;return o?(r||t.getState().nodes).filter(r=>{let a=t.getState().nodeLookup.get(r.id);if(a&&!i&&(r.id===e.id||!a.internals.positionAbsolute))return!1;let c=qa(s?r:a),l=Xa(c,o);return n&&l>0||l>=c.width*c.height||l>=o.width*o.height}):[]},isNodeIntersecting:(e,t,n=!0)=>{let r=Za(e)?e:a(e);if(!r)return!1;let i=Xa(r,t);return n&&i>0||i>=t.width*t.height||i>=r.width*r.height},updateNode:o,updateNodeData:(e,t,n={replace:!1})=>{o(e,e=>{let r=typeof t==`function`?t(e):t;return n.replace?{...e,data:r}:{...e,data:{...e.data,...r}}},n)},updateEdge:s,updateEdgeData:(e,t,n={replace:!1})=>{s(e,e=>{let r=typeof t==`function`?t(e):t;return n.replace?{...e,data:r}:{...e,data:{...e.data,...r}}},n)},getNodesBounds:e=>{let{nodeLookup:n,nodeOrigin:r}=t.getState();return ja(e,{nodeLookup:n,nodeOrigin:r})},getHandleConnections:({type:e,id:n,nodeId:r})=>Array.from(t.getState().connectionLookup.get(`${r}-${e}${n?`-${n}`:``}`)?.values()??[]),getNodeConnections:({type:e,handleId:n,nodeId:r})=>Array.from(t.getState().connectionLookup.get(`${r}${e?n?`-${e}-${n}`:`-${e}`:``}`)?.values()??[]),fitView:async e=>{let r=t.getState().fitViewResolver??po();return t.setState({fitViewQueued:!0,fitViewOptions:e,fitViewResolver:r}),n.nodeQueue.push(e=>[...e]),r.promise}}},[]);return(0,f.useMemo)(()=>({...i,...e,viewportInitialized:r}),[r])}var hl=e=>e.selected,gl=typeof window<`u`?window:void 0;function _l({deleteKeyCode:e,multiSelectionKeyCode:t}){let n=_c(),{deleteElements:r}=ml(),i=Wc(e,{actInsideInputWithModifier:!1}),a=Wc(t,{target:gl});(0,f.useEffect)(()=>{if(i){let{edges:e,nodes:t}=n.getState();r({nodes:t.filter(hl),edges:e.filter(hl)}),n.setState({nodesSelectionActive:!1})}},[i]),(0,f.useEffect)(()=>{n.setState({multiSelectionActive:a})},[a])}function vl(e){let t=_c();(0,f.useEffect)(()=>{let n=()=>{if(!e.current||!(e.current.checkVisibility?.()??!0))return!1;let n=go(e.current);(n.height===0||n.width===0)&&t.getState().onError?.(`004`,pa.error004()),t.setState({width:n.width||500,height:n.height||500})};if(e.current){n(),window.addEventListener(`resize`,n);let t=new ResizeObserver(()=>n());return t.observe(e.current),()=>{window.removeEventListener(`resize`,n),t&&e.current&&t.unobserve(e.current)}}},[])}var yl={position:`absolute`,width:`100%`,height:`100%`,top:0,left:0},bl=e=>({userSelectionActive:e.userSelectionActive,lib:e.lib,connectionInProgress:e.connection.inProgress});function xl({onPaneContextMenu:e,zoomOnScroll:t=!0,zoomOnPinch:n=!0,panOnScroll:r=!1,panOnScrollSpeed:i=.5,panOnScrollMode:a=va.Free,zoomOnDoubleClick:o=!0,panOnDrag:s=!0,defaultViewport:c,translateExtent:l,minZoom:u,maxZoom:d,zoomActivationKeyCode:m,preventScrolling:h=!0,children:g,noWheelClassName:_,noPanClassName:v,onViewportChange:y,isControlledViewport:b,paneClickDistance:x,selectionOnDrag:S}){let C=_c(),w=(0,f.useRef)(null),{userSelectionActive:T,lib:E,connectionInProgress:D}=H(bl,fc),O=Wc(m),k=(0,f.useRef)();vl(w);let A=(0,f.useCallback)(e=>{y?.({x:e[0],y:e[1],zoom:e[2]}),b||C.setState({transform:e})},[y,b]);return(0,f.useEffect)(()=>{if(w.current){k.current=Vs({domNode:w.current,minZoom:u,maxZoom:d,translateExtent:l,viewport:c,onDraggingChange:e=>C.setState(t=>t.paneDragging===e?t:{paneDragging:e}),onPanZoomStart:(e,t)=>{let{onViewportChangeStart:n,onMoveStart:r}=C.getState();r?.(e,t),n?.(t)},onPanZoom:(e,t)=>{let{onViewportChange:n,onMove:r}=C.getState();r?.(e,t),n?.(t)},onPanZoomEnd:(e,t)=>{let{onViewportChangeEnd:n,onMoveEnd:r}=C.getState();r?.(e,t),n?.(t)}});let{x:e,y:t,zoom:n}=k.current.getViewport();return C.setState({panZoom:k.current,transform:[e,t,n],domNode:w.current.closest(`.react-flow`)}),()=>{k.current?.destroy()}}},[]),(0,f.useEffect)(()=>{k.current?.update({onPaneContextMenu:e,zoomOnScroll:t,zoomOnPinch:n,panOnScroll:r,panOnScrollSpeed:i,panOnScrollMode:a,zoomOnDoubleClick:o,panOnDrag:s,zoomActivationKeyPressed:O,preventScrolling:h,noPanClassName:v,userSelectionActive:T,noWheelClassName:_,lib:E,onTransformChange:A,connectionInProgress:D,selectionOnDrag:S,paneClickDistance:x})},[e,t,n,r,i,a,o,s,O,h,v,T,_,E,A,D,S,x]),(0,p.jsx)(`div`,{className:`react-flow__renderer`,ref:w,style:yl,children:g})}var Sl=e=>({userSelectionActive:e.userSelectionActive,userSelectionRect:e.userSelectionRect});function Cl(){let{userSelectionActive:e,userSelectionRect:t}=H(Sl,fc);return e&&t?(0,p.jsx)(`div`,{className:`react-flow__selection react-flow__container`,style:{width:t.width,height:t.height,transform:`translate(${t.x}px, ${t.y}px)`}}):null}var wl=(e,t)=>n=>{n.target===t.current&&e?.(n)},Tl=e=>({userSelectionActive:e.userSelectionActive,elementsSelectable:e.elementsSelectable,connectionInProgress:e.connection.inProgress,dragging:e.paneDragging,panBy:e.panBy,autoPanSpeed:e.autoPanSpeed});function El({isSelecting:e,selectionKeyPressed:t,selectionMode:n=ya.Full,panOnDrag:r,autoPanOnSelection:i,paneClickDistance:a,selectionOnDrag:o,onSelectionStart:s,onSelectionEnd:c,onPaneClick:l,onPaneContextMenu:u,onPaneScroll:d,onPaneMouseEnter:h,onPaneMouseMove:g,onPaneMouseLeave:_,children:v}){let y=(0,f.useRef)(0),b=_c(),{userSelectionActive:x,elementsSelectable:S,dragging:C,connectionInProgress:w,panBy:T,autoPanSpeed:E}=H(Tl,fc),D=S&&(e||x),O=(0,f.useRef)(null),k=(0,f.useRef)(),A=(0,f.useRef)(new Set),j=(0,f.useRef)(new Set),M=(0,f.useRef)(!1),N=(0,f.useRef)({x:0,y:0}),P=(0,f.useRef)(!1),F=e=>{if(M.current||w){M.current=!1;return}l?.(e),b.getState().resetSelectedElements(),b.setState({nodesSelectionActive:!1})},I=e=>{if(Array.isArray(r)&&r?.includes(2)){e.preventDefault();return}u?.(e)},L=d?e=>d(e):void 0,R=e=>{M.current&&=(e.stopPropagation(),!1)},ee=n=>{let{domNode:r,transform:i}=b.getState();if(k.current=r?.getBoundingClientRect(),!k.current)return;let a=n.target===O.current;if(!a&&n.target.closest(`.nokey`)||!e||!(o&&a||t)||n.button!==0||!n.isPrimary)return;n.target?.setPointerCapture?.(n.pointerId),M.current=!1;let{x:s,y:c}=xo(n.nativeEvent,k.current),l=to({x:s,y:c},i);b.setState({userSelectionRect:{width:0,height:0,startX:l.x,startY:l.y,x:s,y:c}}),a||(n.stopPropagation(),n.preventDefault())};function te(e,t){let{userSelectionRect:r}=b.getState();if(!r)return;let{transform:i,nodeLookup:a,edgeLookup:o,connectionLookup:s,triggerNodeChanges:c,triggerEdgeChanges:l,defaultEdgeOptions:u}=b.getState(),d={x:r.startX,y:r.startY},{x:f,y:p}=no(d,i),m={startX:d.x,startY:d.y,x:ee.id)),j.current=new Set;let _=u?.selectable??!0;for(let e of A.current){let t=s.get(e);if(t)for(let{edgeId:e}of t.values()){let t=o.get(e);t&&(t.selectable??_)&&j.current.add(e)}}V(h,A.current)||c($c(a,A.current,!0)),V(g,j.current)||l($c(o,j.current)),b.setState({userSelectionRect:m,userSelectionActive:!0,nodesSelectionActive:!1})}function ne(){if(!i||!k.current)return;let[e,t]=Ua(N.current,k.current,E);T({x:e,y:t}).then(e=>{if(!M.current||!e){y.current=requestAnimationFrame(ne);return}let{x:t,y:n}=N.current;te(t,n),y.current=requestAnimationFrame(ne)})}let z=()=>{cancelAnimationFrame(y.current),y.current=0,P.current=!1};return(0,f.useEffect)(()=>()=>z(),[]),(0,p.jsxs)(`div`,{className:m([`react-flow__pane`,{draggable:r===!0||Array.isArray(r)&&r.includes(0),dragging:C,selection:e}]),onClick:D?void 0:wl(F,O),onContextMenu:wl(I,O),onWheel:wl(L,O),onPointerEnter:D?void 0:h,onPointerMove:D?e=>{let{userSelectionRect:n,transform:r,resetSelectedElements:i}=b.getState();if(!k.current||!n)return;let{x:o,y:c}=xo(e.nativeEvent,k.current);N.current={x:o,y:c};let l=no({x:n.startX,y:n.startY},r);if(!M.current){let n=t?0:a;if(Math.hypot(o-l.x,c-l.y)<=n)return;i(),s?.(e)}M.current=!0,P.current||=(ne(),!0),te(o,c)}:g,onPointerUp:D?e=>{e.button===0&&(e.target?.releasePointerCapture?.(e.pointerId),!x&&e.target===O.current&&b.getState().userSelectionRect&&F?.(e),b.setState({userSelectionActive:!1,userSelectionRect:null}),M.current&&(c?.(e),b.setState({nodesSelectionActive:A.current.size>0})),z())}:void 0,onPointerCancel:D?e=>{e.target?.releasePointerCapture?.(e.pointerId),z()}:void 0,onPointerDownCapture:D?ee:void 0,onClickCapture:D?R:void 0,onPointerLeave:_,ref:O,style:yl,children:[v,(0,p.jsx)(Cl,{})]})}function Dl({id:e,store:t,unselect:n=!1,nodeRef:r}){let{addSelectedNodes:i,unselectNodesAndEdges:a,multiSelectionActive:o,nodeLookup:s,onError:c}=t.getState(),l=s.get(e);if(!l){c?.(`012`,pa.error012(e));return}t.setState({nodesSelectionActive:!1}),l.selected?(n||l.selected&&o)&&(a({nodes:[l],edges:[]}),requestAnimationFrame(()=>r?.current?.blur())):i([e])}function Ol({nodeRef:e,disabled:t=!1,noDragClassName:n,handleSelector:r,nodeId:i,isSelectable:a,nodeClickDistance:o}){let s=_c(),[c,l]=(0,f.useState)(!1),u=(0,f.useRef)();return(0,f.useEffect)(()=>{u.current=gs({getStoreItems:()=>s.getState(),onNodeMouseDown:t=>{Dl({id:t,store:s,nodeRef:e})},onDragStart:()=>{l(!0)},onDragStop:()=>{l(!1)}})},[]),(0,f.useEffect)(()=>{if(!(t||!e.current||!u.current))return u.current.update({noDragClassName:n,handleSelector:r,domNode:e.current,isSelectable:a,nodeId:i,nodeClickDistance:o}),()=>{u.current?.destroy()}},[n,r,t,a,e,i,o]),c}var kl=e=>t=>t.selected&&(t.draggable||e&&t.draggable===void 0);function Al(){let e=_c();return(0,f.useCallback)(t=>{let{nodeExtent:n,snapToGrid:r,snapGrid:i,nodesDraggable:a,onError:o,updateNodePositions:s,nodeLookup:c,nodeOrigin:l}=e.getState(),u=new Map,d=kl(a),f=r?i[0]:5,p=r?i[1]:5,m=t.direction.x*f*t.factor,h=t.direction.y*p*t.factor;for(let[,e]of c){if(!d(e))continue;let t={x:e.internals.positionAbsolute.x+m,y:e.internals.positionAbsolute.y+h};r&&(t=eo(t,i));let{position:a,positionAbsolute:s}=La({nodeId:e.id,nextPosition:t,nodeLookup:c,nodeExtent:n,nodeOrigin:l,onError:o});e.position=a,e.internals.positionAbsolute=s,u.set(e.id,e)}s(u)},[])}var jl=(0,f.createContext)(null),Ml=jl.Provider;jl.Consumer;var Nl=()=>(0,f.useContext)(jl),Pl=e=>({connectOnClick:e.connectOnClick,noPanClassName:e.noPanClassName,rfId:e.rfId}),Fl=(e,t,n)=>r=>{let{connectionClickStartHandle:i,connectionMode:a,connection:o}=r,{fromHandle:s,toHandle:c,isValid:l}=o,u=c?.nodeId===e&&c?.id===t&&c?.type===n;return{connectingFrom:s?.nodeId===e&&s?.id===t&&s?.type===n,connectingTo:u,clickConnecting:i?.nodeId===e&&i?.id===t&&i?.type===n,isPossibleEndHandle:a===_a.Strict?s?.type!==n:e!==s?.nodeId||t!==s?.id,connectionInProcess:!!s,clickConnectionInProcess:!!i,valid:u&&l}};function Il({type:e=`source`,position:t=B.Top,isValidConnection:n,isConnectable:r=!0,isConnectableStart:i=!0,isConnectableEnd:a=!0,id:o,onConnect:s,children:c,className:l,onMouseDown:u,onTouchStart:d,...f},h){let g=o||null,_=e===`target`,v=_c(),y=Nl(),{connectOnClick:b,noPanClassName:x,rfId:S}=H(Pl,fc),{connectingFrom:C,connectingTo:w,clickConnecting:T,isPossibleEndHandle:E,connectionInProcess:D,clickConnectionInProcess:O,valid:k}=H(Fl(y,g,e),fc);y||v.getState().onError?.(`010`,pa.error010());let A=e=>{let{defaultEdgeOptions:t,onConnect:n,hasDefaultEdges:r}=v.getState(),i={...t,...e};if(r){let{edges:e,setEdges:t,onError:n}=v.getState();t(rl(i,e,{onError:n}))}n?.(i),s?.(i)},j=e=>{if(!y)return;let t=bo(e.nativeEvent);if(i&&(t&&e.button===0||!t)){let t=v.getState();Es.onPointerDown(e.nativeEvent,{handleDomNode:e.currentTarget,autoPanOnConnect:t.autoPanOnConnect,connectionMode:t.connectionMode,connectionRadius:t.connectionRadius,domNode:t.domNode,nodeLookup:t.nodeLookup,lib:t.lib,isTarget:_,handleId:g,nodeId:y,flowId:t.rfId,panBy:t.panBy,cancelConnection:t.cancelConnection,onConnectStart:t.onConnectStart,onConnectEnd:(...e)=>v.getState().onConnectEnd?.(...e),updateConnection:t.updateConnection,onConnect:A,isValidConnection:n||((...e)=>v.getState().isValidConnection?.(...e)??!0),getTransform:()=>v.getState().transform,getFromHandle:()=>v.getState().connection.fromHandle,autoPanSpeed:t.autoPanSpeed,dragThreshold:t.connectionDragThreshold})}t?u?.(e):d?.(e)};return(0,p.jsx)(`div`,{"data-handleid":g,"data-nodeid":y,"data-handlepos":t,"data-id":`${S}-${y}-${g}-${e}`,className:m([`react-flow__handle`,`react-flow__handle-${t}`,`nodrag`,x,l,{source:!_,target:_,connectable:r,connectablestart:i,connectableend:a,clickconnecting:T,connectingfrom:C,connectingto:w,valid:k,connectionindicator:r&&(!D||E)&&(D||O?a:i)}]),onMouseDown:j,onTouchStart:j,onClick:b?t=>{let{onClickConnectStart:r,onClickConnectEnd:a,connectionClickStartHandle:o,connectionMode:s,isValidConnection:c,lib:l,rfId:u,nodeLookup:d,connection:f}=v.getState();if(!y||!o&&!i)return;if(!o){r?.(t.nativeEvent,{nodeId:y,handleId:g,handleType:e}),v.setState({connectionClickStartHandle:{nodeId:y,type:e,id:g}});return}let p=_o(t.target),m=n||c,{connection:h,isValid:_}=Es.isValid(t.nativeEvent,{handle:{nodeId:y,id:g,type:e},connectionMode:s,fromNodeId:o.nodeId,fromHandleId:o.id||null,fromType:o.type,isValidConnection:m,flowId:u,doc:p,lib:l,nodeLookup:d});_&&h&&A(h);let b=structuredClone(f);delete b.inProgress,b.toPosition=b.toHandle?b.toHandle.position:null,a?.(t,b),v.setState({connectionClickStartHandle:null})}:void 0,ref:h,...f,children:c})}var Ll=(0,f.memo)(ol(Il));function Rl({data:e,isConnectable:t,sourcePosition:n=B.Bottom}){return(0,p.jsxs)(p.Fragment,{children:[e?.label,(0,p.jsx)(Ll,{type:`source`,position:n,isConnectable:t})]})}function zl({data:e,isConnectable:t,targetPosition:n=B.Top,sourcePosition:r=B.Bottom}){return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(Ll,{type:`target`,position:n,isConnectable:t}),e?.label,(0,p.jsx)(Ll,{type:`source`,position:r,isConnectable:t})]})}function Bl(){return null}function Vl({data:e,isConnectable:t,targetPosition:n=B.Top}){return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(Ll,{type:`target`,position:n,isConnectable:t}),e?.label]})}var Hl={ArrowUp:{x:0,y:-1},ArrowDown:{x:0,y:1},ArrowLeft:{x:-1,y:0},ArrowRight:{x:1,y:0}},Ul={input:Rl,default:zl,output:Vl,group:Bl};function U(e){return e.internals.handleBounds===void 0?{width:e.width??e.initialWidth??e.style?.width,height:e.height??e.initialHeight??e.style?.height}:{width:e.width??e.style?.width,height:e.height??e.style?.height}}var W=e=>{let{width:t,height:n,x:r,y:i}=Ma(e.nodeLookup,{filter:e=>!!e.selected});return{width:Qa(t)?t:null,height:Qa(n)?n:null,userSelectionActive:e.userSelectionActive,transformString:`translate(${e.transform[0]}px,${e.transform[1]}px) scale(${e.transform[2]}) translate(${r}px,${i}px)`}};function Wl({onSelectionContextMenu:e,noPanClassName:t,disableKeyboardA11y:n}){let r=_c(),{width:i,height:a,transformString:o,userSelectionActive:s}=H(W,fc),c=Al(),l=(0,f.useRef)(null);(0,f.useEffect)(()=>{n||l.current?.focus({preventScroll:!0})},[n]);let u=!s&&i!==null&&a!==null;if(Ol({nodeRef:l,disabled:!u}),!u)return null;let d=e?t=>{e(t,r.getState().nodes.filter(e=>e.selected))}:void 0;return(0,p.jsx)(`div`,{className:m([`react-flow__nodesselection`,`react-flow__container`,t]),style:{transform:o},children:(0,p.jsx)(`div`,{ref:l,className:`react-flow__nodesselection-rect`,onContextMenu:d,tabIndex:n?void 0:-1,onKeyDown:n?void 0:e=>{Object.prototype.hasOwnProperty.call(Hl,e.key)&&(e.preventDefault(),c({direction:Hl[e.key],factor:e.shiftKey?4:1}))},style:{width:i,height:a}})})}var Gl=typeof window<`u`?window:void 0,Kl=e=>({nodesSelectionActive:e.nodesSelectionActive,userSelectionActive:e.userSelectionActive});function ql({children:e,onPaneClick:t,onPaneMouseEnter:n,onPaneMouseMove:r,onPaneMouseLeave:i,onPaneContextMenu:a,onPaneScroll:o,paneClickDistance:s,deleteKeyCode:c,selectionKeyCode:l,selectionOnDrag:u,selectionMode:d,onSelectionStart:f,onSelectionEnd:m,multiSelectionKeyCode:h,panActivationKeyCode:g,zoomActivationKeyCode:_,elementsSelectable:v,zoomOnScroll:y,zoomOnPinch:b,panOnScroll:x,panOnScrollSpeed:S,panOnScrollMode:C,zoomOnDoubleClick:w,panOnDrag:T,autoPanOnSelection:E,defaultViewport:D,translateExtent:O,minZoom:k,maxZoom:A,preventScrolling:j,onSelectionContextMenu:M,noWheelClassName:N,noPanClassName:P,disableKeyboardA11y:F,onViewportChange:I,isControlledViewport:L}){let{nodesSelectionActive:R,userSelectionActive:ee}=H(Kl,fc),te=Wc(l,{target:Gl}),ne=Wc(g,{target:Gl}),z=ne||T,re=ne||x,ie=u&&z!==!0,ae=te||ee||ie;return _l({deleteKeyCode:c,multiSelectionKeyCode:h}),(0,p.jsx)(xl,{onPaneContextMenu:a,elementsSelectable:v,zoomOnScroll:y,zoomOnPinch:b,panOnScroll:re,panOnScrollSpeed:S,panOnScrollMode:C,zoomOnDoubleClick:w,panOnDrag:!te&&z,defaultViewport:D,translateExtent:O,minZoom:k,maxZoom:A,zoomActivationKeyCode:_,preventScrolling:j,noWheelClassName:N,noPanClassName:P,onViewportChange:I,isControlledViewport:L,paneClickDistance:s,selectionOnDrag:ie,children:(0,p.jsxs)(El,{onSelectionStart:f,onSelectionEnd:m,onPaneClick:t,onPaneMouseEnter:n,onPaneMouseMove:r,onPaneMouseLeave:i,onPaneContextMenu:a,onPaneScroll:o,panOnDrag:z,autoPanOnSelection:E,isSelecting:!!ae,selectionMode:d,selectionKeyPressed:te,paneClickDistance:s,selectionOnDrag:ie,children:[e,R&&(0,p.jsx)(Wl,{onSelectionContextMenu:M,noPanClassName:P,disableKeyboardA11y:F})]})})}ql.displayName=`FlowRenderer`;var Jl=(0,f.memo)(ql),Yl=e=>t=>e?Na(t.nodeLookup,{x:0,y:0,width:t.width,height:t.height},t.transform,!0).map(e=>e.id):Array.from(t.nodeLookup.keys());function Xl(e){return H((0,f.useCallback)(Yl(e),[e]),fc)}var Zl=e=>e.updateNodeInternals;function Ql(){let e=H(Zl),[t]=(0,f.useState)(()=>typeof ResizeObserver>`u`?null:new ResizeObserver(t=>{let n=new Map;t.forEach(e=>{let t=e.target.getAttribute(`data-id`);n.set(t,{id:t,nodeElement:e.target,force:!0})}),e(n)}));return(0,f.useEffect)(()=>()=>{t?.disconnect()},[t]),t}function $l({node:e,nodeType:t,hasDimensions:n,resizeObserver:r}){let i=_c(),a=(0,f.useRef)(null),o=(0,f.useRef)(null),s=(0,f.useRef)(e.sourcePosition),c=(0,f.useRef)(e.targetPosition),l=(0,f.useRef)(t),u=n&&!!e.internals.handleBounds;return(0,f.useEffect)(()=>{a.current&&!e.hidden&&(!u||o.current!==a.current)&&(o.current&&r?.unobserve(o.current),r?.observe(a.current),o.current=a.current)},[u,e.hidden]),(0,f.useEffect)(()=>()=>{o.current&&=(r?.unobserve(o.current),null)},[]),(0,f.useEffect)(()=>{if(a.current){let n=l.current!==t,r=s.current!==e.sourcePosition,o=c.current!==e.targetPosition;(n||r||o)&&(l.current=t,s.current=e.sourcePosition,c.current=e.targetPosition,i.getState().updateNodeInternals(new Map([[e.id,{id:e.id,nodeElement:a.current,force:!0}]])))}},[e.id,t,e.sourcePosition,e.targetPosition]),a}function eu({id:e,onClick:t,onMouseEnter:n,onMouseMove:r,onMouseLeave:i,onContextMenu:a,onDoubleClick:o,nodesDraggable:s,elementsSelectable:c,nodesConnectable:l,nodesFocusable:u,resizeObserver:d,noDragClassName:f,noPanClassName:h,disableKeyboardA11y:g,rfId:_,nodeTypes:v,nodeClickDistance:y,onError:b}){let{node:x,internals:S,isParent:C}=H(t=>{let n=t.nodeLookup.get(e),r=t.parentLookup.has(e);return{node:n,internals:n.internals,isParent:r}},fc),w=x.type||`default`,T=v?.[w]||Ul[w];T===void 0&&(b?.(`003`,pa.error003(w)),w=`default`,T=v?.default||Ul.default);let E=!!(x.draggable||s&&x.draggable===void 0),D=!!(x.selectable||c&&x.selectable===void 0),O=!!(x.connectable||l&&x.connectable===void 0),k=!!(x.focusable||u&&x.focusable===void 0),A=_c(),j=uo(x),M=$l({node:x,nodeType:w,hasDimensions:j,resizeObserver:d}),N=Ol({nodeRef:M,disabled:x.hidden||!E,noDragClassName:f,handleSelector:x.dragHandle,nodeId:e,isSelectable:D,nodeClickDistance:y}),P=Al();if(x.hidden)return null;let F=lo(x),I=U(x),L=D||E||t||n||r||i,R=n?e=>n(e,{...S.userNode}):void 0,ee=r?e=>r(e,{...S.userNode}):void 0,te=i?e=>i(e,{...S.userNode}):void 0,ne=a?e=>a(e,{...S.userNode}):void 0,z=o?e=>o(e,{...S.userNode}):void 0,re=n=>{let{selectNodesOnDrag:r,nodeDragThreshold:i}=A.getState();D&&(!r||!E||i>0)&&Dl({id:e,store:A,nodeRef:M}),t&&t(n,{...S.userNode})},ie=t=>{if(!(yo(t.nativeEvent)||g)){if(ha.includes(t.key)&&D){let n=t.key===`Escape`;Dl({id:e,store:A,unselect:n,nodeRef:M})}else if(E&&x.selected&&Object.prototype.hasOwnProperty.call(Hl,t.key)){t.preventDefault();let{ariaLabelConfig:e}=A.getState();A.setState({ariaLiveMessage:e[`node.a11yDescription.ariaLiveMessage`]({direction:t.key.replace(`Arrow`,``).toLowerCase(),x:~~S.positionAbsolute.x,y:~~S.positionAbsolute.y})}),P({direction:Hl[t.key],factor:t.shiftKey?4:1})}}},ae=()=>{if(g||!M.current?.matches(`:focus-visible`))return;let{transform:t,width:n,height:r,autoPanOnNodeFocus:i,setCenter:a}=A.getState();i&&(Na(new Map([[e,x]]),{x:0,y:0,width:n,height:r},t,!0).length>0||a(x.position.x+F.width/2,x.position.y+F.height/2,{zoom:t[2]}))};return(0,p.jsx)(`div`,{className:m([`react-flow__node`,`react-flow__node-${w}`,{[h]:E},x.className,{selected:x.selected,selectable:D,parent:C,draggable:E,dragging:N}]),ref:M,style:{zIndex:S.z,transform:`translate(${S.positionAbsolute.x}px,${S.positionAbsolute.y}px)`,pointerEvents:L?`all`:`none`,visibility:j?`visible`:`hidden`,...x.style,...I},"data-id":e,"data-testid":`rf__node-${e}`,onMouseEnter:R,onMouseMove:ee,onMouseLeave:te,onContextMenu:ne,onClick:re,onDoubleClick:z,onKeyDown:k?ie:void 0,tabIndex:k?0:void 0,onFocus:k?ae:void 0,role:x.ariaRole??(k?`group`:void 0),"aria-roledescription":`node`,"aria-describedby":g?void 0:`${bc}-${_}`,"aria-label":x.ariaLabel,...x.domAttributes,children:(0,p.jsx)(Ml,{value:e,children:(0,p.jsx)(T,{id:e,data:x.data,type:w,positionAbsoluteX:S.positionAbsolute.x,positionAbsoluteY:S.positionAbsolute.y,selected:x.selected??!1,selectable:D,draggable:E,deletable:x.deletable??!0,isConnectable:O,sourcePosition:x.sourcePosition,targetPosition:x.targetPosition,dragging:N,dragHandle:x.dragHandle,zIndex:S.z,parentId:x.parentId,...F})})})}var tu=(0,f.memo)(eu),nu=e=>({nodesDraggable:e.nodesDraggable,nodesConnectable:e.nodesConnectable,nodesFocusable:e.nodesFocusable,elementsSelectable:e.elementsSelectable,onError:e.onError});function ru(e){let{nodesDraggable:t,nodesConnectable:n,nodesFocusable:r,elementsSelectable:i,onError:a}=H(nu,fc),o=Xl(e.onlyRenderVisibleElements),s=Ql();return(0,p.jsx)(`div`,{className:`react-flow__nodes`,style:yl,children:o.map(o=>(0,p.jsx)(tu,{id:o,nodeTypes:e.nodeTypes,nodeExtent:e.nodeExtent,onClick:e.onNodeClick,onMouseEnter:e.onNodeMouseEnter,onMouseMove:e.onNodeMouseMove,onMouseLeave:e.onNodeMouseLeave,onContextMenu:e.onNodeContextMenu,onDoubleClick:e.onNodeDoubleClick,noDragClassName:e.noDragClassName,noPanClassName:e.noPanClassName,rfId:e.rfId,disableKeyboardA11y:e.disableKeyboardA11y,resizeObserver:s,nodesDraggable:t,nodesConnectable:n,nodesFocusable:r,elementsSelectable:i,nodeClickDistance:e.nodeClickDistance,onError:a},o))})}ru.displayName=`NodeRenderer`;var iu=(0,f.memo)(ru);function au(e){return H((0,f.useCallback)(t=>{if(!e)return t.edges.map(e=>e.id);let n=[];if(t.width&&t.height)for(let e of t.edges){let r=t.nodeLookup.get(e.source),i=t.nodeLookup.get(e.target);r&&i&&ko({sourceNode:r,targetNode:i,width:t.width,height:t.height,transform:t.transform})&&n.push(e.id)}return n},[e]),fc)}var ou=({color:e=`none`,strokeWidth:t=1})=>(0,p.jsx)(`polyline`,{className:`arrow`,style:{strokeWidth:t,...e&&{stroke:e}},strokeLinecap:`round`,fill:`none`,strokeLinejoin:`round`,points:`-5,-4 0,0 -5,4`}),su=({color:e=`none`,strokeWidth:t=1})=>(0,p.jsx)(`polyline`,{className:`arrowclosed`,style:{strokeWidth:t,...e&&{stroke:e,fill:e}},strokeLinecap:`round`,strokeLinejoin:`round`,points:`-5,-4 0,0 -5,4 -5,-4`}),cu={[Sa.Arrow]:ou,[Sa.ArrowClosed]:su};function lu(e){let t=_c();return(0,f.useMemo)(()=>Object.prototype.hasOwnProperty.call(cu,e)?cu[e]:(t.getState().onError?.(`009`,pa.error009(e)),null),[e])}var uu=({id:e,type:t,color:n,width:r=12.5,height:i=12.5,markerUnits:a=`strokeWidth`,strokeWidth:o,orient:s=`auto-start-reverse`})=>{let c=lu(t);return c?(0,p.jsx)(`marker`,{className:`react-flow__arrowhead`,id:e,markerWidth:`${r}`,markerHeight:`${i}`,viewBox:`-10 -10 20 20`,markerUnits:a,orient:s,refX:`0`,refY:`0`,children:(0,p.jsx)(c,{color:n,strokeWidth:o})}):null},du=({defaultColor:e,rfId:t})=>{let n=H(e=>e.edges),r=H(e=>e.defaultEdgeOptions),i=(0,f.useMemo)(()=>Ko(n,{id:t,defaultColor:e,defaultMarkerStart:r?.markerStart,defaultMarkerEnd:r?.markerEnd}),[n,r,t,e]);return i.length?(0,p.jsx)(`svg`,{className:`react-flow__marker`,"aria-hidden":`true`,children:(0,p.jsx)(`defs`,{children:i.map(e=>(0,p.jsx)(uu,{id:e.id,type:e.type,color:e.color,width:e.width,height:e.height,markerUnits:e.markerUnits,strokeWidth:e.strokeWidth,orient:e.orient},e.id))})}):null};du.displayName=`MarkerDefinitions`;var fu=(0,f.memo)(du);function pu({x:e,y:t,label:n,labelStyle:r,labelShowBg:i=!0,labelBgStyle:a,labelBgPadding:o=[2,4],labelBgBorderRadius:s=2,children:c,className:l,...u}){let[d,h]=(0,f.useState)({x:1,y:0,width:0,height:0}),g=m([`react-flow__edge-textwrapper`,l]),_=(0,f.useRef)(null);return(0,f.useEffect)(()=>{if(_.current){let e=_.current.getBBox();h({x:e.x,y:e.y,width:e.width,height:e.height})}},[n]),n?(0,p.jsxs)(`g`,{transform:`translate(${e-d.width/2} ${t-d.height/2})`,className:g,visibility:d.width?`visible`:`hidden`,...u,children:[i&&(0,p.jsx)(`rect`,{width:d.width+2*o[0],x:-o[0],y:-o[1],height:d.height+2*o[1],className:`react-flow__edge-textbg`,style:a,rx:s,ry:s}),(0,p.jsx)(`text`,{className:`react-flow__edge-text`,y:d.height/2,dy:`0.3em`,ref:_,style:r,children:n}),c]}):null}pu.displayName=`EdgeText`;var mu=(0,f.memo)(pu);function hu({path:e,labelX:t,labelY:n,label:r,labelStyle:i,labelShowBg:a,labelBgStyle:o,labelBgPadding:s,labelBgBorderRadius:c,interactionWidth:l=20,...u}){return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(`path`,{...u,d:e,fill:`none`,className:m([`react-flow__edge-path`,u.className])}),l?(0,p.jsx)(`path`,{d:e,fill:`none`,strokeOpacity:0,strokeWidth:l,className:`react-flow__edge-interaction`}):null,r&&Qa(t)&&Qa(n)?(0,p.jsx)(mu,{x:t,y:n,label:r,labelStyle:i,labelShowBg:a,labelBgStyle:o,labelBgPadding:s,labelBgBorderRadius:c}):null]})}function gu({pos:e,x1:t,y1:n,x2:r,y2:i}){return e===B.Left||e===B.Right?[.5*(t+r),n]:[t,.5*(n+i)]}function _u({sourceX:e,sourceY:t,sourcePosition:n=B.Bottom,targetX:r,targetY:i,targetPosition:a=B.Top}){let[o,s]=gu({pos:n,x1:e,y1:t,x2:r,y2:i}),[c,l]=gu({pos:a,x1:r,y1:i,x2:e,y2:t}),[u,d,f,p]=Co({sourceX:e,sourceY:t,targetX:r,targetY:i,sourceControlX:o,sourceControlY:s,targetControlX:c,targetControlY:l});return[`M${e},${t} C${o},${s} ${c},${l} ${r},${i}`,u,d,f,p]}function vu(e){return(0,f.memo)(({id:t,sourceX:n,sourceY:r,targetX:i,targetY:a,sourcePosition:o,targetPosition:s,label:c,labelStyle:l,labelShowBg:u,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:m,style:h,markerEnd:g,markerStart:_,interactionWidth:v})=>{let[y,b,x]=_u({sourceX:n,sourceY:r,sourcePosition:o,targetX:i,targetY:a,targetPosition:s});return(0,p.jsx)(hu,{id:e.isInternal?void 0:t,path:y,labelX:b,labelY:x,label:c,labelStyle:l,labelShowBg:u,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:m,style:h,markerEnd:g,markerStart:_,interactionWidth:v})})}var yu=vu({isInternal:!1}),bu=vu({isInternal:!0});yu.displayName=`SimpleBezierEdge`,bu.displayName=`SimpleBezierEdgeInternal`;function xu(e){return(0,f.memo)(({id:t,sourceX:n,sourceY:r,targetX:i,targetY:a,label:o,labelStyle:s,labelShowBg:c,labelBgStyle:l,labelBgPadding:u,labelBgBorderRadius:d,style:f,sourcePosition:m=B.Bottom,targetPosition:h=B.Top,markerEnd:g,markerStart:_,pathOptions:v,interactionWidth:y})=>{let[b,x,S]=zo({sourceX:n,sourceY:r,sourcePosition:m,targetX:i,targetY:a,targetPosition:h,borderRadius:v?.borderRadius,offset:v?.offset,stepPosition:v?.stepPosition});return(0,p.jsx)(hu,{id:e.isInternal?void 0:t,path:b,labelX:x,labelY:S,label:o,labelStyle:s,labelShowBg:c,labelBgStyle:l,labelBgPadding:u,labelBgBorderRadius:d,style:f,markerEnd:g,markerStart:_,interactionWidth:y})})}var Su=xu({isInternal:!1}),Cu=xu({isInternal:!0});Su.displayName=`SmoothStepEdge`,Cu.displayName=`SmoothStepEdgeInternal`;function wu(e){return(0,f.memo)(({id:t,...n})=>{let r=e.isInternal?void 0:t;return(0,p.jsx)(Su,{...n,id:r,pathOptions:(0,f.useMemo)(()=>({borderRadius:0,offset:n.pathOptions?.offset}),[n.pathOptions?.offset])})})}var Tu=wu({isInternal:!1}),Eu=wu({isInternal:!0});Tu.displayName=`StepEdge`,Eu.displayName=`StepEdgeInternal`;function Du(e){return(0,f.memo)(({id:t,sourceX:n,sourceY:r,targetX:i,targetY:a,label:o,labelStyle:s,labelShowBg:c,labelBgStyle:l,labelBgPadding:u,labelBgBorderRadius:d,style:f,markerEnd:m,markerStart:h,interactionWidth:g})=>{let[_,v,y]=No({sourceX:n,sourceY:r,targetX:i,targetY:a});return(0,p.jsx)(hu,{id:e.isInternal?void 0:t,path:_,labelX:v,labelY:y,label:o,labelStyle:s,labelShowBg:c,labelBgStyle:l,labelBgPadding:u,labelBgBorderRadius:d,style:f,markerEnd:m,markerStart:h,interactionWidth:g})})}var Ou=Du({isInternal:!1}),ku=Du({isInternal:!0});Ou.displayName=`StraightEdge`,ku.displayName=`StraightEdgeInternal`;function Au(e){return(0,f.memo)(({id:t,sourceX:n,sourceY:r,targetX:i,targetY:a,sourcePosition:o=B.Bottom,targetPosition:s=B.Top,label:c,labelStyle:l,labelShowBg:u,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:m,style:h,markerEnd:g,markerStart:_,pathOptions:v,interactionWidth:y})=>{let[b,x,S]=Eo({sourceX:n,sourceY:r,sourcePosition:o,targetX:i,targetY:a,targetPosition:s,curvature:v?.curvature});return(0,p.jsx)(hu,{id:e.isInternal?void 0:t,path:b,labelX:x,labelY:S,label:c,labelStyle:l,labelShowBg:u,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:m,style:h,markerEnd:g,markerStart:_,interactionWidth:y})})}var ju=Au({isInternal:!1}),Mu=Au({isInternal:!0});ju.displayName=`BezierEdge`,Mu.displayName=`BezierEdgeInternal`;var Nu={default:Mu,straight:ku,step:Eu,smoothstep:Cu,simplebezier:bu},Pu={sourceX:null,sourceY:null,targetX:null,targetY:null,sourcePosition:null,targetPosition:null},Fu=(e,t,n)=>n===B.Left?e-t:n===B.Right?e+t:e,Iu=(e,t,n)=>n===B.Top?e-t:n===B.Bottom?e+t:e,Lu=`react-flow__edgeupdater`;function Ru({position:e,centerX:t,centerY:n,radius:r=10,onMouseDown:i,onMouseEnter:a,onMouseOut:o,type:s}){return(0,p.jsx)(`circle`,{onMouseDown:i,onMouseEnter:a,onMouseOut:o,className:m([Lu,`${Lu}-${s}`]),cx:Fu(t,r,e),cy:Iu(n,r,e),r,stroke:`transparent`,fill:`transparent`})}function zu({isReconnectable:e,reconnectRadius:t,edge:n,sourceX:r,sourceY:i,targetX:a,targetY:o,sourcePosition:s,targetPosition:c,onReconnect:l,onReconnectStart:u,onReconnectEnd:d,setReconnecting:f,setUpdateHover:m}){let h=_c(),g=(e,t)=>{if(e.button!==0)return;let{autoPanOnConnect:r,domNode:i,connectionMode:a,connectionRadius:o,lib:s,onConnectStart:c,cancelConnection:p,nodeLookup:m,rfId:g,panBy:_,updateConnection:v}=h.getState(),y=t.type===`target`;Es.onPointerDown(e.nativeEvent,{autoPanOnConnect:r,connectionMode:a,connectionRadius:o,domNode:i,handleId:t.id,nodeId:t.nodeId,nodeLookup:m,isTarget:y,edgeUpdaterType:t.type,lib:s,flowId:g,cancelConnection:p,panBy:_,isValidConnection:(...e)=>h.getState().isValidConnection?.(...e)??!0,onConnect:e=>l?.(n,e),onConnectStart:(r,i)=>{f(!0),u?.(e,n,t.type),c?.(r,i)},onConnectEnd:(...e)=>h.getState().onConnectEnd?.(...e),onReconnectEnd:(e,r)=>{f(!1),d?.(e,n,t.type,r)},updateConnection:v,getTransform:()=>h.getState().transform,getFromHandle:()=>h.getState().connection.fromHandle,dragThreshold:h.getState().connectionDragThreshold,handleDomNode:e.currentTarget})},_=e=>g(e,{nodeId:n.target,id:n.targetHandle??null,type:`target`}),v=e=>g(e,{nodeId:n.source,id:n.sourceHandle??null,type:`source`}),y=()=>m(!0),b=()=>m(!1);return(0,p.jsxs)(p.Fragment,{children:[(e===!0||e===`source`)&&(0,p.jsx)(Ru,{position:s,centerX:r,centerY:i,radius:t,onMouseDown:_,onMouseEnter:y,onMouseOut:b,type:`source`}),(e===!0||e===`target`)&&(0,p.jsx)(Ru,{position:c,centerX:a,centerY:o,radius:t,onMouseDown:v,onMouseEnter:y,onMouseOut:b,type:`target`})]})}function Bu({id:e,edgesFocusable:t,edgesReconnectable:n,elementsSelectable:r,onClick:i,onDoubleClick:a,onContextMenu:o,onMouseEnter:s,onMouseMove:c,onMouseLeave:l,reconnectRadius:u,onReconnect:d,onReconnectStart:h,onReconnectEnd:g,rfId:_,edgeTypes:v,noPanClassName:y,onError:b,disableKeyboardA11y:x}){let S=H(t=>t.edgeLookup.get(e)),C=H(e=>e.defaultEdgeOptions);S=C?{...C,...S}:S;let w=S.type||`default`,T=v?.[w]||Nu[w];T===void 0&&(b?.(`011`,pa.error011(w)),w=`default`,T=v?.default||Nu.default);let E=!!(S.focusable||t&&S.focusable===void 0),D=d!==void 0&&(S.reconnectable||n&&S.reconnectable===void 0),O=!!(S.selectable||r&&S.selectable===void 0),k=(0,f.useRef)(null),[A,j]=(0,f.useState)(!1),[M,N]=(0,f.useState)(!1),P=_c(),{zIndex:F,sourceX:I,sourceY:L,targetX:R,targetY:ee,sourcePosition:te,targetPosition:ne}=H((0,f.useCallback)(t=>{let n=t.nodeLookup.get(S.source),r=t.nodeLookup.get(S.target);if(!n||!r)return{zIndex:S.zIndex,...Pu};let i=Vo({id:e,sourceNode:n,targetNode:r,sourceHandle:S.sourceHandle||null,targetHandle:S.targetHandle||null,connectionMode:t.connectionMode,onError:b});return{zIndex:Oo({selected:S.selected,zIndex:S.zIndex,sourceNode:n,targetNode:r,elevateOnSelect:t.elevateEdgesOnSelect,zIndexMode:t.zIndexMode}),...i||Pu}},[S.source,S.target,S.sourceHandle,S.targetHandle,S.selected,S.zIndex]),fc),z=(0,f.useMemo)(()=>S.markerStart?`url('#${Go(S.markerStart,_)}')`:void 0,[S.markerStart,_]),re=(0,f.useMemo)(()=>S.markerEnd?`url('#${Go(S.markerEnd,_)}')`:void 0,[S.markerEnd,_]);if(S.hidden||I===null||L===null||R===null||ee===null)return null;let ie=t=>{let{addSelectedEdges:n,unselectNodesAndEdges:r,multiSelectionActive:a}=P.getState();O&&(P.setState({nodesSelectionActive:!1}),S.selected&&a?(r({nodes:[],edges:[S]}),k.current?.blur()):n([e])),i&&i(t,S)},ae=a?e=>{a(e,{...S})}:void 0,oe=o?e=>{o(e,{...S})}:void 0,se=s?e=>{s(e,{...S})}:void 0,ce=c?e=>{c(e,{...S})}:void 0,le=l?e=>{l(e,{...S})}:void 0;return(0,p.jsx)(`svg`,{style:{zIndex:F},children:(0,p.jsxs)(`g`,{className:m([`react-flow__edge`,`react-flow__edge-${w}`,S.className,y,{selected:S.selected,animated:S.animated,inactive:!O&&!i,updating:A,selectable:O}]),onClick:ie,onDoubleClick:ae,onContextMenu:oe,onMouseEnter:se,onMouseMove:ce,onMouseLeave:le,onKeyDown:E?t=>{if(!x&&ha.includes(t.key)&&O){let{unselectNodesAndEdges:n,addSelectedEdges:r}=P.getState();t.key===`Escape`?(k.current?.blur(),n({edges:[S]})):r([e])}}:void 0,tabIndex:E?0:void 0,role:S.ariaRole??(E?`group`:`img`),"aria-roledescription":`edge`,"data-id":e,"data-testid":`rf__edge-${e}`,"aria-label":S.ariaLabel===null?void 0:S.ariaLabel||`Edge from ${S.source} to ${S.target}`,"aria-describedby":E?`${xc}-${_}`:void 0,ref:k,...S.domAttributes,children:[!M&&(0,p.jsx)(T,{id:e,source:S.source,target:S.target,type:S.type,selected:S.selected,animated:S.animated,selectable:O,deletable:S.deletable??!0,label:S.label,labelStyle:S.labelStyle,labelShowBg:S.labelShowBg,labelBgStyle:S.labelBgStyle,labelBgPadding:S.labelBgPadding,labelBgBorderRadius:S.labelBgBorderRadius,sourceX:I,sourceY:L,targetX:R,targetY:ee,sourcePosition:te,targetPosition:ne,data:S.data,style:S.style,sourceHandleId:S.sourceHandle,targetHandleId:S.targetHandle,markerStart:z,markerEnd:re,pathOptions:`pathOptions`in S?S.pathOptions:void 0,interactionWidth:S.interactionWidth}),D&&(0,p.jsx)(zu,{edge:S,isReconnectable:D,reconnectRadius:u,onReconnect:d,onReconnectStart:h,onReconnectEnd:g,sourceX:I,sourceY:L,targetX:R,targetY:ee,sourcePosition:te,targetPosition:ne,setUpdateHover:j,setReconnecting:N})]})})}var Vu=(0,f.memo)(Bu),Hu=e=>({edgesFocusable:e.edgesFocusable,edgesReconnectable:e.edgesReconnectable,elementsSelectable:e.elementsSelectable,connectionMode:e.connectionMode,onError:e.onError});function Uu({defaultMarkerColor:e,onlyRenderVisibleElements:t,rfId:n,edgeTypes:r,noPanClassName:i,onReconnect:a,onEdgeContextMenu:o,onEdgeMouseEnter:s,onEdgeMouseMove:c,onEdgeMouseLeave:l,onEdgeClick:u,reconnectRadius:d,onEdgeDoubleClick:f,onReconnectStart:m,onReconnectEnd:h,disableKeyboardA11y:g}){let{edgesFocusable:_,edgesReconnectable:v,elementsSelectable:y,onError:b}=H(Hu,fc),x=au(t);return(0,p.jsxs)(`div`,{className:`react-flow__edges`,children:[(0,p.jsx)(fu,{defaultColor:e,rfId:n}),x.map(e=>(0,p.jsx)(Vu,{id:e,edgesFocusable:_,edgesReconnectable:v,elementsSelectable:y,noPanClassName:i,onReconnect:a,onContextMenu:o,onMouseEnter:s,onMouseMove:c,onMouseLeave:l,onClick:u,reconnectRadius:d,onDoubleClick:f,onReconnectStart:m,onReconnectEnd:h,rfId:n,onError:b,edgeTypes:r,disableKeyboardA11y:g},e))]})}Uu.displayName=`EdgeRenderer`;var Wu=(0,f.memo)(Uu),Gu=e=>`translate(${e.transform[0]}px,${e.transform[1]}px) scale(${e.transform[2]})`;function Ku({children:e}){return(0,p.jsx)(`div`,{className:`react-flow__viewport xyflow__viewport react-flow__container`,style:{transform:H(Gu)},children:e})}function qu(e){let t=ml(),n=(0,f.useRef)(!1);(0,f.useEffect)(()=>{!n.current&&t.viewportInitialized&&e&&(setTimeout(()=>e(t),1),n.current=!0)},[e,t.viewportInitialized])}var Ju=e=>e.panZoom?.syncViewport;function Yu(e){let t=H(Ju),n=_c();return(0,f.useEffect)(()=>{e&&(t?.(e),n.setState({transform:[e.x,e.y,e.zoom]}))},[e,t]),null}function Xu(e){return e.connection.inProgress?{...e.connection,to:to(e.connection.to,e.transform)}:{...e.connection}}function Zu(e){return e?t=>e(Xu(t)):Xu}function Qu(e){return H(Zu(e),fc)}var $u=e=>({nodesConnectable:e.nodesConnectable,isValid:e.connection.isValid,inProgress:e.connection.inProgress,width:e.width,height:e.height});function ed({containerStyle:e,style:t,type:n,component:r}){let{nodesConnectable:i,width:a,height:o,isValid:s,inProgress:c}=H($u,fc);return a&&i&&c?(0,p.jsx)(`svg`,{style:e,width:a,height:o,className:`react-flow__connectionline react-flow__container`,children:(0,p.jsx)(`g`,{className:m([`react-flow__connection`,wa(s)]),children:(0,p.jsx)(td,{style:t,type:n,CustomComponent:r,isValid:s})})}):null}var td=({style:e,type:t=xa.Bezier,CustomComponent:n,isValid:r})=>{let{inProgress:i,from:a,fromNode:o,fromHandle:s,fromPosition:c,to:l,toNode:u,toHandle:d,toPosition:f,pointer:m}=Qu();if(!i)return;if(n)return(0,p.jsx)(n,{connectionLineType:t,connectionLineStyle:e,fromNode:o,fromHandle:s,fromX:a.x,fromY:a.y,toX:l.x,toY:l.y,fromPosition:c,toPosition:f,connectionStatus:wa(r),toNode:u,toHandle:d,pointer:m});let h=``,g={sourceX:a.x,sourceY:a.y,sourcePosition:c,targetX:l.x,targetY:l.y,targetPosition:f};switch(t){case xa.Bezier:[h]=Eo(g);break;case xa.SimpleBezier:[h]=_u(g);break;case xa.Step:[h]=zo({...g,borderRadius:0});break;case xa.SmoothStep:[h]=zo(g);break;default:[h]=No(g)}return(0,p.jsx)(`path`,{d:h,fill:`none`,className:`react-flow__connection-path`,style:e})};td.displayName=`ConnectionLine`;var nd={};function rd(e=nd){(0,f.useRef)(e),_c(),(0,f.useEffect)(()=>{},[e])}function id(){_c(),(0,f.useRef)(!1),(0,f.useEffect)(()=>{},[])}function ad({nodeTypes:e,edgeTypes:t,onInit:n,onNodeClick:r,onEdgeClick:i,onNodeDoubleClick:a,onEdgeDoubleClick:o,onNodeMouseEnter:s,onNodeMouseMove:c,onNodeMouseLeave:l,onNodeContextMenu:u,onSelectionContextMenu:d,onSelectionStart:f,onSelectionEnd:m,connectionLineType:h,connectionLineStyle:g,connectionLineComponent:_,connectionLineContainerStyle:v,selectionKeyCode:y,selectionOnDrag:b,selectionMode:x,multiSelectionKeyCode:S,panActivationKeyCode:C,zoomActivationKeyCode:w,deleteKeyCode:T,onlyRenderVisibleElements:E,elementsSelectable:D,defaultViewport:O,translateExtent:k,minZoom:A,maxZoom:j,preventScrolling:M,defaultMarkerColor:N,zoomOnScroll:P,zoomOnPinch:F,panOnScroll:I,panOnScrollSpeed:L,panOnScrollMode:R,zoomOnDoubleClick:ee,panOnDrag:te,autoPanOnSelection:ne,onPaneClick:z,onPaneMouseEnter:re,onPaneMouseMove:ie,onPaneMouseLeave:ae,onPaneScroll:oe,onPaneContextMenu:se,paneClickDistance:ce,nodeClickDistance:le,onEdgeContextMenu:ue,onEdgeMouseEnter:de,onEdgeMouseMove:fe,onEdgeMouseLeave:pe,reconnectRadius:me,onReconnect:he,onReconnectStart:ge,onReconnectEnd:_e,noDragClassName:ve,noWheelClassName:ye,noPanClassName:be,disableKeyboardA11y:xe,nodeExtent:Se,rfId:Ce,viewport:we,onViewportChange:Te}){return rd(e),rd(t),id(),qu(n),Yu(we),(0,p.jsx)(Jl,{onPaneClick:z,onPaneMouseEnter:re,onPaneMouseMove:ie,onPaneMouseLeave:ae,onPaneContextMenu:se,onPaneScroll:oe,paneClickDistance:ce,deleteKeyCode:T,selectionKeyCode:y,selectionOnDrag:b,selectionMode:x,onSelectionStart:f,onSelectionEnd:m,multiSelectionKeyCode:S,panActivationKeyCode:C,zoomActivationKeyCode:w,elementsSelectable:D,zoomOnScroll:P,zoomOnPinch:F,zoomOnDoubleClick:ee,panOnScroll:I,panOnScrollSpeed:L,panOnScrollMode:R,panOnDrag:te,autoPanOnSelection:ne,defaultViewport:O,translateExtent:k,minZoom:A,maxZoom:j,onSelectionContextMenu:d,preventScrolling:M,noDragClassName:ve,noWheelClassName:ye,noPanClassName:be,disableKeyboardA11y:xe,onViewportChange:Te,isControlledViewport:!!we,children:(0,p.jsxs)(Ku,{children:[(0,p.jsx)(Wu,{edgeTypes:t,onEdgeClick:i,onEdgeDoubleClick:o,onReconnect:he,onReconnectStart:ge,onReconnectEnd:_e,onlyRenderVisibleElements:E,onEdgeContextMenu:ue,onEdgeMouseEnter:de,onEdgeMouseMove:fe,onEdgeMouseLeave:pe,reconnectRadius:me,defaultMarkerColor:N,noPanClassName:be,disableKeyboardA11y:xe,rfId:Ce}),(0,p.jsx)(ed,{style:g,type:h,component:_,containerStyle:v}),(0,p.jsx)(`div`,{className:`react-flow__edgelabel-renderer`}),(0,p.jsx)(iu,{nodeTypes:e,onNodeClick:r,onNodeDoubleClick:a,onNodeMouseEnter:s,onNodeMouseMove:c,onNodeMouseLeave:l,onNodeContextMenu:u,nodeClickDistance:le,onlyRenderVisibleElements:E,noPanClassName:be,noDragClassName:ve,disableKeyboardA11y:xe,nodeExtent:Se,rfId:Ce}),(0,p.jsx)(`div`,{className:`react-flow__viewport-portal`})]})})}ad.displayName=`GraphView`;var od=(0,f.memo)(ad),sd=$a(`React Flow`,`https://reactflow.dev/`),cd=({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,width:i,height:a,fitView:o,fitViewOptions:s,minZoom:c=.5,maxZoom:l=2,nodeOrigin:u,nodeExtent:d,zIndexMode:f=`basic`}={})=>{let p=new Map,m=new Map,h=new Map,g=new Map,_=r??t??[],v=n??e??[],y=u??[0,0],b=d??ma;us(h,g,_);let{nodesInitialized:x}=ts(v,p,m,{nodeOrigin:y,nodeExtent:b,zIndexMode:f}),S=[0,0,1];if(o&&i&&a){let{x:e,y:t,zoom:n}=oo(Ma(p,{filter:e=>!!((e.width||e.initialWidth)&&(e.height||e.initialHeight))}),i,a,c,l,s?.padding??.1);S=[e,t,n]}return{rfId:`1`,width:i??0,height:a??0,transform:S,nodes:v,nodesInitialized:x,nodeLookup:p,parentLookup:m,edges:_,edgeLookup:g,connectionLookup:h,onNodesChange:null,onEdgesChange:null,hasDefaultNodes:n!==void 0,hasDefaultEdges:r!==void 0,panZoom:null,minZoom:c,maxZoom:l,translateExtent:ma,nodeExtent:b,nodesSelectionActive:!1,userSelectionActive:!1,userSelectionRect:null,connectionMode:_a.Strict,domNode:null,paneDragging:!1,noPanClassName:`nopan`,nodeOrigin:y,nodeDragThreshold:1,connectionDragThreshold:1,snapGrid:[15,15],snapToGrid:!1,nodesDraggable:!0,nodesConnectable:!0,nodesFocusable:!0,edgesFocusable:!0,edgesReconnectable:!0,elementsSelectable:!0,elevateNodesOnSelect:!0,elevateEdgesOnSelect:!0,selectNodesOnDrag:!0,multiSelectionActive:!1,fitViewQueued:o??!1,fitViewOptions:s,fitViewResolver:null,connection:{...ba},connectionClickStartHandle:null,connectOnClick:!0,ariaLiveMessage:``,autoPanOnConnect:!0,autoPanOnNodeDrag:!0,autoPanOnNodeFocus:!0,autoPanSpeed:15,connectionRadius:20,onError:sd,isValidConnection:void 0,onSelectionChangeHandlers:[],lib:`react`,debug:!1,ariaLabelConfig:ga,zIndexMode:f,onNodesChangeMiddlewareMap:new Map,onEdgesChangeMiddlewareMap:new Map}},ld=({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,width:i,height:a,fitView:o,fitViewOptions:s,minZoom:c,maxZoom:l,nodeOrigin:u,nodeExtent:d,zIndexMode:f})=>dc((p,m)=>{async function h(){let{nodeLookup:e,panZoom:t,fitViewOptions:n,fitViewResolver:r,width:i,height:a,minZoom:o,maxZoom:s}=m();t&&(await Ia({nodes:e,width:i,height:a,panZoom:t,minZoom:o,maxZoom:s},n),r?.resolve(!0),p({fitViewResolver:null}))}return{...cd({nodes:e,edges:t,width:i,height:a,fitView:o,fitViewOptions:s,minZoom:c,maxZoom:l,nodeOrigin:u,nodeExtent:d,defaultNodes:n,defaultEdges:r,zIndexMode:f}),setNodes:e=>{let{nodeLookup:t,parentLookup:n,nodeOrigin:r,elevateNodesOnSelect:i,fitViewQueued:a,zIndexMode:o,nodesSelectionActive:s}=m(),{nodesInitialized:c,hasSelectedNodes:l}=ts(e,t,n,{nodeOrigin:r,nodeExtent:d,elevateNodesOnSelect:i,checkEquality:!0,zIndexMode:o}),u=s&&l;a&&c?(h(),p({nodes:e,nodesInitialized:c,fitViewQueued:!1,fitViewOptions:void 0,nodesSelectionActive:u})):p({nodes:e,nodesInitialized:c,nodesSelectionActive:u})},setEdges:e=>{let{connectionLookup:t,edgeLookup:n}=m();us(t,n,e),p({edges:e})},setDefaultNodesAndEdges:(e,t)=>{if(e){let{setNodes:t}=m();t(e),p({hasDefaultNodes:!0})}if(t){let{setEdges:e}=m();e(t),p({hasDefaultEdges:!0})}},updateNodeInternals:e=>{let{triggerNodeChanges:t,nodeLookup:n,parentLookup:r,domNode:i,nodeOrigin:a,nodeExtent:o,debug:s,fitViewQueued:c,zIndexMode:l}=m(),{changes:u,updatedInternals:d}=ss(e,n,r,i,a,o,l);d&&(Qo(n,r,{nodeOrigin:a,nodeExtent:o,zIndexMode:l}),c?(h(),p({fitViewQueued:!1,fitViewOptions:void 0})):p({}),u?.length>0&&(s&&console.log(`React Flow: trigger node changes`,u),t?.(u)))},updateNodePositions:(e,t=!1)=>{let n=[],r=[],{nodeLookup:i,triggerNodeChanges:a,connection:o,updateConnection:s,onNodesChangeMiddlewareMap:c}=m();for(let[a,c]of e){let e=i.get(a),l=!!(e?.expandParent&&e?.parentId&&c?.position),u={id:a,type:`position`,position:l?{x:Math.max(0,c.position.x),y:Math.max(0,c.position.y)}:c.position,dragging:t};if(e&&o.inProgress&&o.fromNode.id===e.id){let t=Uo(e,o.fromHandle,B.Left,!0);s({...o,from:t})}l&&e.parentId&&n.push({id:a,parentId:e.parentId,rect:{...c.internals.positionAbsolute,width:c.measured.width??0,height:c.measured.height??0}}),r.push(u)}if(n.length>0){let{parentLookup:e,nodeOrigin:t}=m(),a=os(n,i,e,t);r.push(...a)}for(let e of c.values())r=e(r);a(r)},triggerNodeChanges:e=>{let{onNodesChange:t,setNodes:n,nodes:r,hasDefaultNodes:i,debug:a}=m();e?.length&&(i&&n(Xc(e,r)),a&&console.log(`React Flow: trigger node changes`,e),t?.(e))},triggerEdgeChanges:e=>{let{onEdgesChange:t,setEdges:n,edges:r,hasDefaultEdges:i,debug:a}=m();e?.length&&(i&&n(Zc(e,r)),a&&console.log(`React Flow: trigger edge changes`,e),t?.(e))},addSelectedNodes:e=>{let{multiSelectionActive:t,edgeLookup:n,nodeLookup:r,triggerNodeChanges:i,triggerEdgeChanges:a}=m();if(t){i(e.map(e=>Qc(e,!0)));return}i($c(r,new Set([...e]),!0)),a($c(n))},addSelectedEdges:e=>{let{multiSelectionActive:t,edgeLookup:n,nodeLookup:r,triggerNodeChanges:i,triggerEdgeChanges:a}=m();if(t){a(e.map(e=>Qc(e,!0)));return}a($c(n,new Set([...e]))),i($c(r,new Set,!0))},unselectNodesAndEdges:({nodes:e,edges:t}={})=>{let{edges:n,nodes:r,nodeLookup:i,triggerNodeChanges:a,triggerEdgeChanges:o}=m(),s=e||r,c=t||n,l=[];for(let e of s){if(!e.selected)continue;let t=i.get(e.id);t&&(t.selected=!1),l.push(Qc(e.id,!1))}let u=[];for(let e of c)e.selected&&u.push(Qc(e.id,!1));a(l),o(u)},setMinZoom:e=>{let{panZoom:t,maxZoom:n}=m();t?.setScaleExtent([e,n]),p({minZoom:e})},setMaxZoom:e=>{let{panZoom:t,minZoom:n}=m();t?.setScaleExtent([n,e]),p({maxZoom:e})},setTranslateExtent:e=>{m().panZoom?.setTranslateExtent(e),p({translateExtent:e})},resetSelectedElements:()=>{let{edges:e,nodes:t,triggerNodeChanges:n,triggerEdgeChanges:r,elementsSelectable:i}=m();if(!i)return;let a=t.reduce((e,t)=>t.selected?[...e,Qc(t.id,!1)]:e,[]),o=e.reduce((e,t)=>t.selected?[...e,Qc(t.id,!1)]:e,[]);n(a),r(o)},setNodeExtent:e=>{let{nodes:t,nodeLookup:n,parentLookup:r,nodeOrigin:i,elevateNodesOnSelect:a,nodeExtent:o,zIndexMode:s}=m();e[0][0]===o[0][0]&&e[0][1]===o[0][1]&&e[1][0]===o[1][0]&&e[1][1]===o[1][1]||(ts(t,n,r,{nodeOrigin:i,nodeExtent:e,elevateNodesOnSelect:a,checkEquality:!1,zIndexMode:s}),p({nodeExtent:e}))},panBy:e=>{let{transform:t,width:n,height:r,panZoom:i,translateExtent:a}=m();return cs({delta:e,panZoom:i,transform:t,translateExtent:a,width:n,height:r})},setCenter:async(e,t,n)=>{let{width:r,height:i,maxZoom:a,panZoom:o}=m();if(!o)return!1;let s=n?.zoom===void 0?a:n.zoom;return await o.setViewport({x:r/2-e*s,y:i/2-t*s,zoom:s},{duration:n?.duration,ease:n?.ease,interpolate:n?.interpolate}),!0},cancelConnection:()=>{p({connection:{...ba}})},updateConnection:e=>{p({connection:e})},reset:()=>p({...cd()})}},Object.is);function ud({initialNodes:e,initialEdges:t,defaultNodes:n,defaultEdges:r,initialWidth:i,initialHeight:a,initialMinZoom:o,initialMaxZoom:s,initialFitViewOptions:c,fitView:l,nodeOrigin:u,nodeExtent:d,zIndexMode:m,children:h}){let[g]=(0,f.useState)(()=>ld({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,width:i,height:a,fitView:l,minZoom:o,maxZoom:s,fitViewOptions:c,nodeOrigin:u,nodeExtent:d,zIndexMode:m}));return(0,p.jsx)(hc,{value:g,children:(0,p.jsx)(dl,{children:h})})}function dd({children:e,nodes:t,edges:n,defaultNodes:r,defaultEdges:i,width:a,height:o,fitView:s,fitViewOptions:c,minZoom:l,maxZoom:u,nodeOrigin:d,nodeExtent:m,zIndexMode:h}){return(0,f.useContext)(mc)?(0,p.jsx)(p.Fragment,{children:e}):(0,p.jsx)(ud,{initialNodes:t,initialEdges:n,defaultNodes:r,defaultEdges:i,initialWidth:a,initialHeight:o,fitView:s,initialFitViewOptions:c,initialMinZoom:l,initialMaxZoom:u,nodeOrigin:d,nodeExtent:m,zIndexMode:h,children:e})}var fd={width:`100%`,height:`100%`,overflow:`hidden`,position:`relative`,zIndex:0};function pd({nodes:e,edges:t,defaultNodes:n,defaultEdges:r,className:i,nodeTypes:a,edgeTypes:o,onNodeClick:s,onEdgeClick:c,onInit:l,onMove:u,onMoveStart:d,onMoveEnd:h,onConnect:g,onConnectStart:_,onConnectEnd:v,onClickConnectStart:y,onClickConnectEnd:b,onNodeMouseEnter:x,onNodeMouseMove:S,onNodeMouseLeave:C,onNodeContextMenu:w,onNodeDoubleClick:T,onNodeDragStart:E,onNodeDrag:D,onNodeDragStop:O,onNodesDelete:k,onEdgesDelete:A,onDelete:j,onSelectionChange:M,onSelectionDragStart:N,onSelectionDrag:P,onSelectionDragStop:F,onSelectionContextMenu:I,onSelectionStart:L,onSelectionEnd:R,onBeforeDelete:ee,connectionMode:te,connectionLineType:ne=xa.Bezier,connectionLineStyle:z,connectionLineComponent:re,connectionLineContainerStyle:ie,deleteKeyCode:ae=`Backspace`,selectionKeyCode:oe=`Shift`,selectionOnDrag:se=!1,selectionMode:ce=ya.Full,panActivationKeyCode:le=`Space`,multiSelectionKeyCode:ue=so()?`Meta`:`Control`,zoomActivationKeyCode:de=so()?`Meta`:`Control`,snapToGrid:fe,snapGrid:pe,onlyRenderVisibleElements:me=!1,selectNodesOnDrag:he,nodesDraggable:ge,autoPanOnNodeFocus:_e,nodesConnectable:ve,nodesFocusable:ye,nodeOrigin:be=Fc,edgesFocusable:xe,edgesReconnectable:Se,elementsSelectable:Ce=!0,defaultViewport:we=Ic,minZoom:Te=.5,maxZoom:Ee=2,translateExtent:De=ma,preventScrolling:Oe=!0,nodeExtent:ke,defaultMarkerColor:Ae=`#b1b1b7`,zoomOnScroll:je=!0,zoomOnPinch:Me=!0,panOnScroll:Ne=!1,panOnScrollSpeed:Pe=.5,panOnScrollMode:Fe=va.Free,zoomOnDoubleClick:Ie=!0,panOnDrag:Le=!0,onPaneClick:Re,onPaneMouseEnter:ze,onPaneMouseMove:Be,onPaneMouseLeave:Ve,onPaneScroll:He,onPaneContextMenu:Ue,paneClickDistance:We=1,nodeClickDistance:Ge=0,children:Ke,onReconnect:qe,onReconnectStart:Je,onReconnectEnd:Ye,onEdgeContextMenu:Xe,onEdgeDoubleClick:Ze,onEdgeMouseEnter:Qe,onEdgeMouseMove:$e,onEdgeMouseLeave:et,reconnectRadius:tt=10,onNodesChange:nt,onEdgesChange:rt,noDragClassName:it=`nodrag`,noWheelClassName:at=`nowheel`,noPanClassName:ot=`nopan`,fitView:st,fitViewOptions:ct,connectOnClick:lt,attributionPosition:ut,proOptions:dt,defaultEdgeOptions:ft,elevateNodesOnSelect:pt=!0,elevateEdgesOnSelect:mt=!1,disableKeyboardA11y:ht=!1,autoPanOnConnect:gt,autoPanOnNodeDrag:_t,autoPanOnSelection:vt=!0,autoPanSpeed:yt,connectionRadius:bt,isValidConnection:xt,onError:St,style:Ct,id:wt,nodeDragThreshold:Tt,connectionDragThreshold:Et,viewport:Dt,onViewportChange:Ot,width:kt,height:At,colorMode:jt=`light`,debug:Mt,onScroll:Nt,ariaLabelConfig:Pt,zIndexMode:Ft=`basic`,...It},Lt){let Rt=wt||`1`,zt=Hc(jt),Bt=(0,f.useCallback)(e=>{e.currentTarget.scrollTo({top:0,left:0,behavior:`instant`}),Nt?.(e)},[Nt]);return(0,p.jsx)(`div`,{"data-testid":`rf__wrapper`,...It,onScroll:Bt,style:{...Ct,...fd},ref:Lt,className:m([`react-flow`,i,zt]),id:wt,role:`application`,children:(0,p.jsxs)(dd,{nodes:e,edges:t,width:kt,height:At,fitView:st,fitViewOptions:ct,minZoom:Te,maxZoom:Ee,nodeOrigin:be,nodeExtent:ke,zIndexMode:Ft,children:[(0,p.jsx)(Bc,{nodes:e,edges:t,defaultNodes:n,defaultEdges:r,onConnect:g,onConnectStart:_,onConnectEnd:v,onClickConnectStart:y,onClickConnectEnd:b,nodesDraggable:ge,autoPanOnNodeFocus:_e,nodesConnectable:ve,nodesFocusable:ye,edgesFocusable:xe,edgesReconnectable:Se,elementsSelectable:Ce,elevateNodesOnSelect:pt,elevateEdgesOnSelect:mt,minZoom:Te,maxZoom:Ee,nodeExtent:ke,onNodesChange:nt,onEdgesChange:rt,snapToGrid:fe,snapGrid:pe,connectionMode:te,translateExtent:De,connectOnClick:lt,defaultEdgeOptions:ft,fitView:st,fitViewOptions:ct,onNodesDelete:k,onEdgesDelete:A,onDelete:j,onNodeDragStart:E,onNodeDrag:D,onNodeDragStop:O,onSelectionDrag:P,onSelectionDragStart:N,onSelectionDragStop:F,onMove:u,onMoveStart:d,onMoveEnd:h,noPanClassName:ot,nodeOrigin:be,rfId:Rt,autoPanOnConnect:gt,autoPanOnNodeDrag:_t,autoPanSpeed:yt,onError:St,connectionRadius:bt,isValidConnection:xt,selectNodesOnDrag:he,nodeDragThreshold:Tt,connectionDragThreshold:Et,onBeforeDelete:ee,debug:Mt,ariaLabelConfig:Pt,zIndexMode:Ft}),(0,p.jsx)(od,{onInit:l,onNodeClick:s,onEdgeClick:c,onNodeMouseEnter:x,onNodeMouseMove:S,onNodeMouseLeave:C,onNodeContextMenu:w,onNodeDoubleClick:T,nodeTypes:a,edgeTypes:o,connectionLineType:ne,connectionLineStyle:z,connectionLineComponent:re,connectionLineContainerStyle:ie,selectionKeyCode:oe,selectionOnDrag:se,selectionMode:ce,deleteKeyCode:ae,multiSelectionKeyCode:ue,panActivationKeyCode:le,zoomActivationKeyCode:de,onlyRenderVisibleElements:me,defaultViewport:we,translateExtent:De,minZoom:Te,maxZoom:Ee,preventScrolling:Oe,zoomOnScroll:je,zoomOnPinch:Me,zoomOnDoubleClick:Ie,panOnScroll:Ne,panOnScrollSpeed:Pe,panOnScrollMode:Fe,panOnDrag:Le,autoPanOnSelection:vt,onPaneClick:Re,onPaneMouseEnter:ze,onPaneMouseMove:Be,onPaneMouseLeave:Ve,onPaneScroll:He,onPaneContextMenu:Ue,paneClickDistance:We,nodeClickDistance:Ge,onSelectionContextMenu:I,onSelectionStart:L,onSelectionEnd:R,onReconnect:qe,onReconnectStart:Je,onReconnectEnd:Ye,onEdgeContextMenu:Xe,onEdgeDoubleClick:Ze,onEdgeMouseEnter:Qe,onEdgeMouseMove:$e,onEdgeMouseLeave:et,reconnectRadius:tt,defaultMarkerColor:Ae,noDragClassName:it,noWheelClassName:at,noPanClassName:ot,rfId:Rt,disableKeyboardA11y:ht,nodeExtent:ke,viewport:Dt,onViewportChange:Ot}),(0,p.jsx)(Pc,{onSelectionChange:M}),Ke,(0,p.jsx)(Oc,{proOptions:dt,position:ut}),(0,p.jsx)(Ec,{rfId:Rt,disableKeyboardA11y:ht})]})})}var md=ol(pd);function hd(){let e=_c();return(0,f.useCallback)(t=>{let{domNode:n,updateNodeInternals:r}=e.getState(),i=Array.isArray(t)?t:[t],a=new Map;i.forEach(e=>{let t=n?.querySelector(`.react-flow__node[data-id="${e}"]`);t&&a.set(e,{id:e,nodeElement:t,force:!0})}),requestAnimationFrame(()=>r(a,{triggerFitView:!1}))},[])}var gd=e=>({x:e.transform[0],y:e.transform[1],zoom:e.transform[2]});function _d(){return H(gd,fc)}pa.error014();function vd({dimensions:e,lineWidth:t,variant:n,className:r}){return(0,p.jsx)(`path`,{strokeWidth:t,d:`M${e[0]/2} 0 V${e[1]} M0 ${e[1]/2} H${e[0]}`,className:m([`react-flow__background-pattern`,n,r])})}function yd({radius:e,className:t}){return(0,p.jsx)(`circle`,{cx:e,cy:e,r:e,className:m([`react-flow__background-pattern`,`dots`,t])})}var bd;(function(e){e.Lines=`lines`,e.Dots=`dots`,e.Cross=`cross`})(bd||={});var xd={[bd.Dots]:1,[bd.Lines]:1,[bd.Cross]:6},Sd=e=>({transform:e.transform,patternId:`pattern-${e.rfId}`});function Cd({id:e,variant:t=bd.Dots,gap:n=20,size:r,lineWidth:i=1,offset:a=0,color:o,bgColor:s,style:c,className:l,patternClassName:u}){let d=(0,f.useRef)(null),{transform:h,patternId:g}=H(Sd,fc),_=r||xd[t],v=t===bd.Dots,y=t===bd.Cross,b=Array.isArray(n)?n:[n,n],x=[b[0]*h[2]||1,b[1]*h[2]||1],S=_*h[2],C=Array.isArray(a)?a:[a,a],w=y?[S,S]:x,T=[C[0]*h[2]||1+w[0]/2,C[1]*h[2]||1+w[1]/2],E=`${g}${e||``}`;return(0,p.jsxs)(`svg`,{className:m([`react-flow__background`,l]),style:{...c,...yl,"--xy-background-color-props":s,"--xy-background-pattern-color-props":o},ref:d,"data-testid":`rf__background`,children:[(0,p.jsx)(`pattern`,{id:E,x:h[0]%x[0],y:h[1]%x[1],width:x[0],height:x[1],patternUnits:`userSpaceOnUse`,patternTransform:`translate(-${T[0]},-${T[1]})`,children:v?(0,p.jsx)(yd,{radius:S/2,className:u}):(0,p.jsx)(vd,{dimensions:w,lineWidth:i,variant:t,className:u})}),(0,p.jsx)(`rect`,{x:`0`,y:`0`,width:`100%`,height:`100%`,fill:`url(#${E})`})]})}Cd.displayName=`Background`;var wd=(0,f.memo)(Cd);function Td(){return(0,p.jsx)(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 32 32`,children:(0,p.jsx)(`path`,{d:`M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z`})})}function Ed(){return(0,p.jsx)(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 32 5`,children:(0,p.jsx)(`path`,{d:`M0 0h32v4.2H0z`})})}function G(){return(0,p.jsx)(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 32 30`,children:(0,p.jsx)(`path`,{d:`M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z`})})}function Dd(){return(0,p.jsx)(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 25 32`,children:(0,p.jsx)(`path`,{d:`M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z`})})}function Od(){return(0,p.jsx)(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 25 32`,children:(0,p.jsx)(`path`,{d:`M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z`})})}function kd({children:e,className:t,...n}){return(0,p.jsx)(`button`,{type:`button`,className:m([`react-flow__controls-button`,t]),...n,children:e})}var Ad=e=>({isInteractive:e.nodesDraggable||e.nodesConnectable||e.elementsSelectable,minZoomReached:e.transform[2]<=e.minZoom,maxZoomReached:e.transform[2]>=e.maxZoom,ariaLabelConfig:e.ariaLabelConfig});function jd({style:e,showZoom:t=!0,showFitView:n=!0,showInteractive:r=!0,fitViewOptions:i,onZoomIn:a,onZoomOut:o,onFitView:s,onInteractiveChange:c,className:l,children:u,position:d=`bottom-left`,orientation:f=`vertical`,"aria-label":h}){let g=_c(),{isInteractive:_,minZoomReached:v,maxZoomReached:y,ariaLabelConfig:b}=H(Ad,fc),{zoomIn:x,zoomOut:S,fitView:C}=ml();return(0,p.jsxs)(Dc,{className:m([`react-flow__controls`,f===`horizontal`?`horizontal`:`vertical`,l]),position:d,style:e,"data-testid":`rf__controls`,"aria-label":h??b[`controls.ariaLabel`],children:[t&&(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)(kd,{onClick:()=>{x(),a?.()},className:`react-flow__controls-zoomin`,title:b[`controls.zoomIn.ariaLabel`],"aria-label":b[`controls.zoomIn.ariaLabel`],disabled:y,children:(0,p.jsx)(Td,{})}),(0,p.jsx)(kd,{onClick:()=>{S(),o?.()},className:`react-flow__controls-zoomout`,title:b[`controls.zoomOut.ariaLabel`],"aria-label":b[`controls.zoomOut.ariaLabel`],disabled:v,children:(0,p.jsx)(Ed,{})})]}),n&&(0,p.jsx)(kd,{className:`react-flow__controls-fitview`,onClick:()=>{C(i),s?.()},title:b[`controls.fitView.ariaLabel`],"aria-label":b[`controls.fitView.ariaLabel`],children:(0,p.jsx)(G,{})}),r&&(0,p.jsx)(kd,{className:`react-flow__controls-interactive`,onClick:()=>{g.setState({nodesDraggable:!_,nodesConnectable:!_,elementsSelectable:!_}),c?.(!_)},title:b[`controls.interactive.ariaLabel`],"aria-label":b[`controls.interactive.ariaLabel`],children:_?(0,p.jsx)(Od,{}):(0,p.jsx)(Dd,{})}),u]})}jd.displayName=`Controls`,(0,f.memo)(jd);function Md({id:e,x:t,y:n,width:r,height:i,style:a,color:o,strokeColor:s,strokeWidth:c,className:l,borderRadius:u,shapeRendering:d,selected:f,onClick:h}){let{background:g,backgroundColor:_}=a||{},v=o||g||_;return(0,p.jsx)(`rect`,{className:m([`react-flow__minimap-node`,{selected:f},l]),x:t,y:n,rx:u,ry:u,width:r,height:i,style:{fill:v,stroke:s,strokeWidth:c},shapeRendering:d,onClick:h?t=>h(t,e):void 0})}var Nd=(0,f.memo)(Md),Pd=e=>e.nodes.map(e=>e.id),Fd=e=>e instanceof Function?e:()=>e;function Id({nodeStrokeColor:e,nodeColor:t,nodeClassName:n=``,nodeBorderRadius:r=5,nodeStrokeWidth:i,nodeComponent:a=Nd,onClick:o}){let s=H(Pd,fc),c=Fd(t),l=Fd(e),u=Fd(n),d=typeof window>`u`||window.chrome?`crispEdges`:`geometricPrecision`;return(0,p.jsx)(p.Fragment,{children:s.map(e=>(0,p.jsx)(Rd,{id:e,nodeColorFunc:c,nodeStrokeColorFunc:l,nodeClassNameFunc:u,nodeBorderRadius:r,nodeStrokeWidth:i,NodeComponent:a,onClick:o,shapeRendering:d},e))})}function Ld({id:e,nodeColorFunc:t,nodeStrokeColorFunc:n,nodeClassNameFunc:r,nodeBorderRadius:i,nodeStrokeWidth:a,shapeRendering:o,NodeComponent:s,onClick:c}){let{node:l,x:u,y:d,width:f,height:m}=H(t=>{let n=t.nodeLookup.get(e);if(!n)return{node:void 0,x:0,y:0,width:0,height:0};let r=n.internals.userNode,{x:i,y:a}=n.internals.positionAbsolute,{width:o,height:s}=lo(r);return{node:r,x:i,y:a,width:o,height:s}},fc);return!l||l.hidden||!uo(l)?null:(0,p.jsx)(s,{x:u,y:d,width:f,height:m,style:l.style,selected:!!l.selected,className:r(l),color:t(l),borderRadius:i,strokeColor:n(l),strokeWidth:a,shapeRendering:o,onClick:c,id:l.id})}var Rd=(0,f.memo)(Ld),zd=(0,f.memo)(Id),Bd=200,Vd=150,Hd=e=>!e.hidden,Ud=e=>{let t={x:-e.transform[0]/e.transform[2],y:-e.transform[1]/e.transform[2],width:e.width/e.transform[2],height:e.height/e.transform[2]};return{viewBB:t,boundingRect:e.nodeLookup.size>0?Ya(Ma(e.nodeLookup,{filter:Hd}),t):t,rfId:e.rfId,panZoom:e.panZoom,translateExtent:e.translateExtent,flowWidth:e.width,flowHeight:e.height,ariaLabelConfig:e.ariaLabelConfig}},Wd=`react-flow__minimap-desc`;function Gd({style:e,className:t,nodeStrokeColor:n,nodeColor:r,nodeClassName:i=``,nodeBorderRadius:a=5,nodeStrokeWidth:o,nodeComponent:s,bgColor:c,maskColor:l,maskStrokeColor:u,maskStrokeWidth:d,position:h=`bottom-right`,onClick:g,onNodeClick:_,pannable:v=!1,zoomable:y=!1,ariaLabel:b,inversePan:x,zoomStep:S=1,offsetScale:C=5}){let w=_c(),T=(0,f.useRef)(null),{boundingRect:E,viewBB:D,rfId:O,panZoom:k,translateExtent:A,flowWidth:j,flowHeight:M,ariaLabelConfig:N}=H(Ud,fc),P=e?.width??Bd,F=e?.height??Vd,I=E.width/P,L=E.height/F,R=Math.max(I,L),ee=R*P,te=R*F,ne=C*R,z=E.x-(ee-E.width)/2-ne,re=E.y-(te-E.height)/2-ne,ie=ee+ne*2,ae=te+ne*2,oe=`${Wd}-${O}`,se=(0,f.useRef)(0),ce=(0,f.useRef)();se.current=R,(0,f.useEffect)(()=>{if(T.current&&k)return ce.current=Ds({domNode:T.current,panZoom:k,getTransform:()=>w.getState().transform,getViewScale:()=>se.current}),()=>{ce.current?.destroy()}},[k]),(0,f.useEffect)(()=>{ce.current?.update({translateExtent:A,width:j,height:M,inversePan:x,pannable:v,zoomStep:S,zoomable:y})},[v,y,x,S,A,j,M]);let le=g?e=>{let[t,n]=ce.current?.pointer(e)||[0,0];g(e,{x:t,y:n})}:void 0,ue=_?(0,f.useCallback)((e,t)=>{let n=w.getState().nodeLookup.get(t).internals.userNode;_(e,n)},[]):void 0,de=b??N[`minimap.ariaLabel`];return(0,p.jsx)(Dc,{position:h,style:{...e,"--xy-minimap-background-color-props":typeof c==`string`?c:void 0,"--xy-minimap-mask-background-color-props":typeof l==`string`?l:void 0,"--xy-minimap-mask-stroke-color-props":typeof u==`string`?u:void 0,"--xy-minimap-mask-stroke-width-props":typeof d==`number`?d*R:void 0,"--xy-minimap-node-background-color-props":typeof r==`string`?r:void 0,"--xy-minimap-node-stroke-color-props":typeof n==`string`?n:void 0,"--xy-minimap-node-stroke-width-props":typeof o==`number`?o:void 0},className:m([`react-flow__minimap`,t]),"data-testid":`rf__minimap`,children:(0,p.jsxs)(`svg`,{width:P,height:F,viewBox:`${z} ${re} ${ie} ${ae}`,className:`react-flow__minimap-svg`,role:`img`,"aria-labelledby":oe,ref:T,onClick:le,children:[de&&(0,p.jsx)(`title`,{id:oe,children:de}),(0,p.jsx)(zd,{onClick:ue,nodeColor:r,nodeStrokeColor:n,nodeBorderRadius:a,nodeClassName:i,nodeStrokeWidth:o,nodeComponent:s}),(0,p.jsx)(`path`,{className:`react-flow__minimap-mask`,d:`M${z-ne},${re-ne}h${ie+ne*2}v${ae+ne*2}h${-ie-ne*2}z + M${D.x},${D.y}h${D.width}v${D.height}h${-D.width}z`,fillRule:`evenodd`,pointerEvents:`none`})]})})}Gd.displayName=`MiniMap`,(0,f.memo)(Gd);var Kd=e=>t=>e?`${Math.max(1/t.transform[2],1)}`:void 0,qd={[Hs.Line]:`right`,[Hs.Handle]:`bottom-right`};function Jd({nodeId:e,position:t,variant:n=Hs.Handle,className:r,style:i=void 0,children:a,color:o,minWidth:s=10,minHeight:c=10,maxWidth:l=Number.MAX_VALUE,maxHeight:u=Number.MAX_VALUE,keepAspectRatio:d=!1,resizeDirection:h,autoScale:g=!0,shouldResize:_,onResizeStart:v,onResize:y,onResizeEnd:b}){let x=Nl(),S=typeof e==`string`?e:x,C=_c(),w=(0,f.useRef)(null),T=n===Hs.Handle,E=H((0,f.useCallback)(Kd(T&&g),[T,g]),fc),D=(0,f.useRef)(null),O=t??qd[n];return(0,f.useEffect)(()=>{if(!(!w.current||!S))return D.current||=$s({domNode:w.current,nodeId:S,getStoreItems:()=>{let{nodeLookup:e,transform:t,snapGrid:n,snapToGrid:r,nodeOrigin:i,domNode:a}=C.getState();return{nodeLookup:e,transform:t,snapGrid:n,snapToGrid:r,nodeOrigin:i,paneDomNode:a}},onChange:(e,t)=>{let{triggerNodeChanges:n,nodeLookup:r,parentLookup:i,nodeOrigin:a}=C.getState(),o=[],s={x:e.x,y:e.y},c=r.get(S);if(c&&c.expandParent&&c.parentId){let t=c.origin??a,n=e.width??c.measured.width??0,l=e.height??c.measured.height??0,u=os([{id:c.id,parentId:c.parentId,rect:{width:n,height:l,...fo({x:e.x??c.position.x,y:e.y??c.position.y},{width:n,height:l},c.parentId,r,t)}}],r,i,a);o.push(...u),s.x=e.x?Math.max(t[0]*n,e.x):void 0,s.y=e.y?Math.max(t[1]*l,e.y):void 0}if(s.x!==void 0&&s.y!==void 0){let e={id:S,type:`position`,position:{...s}};o.push(e)}if(e.width!==void 0&&e.height!==void 0){let t={id:S,type:`dimensions`,resizing:!0,setAttributes:h?h===`horizontal`?`width`:`height`:!0,dimensions:{width:e.width,height:e.height}};o.push(t)}for(let e of t){let t={...e,type:`position`};o.push(t)}n(o)},onEnd:({width:e,height:t})=>{let n={id:S,type:`dimensions`,resizing:!1,dimensions:{width:e,height:t}};C.getState().triggerNodeChanges([n])}}),D.current.update({controlPosition:O,boundaries:{minWidth:s,minHeight:c,maxWidth:l,maxHeight:u},keepAspectRatio:d,resizeDirection:h,onResizeStart:v,onResize:y,onResizeEnd:b,shouldResize:_}),()=>{D.current?.destroy()}},[O,s,c,l,u,d,v,y,b,_]),(0,p.jsx)(`div`,{className:m([`react-flow__resize-control`,`nodrag`,...O.split(`-`),n,r]),ref:w,style:{...i,scale:E,...o&&{[T?`backgroundColor`:`borderColor`]:o}},children:a})}var Yd=(0,f.memo)(Jd),Xd=e=>e?.ownerDocument??document,Zd=e=>e&&`window`in e&&e.window===e?e:Xd(e).defaultView||window;function Qd(e){return typeof e==`object`&&!!e&&`nodeType`in e&&typeof e.nodeType==`number`}function $d(e){return Qd(e)&&e.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&`host`in e}var ef=!1;function tf(){return ef}function nf(e,t){if(!tf())return t&&e?e.contains(t):!1;if(!e||!t)return!1;let n=t;for(;n!==null;){if(n===e)return!0;n=n.tagName===`SLOT`&&n.assignedSlot?n.assignedSlot.parentNode:$d(n)?n.host:n.parentNode}return!1}var rf=(e=document)=>{if(!tf())return e.activeElement;let t=e.activeElement;for(;t&&`shadowRoot`in t&&t.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t};function af(e){if(tf()&&e.target instanceof Element&&e.target.shadowRoot){if(`composedPath`in e)return e.composedPath()[0]??null;if(`composedPath`in e.nativeEvent)return e.nativeEvent.composedPath()[0]??null}return e.target}function of(e){if(cf())e.focus({preventScroll:!0});else{let t=lf(e);e.focus(),uf(t)}}var sf=null;function cf(){if(sf==null){sf=!1;try{document.createElement(`div`).focus({get preventScroll(){return sf=!0,!0}})}catch{}}return sf}function lf(e){let t=e.parentNode,n=[],r=document.scrollingElement||document.documentElement;for(;t instanceof HTMLElement&&t!==r;)(t.offsetHeight{};function ff(e){let t=e;return t.nativeEvent=e,t.isDefaultPrevented=()=>t.defaultPrevented,t.isPropagationStopped=()=>t.cancelBubble,t.persist=()=>{},t}function pf(e,t){Object.defineProperty(e,"target",{value:t}),Object.defineProperty(e,"currentTarget",{value:t})}function mf(e){let t=(0,f.useRef)({isFocused:!1,observer:null});return df(()=>{let e=t.current;return()=>{e.observer&&=(e.observer.disconnect(),null)}},[]),(0,f.useCallback)(n=>{let r=af(n);if(r instanceof HTMLButtonElement||r instanceof HTMLInputElement||r instanceof HTMLTextAreaElement||r instanceof HTMLSelectElement){t.current.isFocused=!0;let n=r;n.addEventListener(`focusout`,r=>{if(t.current.isFocused=!1,n.disabled){let t=ff(r);e?.(t)}t.current.observer&&(t.current.observer.disconnect(),t.current.observer=null)},{once:!0}),t.current.observer=new MutationObserver(()=>{if(t.current.isFocused&&n.disabled){t.current.observer?.disconnect();let e=n===rf()?null:rf();n.dispatchEvent(new FocusEvent(`blur`,{relatedTarget:e})),n.dispatchEvent(new FocusEvent(`focusout`,{bubbles:!0,relatedTarget:e}))}}),t.current.observer.observe(n,{attributes:!0,attributeFilter:[`disabled`]})}},[e])}function hf(e){if(typeof window>`u`||window.navigator==null)return!1;let t=window.navigator.userAgentData?.brands;return Array.isArray(t)&&t.some(t=>e.test(t.brand))||e.test(window.navigator.userAgent)}function gf(e){return typeof window<`u`&&window.navigator!=null&&e.test(window.navigator.userAgentData?.platform||window.navigator.platform)}function _f(e){let t=null;return()=>(t??=e(),t)}var vf=_f(function(){return gf(/^Mac/i)}),yf=_f(function(){return gf(/^iPad/i)||vf()&&navigator.maxTouchPoints>1}),bf=_f(function(){return hf(/AppleWebKit/i)&&!xf()}),xf=_f(function(){return hf(/Chrome/i)}),Sf=_f(function(){return hf(/Android/i)}),Cf=_f(function(){return hf(/Firefox/i)});function wf(e){return e.pointerType===``&&e.isTrusted?!0:Sf()&&e.pointerType?e.type===`click`&&e.buttons===1:e.detail===0&&!e.pointerType}function Tf(e,t,n=!0){let{metaKey:r,ctrlKey:i,altKey:a,shiftKey:o}=t;Cf()&&window.event?.type?.startsWith(`key`)&&e.target===`_blank`&&(vf()?r=!0:i=!0);let s=bf()&&vf()&&!yf()?new KeyboardEvent(`keydown`,{keyIdentifier:`Enter`,metaKey:r,ctrlKey:i,altKey:a,shiftKey:o}):new MouseEvent(`click`,{metaKey:r,ctrlKey:i,altKey:a,shiftKey:o,detail:1,bubbles:!0,cancelable:!0});Tf.isOpening=n,of(e),e.dispatchEvent(s),Tf.isOpening=!1}Tf.isOpening=!1;var Ef=null,Df=new Set,Of=new Map,kf=!1,Af=!1,jf={Tab:!0,Escape:!0};function Mf(e,t){for(let n of Df)n(e,t)}function Nf(e){return!(e.metaKey||!vf()&&e.altKey||e.ctrlKey||e.key===`Control`||e.key===`Shift`||e.key===`Meta`)}function Pf(e){kf=!0,!Tf.isOpening&&Nf(e)&&(Ef=`keyboard`,Mf(`keyboard`,e))}function Ff(e){Ef=`pointer`,`pointerType`in e&&e.pointerType,(e.type===`mousedown`||e.type===`pointerdown`)&&(kf=!0,Mf(`pointer`,e))}function If(e){!Tf.isOpening&&wf(e)&&(kf=!0,Ef=`virtual`)}function Lf(e){let t=Zd(af(e)),n=Xd(af(e));af(e)===t||af(e)===n||!e.isTrusted||(!kf&&!Af&&(Ef=`virtual`,Mf(`virtual`,e)),kf=!1,Af=!1)}function Rf(){kf=!1,Af=!0}function zf(e){if(typeof window>`u`||typeof document>`u`)return;let t=Zd(e),n=Xd(e);if(Of.get(t))return;let r=t.HTMLElement.prototype.focus;t.HTMLElement.prototype.focus=function(){kf=!0,r.apply(this,arguments)},n.addEventListener(`keydown`,Pf,!0),n.addEventListener(`keyup`,Pf,!0),n.addEventListener(`click`,If,!0),t.addEventListener(`focus`,Lf,!0),t.addEventListener(`blur`,Rf,!1),typeof PointerEvent<`u`&&(n.addEventListener(`pointerdown`,Ff,!0),n.addEventListener(`pointermove`,Ff,!0),n.addEventListener(`pointerup`,Ff,!0)),t.addEventListener(`beforeunload`,()=>{Bf(e)},{once:!0}),Of.set(t,{focus:r})}var Bf=(e,t)=>{let n=Zd(e),r=Xd(e);t&&r.removeEventListener(`DOMContentLoaded`,t),Of.has(n)&&(n.HTMLElement.prototype.focus=Of.get(n).focus,r.removeEventListener(`keydown`,Pf,!0),r.removeEventListener(`keyup`,Pf,!0),r.removeEventListener(`click`,If,!0),n.removeEventListener(`focus`,Lf,!0),n.removeEventListener(`blur`,Rf,!1),typeof PointerEvent<`u`&&(r.removeEventListener(`pointerdown`,Ff,!0),r.removeEventListener(`pointermove`,Ff,!0),r.removeEventListener(`pointerup`,Ff,!0)),Of.delete(n))};function Vf(e){let t=Xd(e),n;return t.readyState===`loading`?(n=()=>{zf(e)},t.addEventListener(`DOMContentLoaded`,n)):zf(e),()=>Bf(e,n)}typeof document<`u`&&Vf();function Hf(){return Ef!==`pointer`}var Uf=new Set([`checkbox`,`radio`,`range`,`color`,`file`,`image`,`button`,`submit`,`reset`]);function Wf(e,t,n){let r=n?af(n):void 0,i=Xd(r),a=Zd(r),o=a===void 0?HTMLInputElement:a.HTMLInputElement,s=a===void 0?HTMLTextAreaElement:a.HTMLTextAreaElement,c=a===void 0?HTMLElement:a.HTMLElement,l=a===void 0?KeyboardEvent:a.KeyboardEvent,u=rf(i);return e=e||u instanceof o&&!Uf.has(u.type)||u instanceof s||u instanceof c&&u.isContentEditable,!(e&&t===`keyboard`&&n instanceof l&&!jf[n.key])}function Gf(e,t,n){zf(),(0,f.useEffect)(()=>{if(n?.enabled===!1)return;let t=(t,r)=>{Wf(!!n?.isTextInput,t,r)&&e(Hf())};return Df.add(t),()=>{Df.delete(t)}},t)}function Kf(e){let{isDisabled:t,onFocus:n,onBlur:r,onFocusChange:i}=e,a=(0,f.useCallback)(e=>{if(af(e)===e.currentTarget)return r&&r(e),i&&i(!1),!0},[r,i]),o=mf(a),s=(0,f.useCallback)(e=>{let t=af(e),r=Xd(t),a=r?rf(r):rf();t===e.currentTarget&&t===a&&(n&&n(e),i&&i(!0),o(e))},[i,n,o]);return{focusProps:{onFocus:!t&&(n||i||r)?s:void 0,onBlur:!t&&(r||i)?a:void 0}}}function qf(){let e=(0,f.useRef)(new Map),t=(0,f.useCallback)((t,n,r,i)=>{let a=i?.once?(...t)=>{e.current.delete(r),r(...t)}:r;e.current.set(r,{type:n,eventTarget:t,fn:a,options:i}),t.addEventListener(n,a,i)},[]),n=(0,f.useCallback)((t,n,r,i)=>{let a=e.current.get(r)?.fn||r;t.removeEventListener(n,a,i),e.current.delete(r)},[]),r=(0,f.useCallback)(()=>{e.current.forEach((e,t)=>{n(e.eventTarget,e.type,t,e.options)})},[n]);return(0,f.useEffect)(()=>r,[r]),{addGlobalListener:t,removeGlobalListener:n,removeAllGlobalListeners:r}}function Jf(e){let{isDisabled:t,onBlurWithin:n,onFocusWithin:r,onFocusWithinChange:i}=e,a=(0,f.useRef)({isFocusWithin:!1}),{addGlobalListener:o,removeAllGlobalListeners:s}=qf(),c=(0,f.useCallback)(e=>{nf(e.currentTarget,af(e))&&a.current.isFocusWithin&&!nf(e.currentTarget,e.relatedTarget)&&(a.current.isFocusWithin=!1,s(),n&&n(e),i&&i(!1))},[n,i,a,s]),l=mf(c),u=(0,f.useCallback)(e=>{if(!nf(e.currentTarget,af(e)))return;let t=af(e),n=Xd(t),s=rf(n);if(!a.current.isFocusWithin&&s===t){r&&r(e),i&&i(!0),a.current.isFocusWithin=!0,l(e);let t=e.currentTarget;o(n,`focus`,e=>{let r=af(e);if(a.current.isFocusWithin&&!nf(t,r)){let e=new n.defaultView.FocusEvent(`blur`,{relatedTarget:r});pf(e,t);let i=ff(e);c(i)}},{capture:!0})}},[r,i,l,o,c]);return t?{focusWithinProps:{onFocus:void 0,onBlur:void 0}}:{focusWithinProps:{onFocus:u,onBlur:c}}}function Yf(e={}){let{autoFocus:t=!1,isTextInput:n,within:r}=e,i=(0,f.useRef)({isFocused:!1,isFocusVisible:t||Hf()}),[a,o]=(0,f.useState)(!1),[s,c]=(0,f.useState)(()=>i.current.isFocused&&i.current.isFocusVisible),l=(0,f.useCallback)(()=>c(i.current.isFocused&&i.current.isFocusVisible),[]),u=(0,f.useCallback)(e=>{i.current.isFocused=e,i.current.isFocusVisible=Hf(),o(e),l()},[l]);Gf(e=>{i.current.isFocusVisible=e,l()},[n,a],{enabled:a,isTextInput:n});let{focusProps:d}=Kf({isDisabled:r,onFocusChange:u}),{focusWithinProps:p}=Jf({isDisabled:!r,onFocusWithinChange:u});return{isFocused:a,isFocusVisible:s,focusProps:r?p:d}}var Xf=!1,Zf=0;function Qf(){Xf=!0,setTimeout(()=>{Xf=!1},500)}function $f(e){e.pointerType===`touch`&&Qf()}function ep(){let e=Xd(null);if(e!==void 0)return Zf===0&&typeof PointerEvent<`u`&&e.addEventListener(`pointerup`,$f),Zf++,()=>{Zf--,!(Zf>0)&&typeof PointerEvent<`u`&&e.removeEventListener(`pointerup`,$f)}}function tp(e){let{onHoverStart:t,onHoverChange:n,onHoverEnd:r,isDisabled:i}=e,[a,o]=(0,f.useState)(!1),s=(0,f.useRef)({isHovered:!1,ignoreEmulatedMouseEvents:!1,pointerType:``,target:null}).current;(0,f.useEffect)(ep,[]);let{addGlobalListener:c,removeAllGlobalListeners:l}=qf(),{hoverProps:u,triggerHoverEnd:d}=(0,f.useMemo)(()=>{let e=(e,r)=>{if(s.pointerType=r,i||r===`touch`||s.isHovered||!nf(e.currentTarget,af(e)))return;s.isHovered=!0;let l=e.currentTarget;s.target=l,c(Xd(af(e)),`pointerover`,e=>{s.isHovered&&s.target&&!nf(s.target,af(e))&&a(e,e.pointerType)},{capture:!0}),t&&t({type:`hoverstart`,target:l,pointerType:r}),n&&n(!0),o(!0)},a=(e,t)=>{let i=s.target;s.pointerType=``,s.target=null,!(t===`touch`||!s.isHovered||!i)&&(s.isHovered=!1,l(),r&&r({type:`hoverend`,target:i,pointerType:t}),n&&n(!1),o(!1))},u={};return typeof PointerEvent<`u`&&(u.onPointerEnter=t=>{Xf&&t.pointerType===`mouse`||e(t,t.pointerType)},u.onPointerLeave=e=>{!i&&nf(e.currentTarget,af(e))&&a(e,e.pointerType)}),{hoverProps:u,triggerHoverEnd:a}},[t,n,r,i,s,c,l]);return(0,f.useEffect)(()=>{i&&d({currentTarget:s.target},s.pointerType)},[i]),{hoverProps:u,isHovered:a}}var np=Object.defineProperty,rp=(e,t,n)=>t in e?np(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,ip=(e,t,n)=>(rp(e,typeof t==`symbol`?t:t+``,n),n),ap=new class{constructor(){ip(this,`current`,this.detect()),ip(this,`handoffState`,`pending`),ip(this,`currentId`,0)}set(e){this.current!==e&&(this.handoffState=`pending`,this.currentId=0,this.current=e)}reset(){this.set(this.detect())}nextId(){return++this.currentId}get isServer(){return this.current===`server`}get isClient(){return this.current===`client`}detect(){return typeof window>`u`||typeof document>`u`?`server`:`client`}handoff(){this.handoffState===`pending`&&(this.handoffState=`complete`)}get isHandoffComplete(){return this.handoffState===`complete`}};function op(e){return ap.isServer?null:e==null?document:e?.ownerDocument??document}function sp(e){return ap.isServer?null:e==null?document:(e?.getRootNode)?.call(e)??document}function cp(e){return sp(e)?.activeElement??null}function lp(e){return cp(e)===e}function up(e){typeof queueMicrotask==`function`?queueMicrotask(e):Promise.resolve().then(e).catch(e=>setTimeout(()=>{throw e}))}function dp(){let e=[],t={addEventListener(e,n,r,i){return e.addEventListener(n,r,i),t.add(()=>e.removeEventListener(n,r,i))},requestAnimationFrame(...e){let n=requestAnimationFrame(...e);return t.add(()=>cancelAnimationFrame(n))},nextFrame(...e){return t.requestAnimationFrame(()=>t.requestAnimationFrame(...e))},setTimeout(...e){let n=setTimeout(...e);return t.add(()=>clearTimeout(n))},microTask(...e){let n={current:!0};return up(()=>{n.current&&e[0]()}),t.add(()=>{n.current=!1})},style(e,t,n){let r=e.style.getPropertyValue(t);return Object.assign(e.style,{[t]:n}),this.add(()=>{Object.assign(e.style,{[t]:r})})},group(e){let t=dp();return e(t),this.add(()=>t.dispose())},add(t){return e.includes(t)||e.push(t),()=>{let n=e.indexOf(t);if(n>=0)for(let t of e.splice(n,1))t()}},dispose(){for(let t of e.splice(0))t()}};return t}function fp(){let[e]=(0,f.useState)(dp);return(0,f.useEffect)(()=>()=>e.dispose(),[e]),e}var K=(e,t)=>{ap.isServer?(0,f.useEffect)(e,t):(0,f.useLayoutEffect)(e,t)};function pp(e){let t=(0,f.useRef)(e);return K(()=>{t.current=e},[e]),t}var q=function(e){let t=pp(e);return f.useCallback((...e)=>t.current(...e),[t])};function mp(e){let t=e.width/2,n=e.height/2;return{top:e.clientY-n,right:e.clientX+t,bottom:e.clientY+n,left:e.clientX-t}}function hp(e,t){return!(!e||!t||e.rightt.right||e.bottomt.bottom)}function gp({disabled:e=!1}={}){let t=(0,f.useRef)(null),[n,r]=(0,f.useState)(!1),i=fp(),a=q(()=>{t.current=null,r(!1),i.dispose()}),o=q(e=>{if(i.dispose(),t.current===null){t.current=e.currentTarget,r(!0);{let n=op(e.currentTarget);i.addEventListener(n,`pointerup`,a,!1),i.addEventListener(n,`pointermove`,e=>{if(t.current){let n=mp(e);r(hp(n,t.current.getBoundingClientRect()))}},!1),i.addEventListener(n,`pointercancel`,a,!1)}}});return{pressed:n,pressProps:e?{}:{onPointerDown:o,onPointerUp:a,onClick:a}}}function _p(e){return(0,f.useMemo)(()=>e,Object.values(e))}var vp=(0,f.createContext)(void 0);function yp(){return(0,f.useContext)(vp)}function bp(...e){return Array.from(new Set(e.flatMap(e=>typeof e==`string`?e.split(` `):[]))).filter(Boolean).join(` `)}function xp(e,t,...n){if(e in t){let r=t[e];return typeof r==`function`?r(...n):r}let r=Error(`Tried to handle "${e}" but there is no handler defined. Only defined handlers are: ${Object.keys(t).map(e=>`"${e}"`).join(`, `)}.`);throw Error.captureStackTrace&&Error.captureStackTrace(r,xp),r}var Sp=(e=>(e[e.None=0]=`None`,e[e.RenderStrategy=1]=`RenderStrategy`,e[e.Static=2]=`Static`,e))(Sp||{}),Cp=(e=>(e[e.Unmount=0]=`Unmount`,e[e.Hidden=1]=`Hidden`,e))(Cp||{});function J(){let e=Ep();return(0,f.useCallback)(t=>wp({mergeRefs:e,...t}),[e])}function wp({ourProps:e,theirProps:t,slot:n,defaultTag:r,features:i,visible:a=!0,name:o,mergeRefs:s}){s??=Dp;let c=Op(t,e);if(a)return Tp(c,n,r,o,s);let l=i??0;if(l&2){let{static:e=!1,...t}=c;if(e)return Tp(t,n,r,o,s)}if(l&1){let{unmount:e=!0,...t}=c;return xp(+!e,{0(){return null},1(){return Tp({...t,hidden:!0,style:{display:`none`}},n,r,o,s)}})}return Tp(c,n,r,o,s)}function Tp(e,t={},n,r,i){let{as:a=n,children:o,refName:s=`ref`,...c}=jp(e,[`unmount`,`static`]),l=e.ref===void 0?{}:{[s]:e.ref},u=typeof o==`function`?o(t):o;u=Np(u),`className`in c&&c.className&&typeof c.className==`function`&&(c.className=c.className(t)),c[`aria-labelledby`]&&c[`aria-labelledby`]===c.id&&(c[`aria-labelledby`]=void 0);let d={};if(t){let e=!1,n=[];for(let[r,i]of Object.entries(t))typeof i==`boolean`&&(e=!0),i===!0&&n.push(r.replace(/([A-Z])/g,e=>`-${e.toLowerCase()}`));if(e){d[`data-headlessui-state`]=n.join(` `);for(let e of n)d[`data-${e}`]=``}}if(Pp(a)&&(Object.keys(Ap(c)).length>0||Object.keys(Ap(d)).length>0))if(!(0,f.isValidElement)(u)||Array.isArray(u)&&u.length>1||Fp(u)){if(Object.keys(Ap(c)).length>0)throw Error([`Passing props on "Fragment"!`,``,`The current component <${r} /> is rendering a "Fragment".`,`However we need to passthrough the following props:`,Object.keys(Ap(c)).concat(Object.keys(Ap(d))).map(e=>` - ${e}`).join(` +`),``,`You can apply a few solutions:`,['Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".',`Render a single element as the child so that we can forward the props onto that element.`].map(e=>` - ${e}`).join(` +`)].join(` +`))}else{let e=u.props?.className,t=typeof e==`function`?(...t)=>bp(e(...t),c.className):bp(e,c.className),n=t?{className:t}:{},r=Op(u.props,Ap(jp(c,[`ref`])));for(let e in d)e in r&&delete d[e];return(0,f.cloneElement)(u,Object.assign({},r,d,l,{ref:i(Mp(u),l.ref)},n))}return(0,f.createElement)(a,Object.assign({},jp(c,[`ref`]),!Pp(a)&&l,!Pp(a)&&d),u)}function Ep(){let e=(0,f.useRef)([]),t=(0,f.useCallback)(t=>{for(let n of e.current)n!=null&&(typeof n==`function`?n(t):n.current=t)},[]);return(...n)=>{if(!n.every(e=>e==null))return e.current=n,t}}function Dp(...e){return e.every(e=>e==null)?void 0:t=>{for(let n of e)n!=null&&(typeof n==`function`?n(t):n.current=t)}}function Op(...e){if(e.length===0)return{};if(e.length===1)return e[0];let t={},n={};for(let r of e)for(let e in r)e.startsWith(`on`)&&typeof r[e]==`function`?(n[e]??(n[e]=[]),n[e].push(r[e])):t[e]=r[e];if(t.disabled||t[`aria-disabled`])for(let e in n)/^(on(?:Click|Pointer|Mouse|Key)(?:Down|Up|Press)?)$/.test(e)&&(n[e]=[e=>(e?.preventDefault)?.call(e)]);for(let e in n)Object.assign(t,{[e](t,...r){let i=n[e];for(let e of i){if((t instanceof Event||t?.nativeEvent instanceof Event)&&t.defaultPrevented)return;e(t,...r)}}});return t}function kp(...e){if(e.length===0)return{};if(e.length===1)return e[0];let t={},n={};for(let r of e)for(let e in r)e.startsWith(`on`)&&typeof r[e]==`function`?(n[e]??(n[e]=[]),n[e].push(r[e])):t[e]=r[e];for(let e in n)Object.assign(t,{[e](...t){let r=n[e];for(let e of r)e?.(...t)}});return t}function Y(e){return Object.assign((0,f.forwardRef)(e),{displayName:e.displayName??e.name})}function Ap(e){let t=Object.assign({},e);for(let e in t)t[e]===void 0&&delete t[e];return t}function jp(e,t=[]){let n=Object.assign({},e);for(let e of t)e in n&&delete n[e];return n}function Mp(e){return`19.2.7`.split(`.`)[0]>=`19`?e.props.ref:e.ref}function Np(e){if(e!=null&&e.$$typeof===Symbol.for(`react.lazy`)){let t=e._payload;if(t!=null&&t.status===`fulfilled`)return Np(t.value)}return e}function Pp(e){return e===f.Fragment||e===Symbol.for(`react.fragment`)}function Fp(e){return Pp(e.type)}function Ip(e,t,n){let[r,i]=(0,f.useState)(n),a=e!==void 0,o=(0,f.useRef)(a),s=(0,f.useRef)(!1),c=(0,f.useRef)(!1);return a&&!o.current&&!s.current?(s.current=!0,o.current=a,console.error(`A component is changing from uncontrolled to controlled. This may be caused by the value changing from undefined to a defined value, which should not happen.`)):!a&&o.current&&!c.current&&(c.current=!0,o.current=a,console.error(`A component is changing from controlled to uncontrolled. This may be caused by the value changing from a defined value to undefined, which should not happen.`)),[a?e:r,q(e=>(a||(0,pc.flushSync)(()=>i(e)),t?.(e)))]}function Lp(e){let[t]=(0,f.useState)(e);return t}function Rp(e={},t=null,n=[]){for(let[r,i]of Object.entries(e))Bp(n,zp(t,r),i);return n}function zp(e,t){return e?e+`[`+t+`]`:t}function Bp(e,t,n){if(Array.isArray(n))for(let[r,i]of n.entries())Bp(e,zp(t,r.toString()),i);else n instanceof Date?e.push([t,n.toISOString()]):typeof n==`boolean`?e.push([t,n?`1`:`0`]):typeof n==`string`?e.push([t,n]):typeof n==`number`?e.push([t,`${n}`]):n==null?e.push([t,``]):Hp(n)&&!(0,f.isValidElement)(n)&&Rp(n,t,e)}function Vp(e){var t;let n=e?.form??e.closest(`form`);if(n){for(let t of n.elements)if(t!==e&&(t.tagName===`INPUT`&&t.type===`submit`||t.tagName===`BUTTON`&&t.type===`submit`||t.nodeName===`INPUT`&&t.type===`image`)){t.click();return}(t=n.requestSubmit)==null||t.call(n)}}function Hp(e){if(Object.prototype.toString.call(e)!==`[object Object]`)return!1;let t=Object.getPrototypeOf(e);return t===null||Object.getPrototypeOf(t)===null}var Up=`span`,Wp=(e=>(e[e.None=1]=`None`,e[e.Focusable=2]=`Focusable`,e[e.Hidden=4]=`Hidden`,e))(Wp||{});function Gp(e,t){let{features:n=1,...r}=e,i={ref:t,"aria-hidden":(n&2)==2?!0:r[`aria-hidden`]??void 0,hidden:(n&4)==4||void 0,style:{position:`fixed`,top:1,left:1,width:1,height:0,padding:0,margin:-1,overflow:`hidden`,clip:`rect(0, 0, 0, 0)`,whiteSpace:`nowrap`,borderWidth:`0`,...(n&4)==4&&(n&2)!=2&&{display:`none`}}};return J()({ourProps:i,theirProps:r,slot:{},defaultTag:Up,name:`Hidden`})}var Kp=Y(Gp),qp=(0,f.createContext)(null);function Jp({children:e}){let t=(0,f.useContext)(qp);if(!t)return f.createElement(f.Fragment,null,e);let{target:n}=t;return n?(0,pc.createPortal)(f.createElement(f.Fragment,null,e),n):null}function Yp({data:e,form:t,disabled:n,onReset:r,overrides:i}){let[a,o]=(0,f.useState)(null),s=fp();return(0,f.useEffect)(()=>{if(r&&a)return s.addEventListener(a,`reset`,r)},[a,t,r]),f.createElement(Jp,null,f.createElement(Xp,{setForm:o,formId:t}),Rp(e).map(([e,r])=>f.createElement(Kp,{features:Wp.Hidden,...Ap({key:e,as:`input`,type:`hidden`,hidden:!0,readOnly:!0,form:t,disabled:n,name:e,value:r,...i})})))}function Xp({setForm:e,formId:t}){return(0,f.useEffect)(()=>{if(t){let n=document.getElementById(t);n&&e(n)}},[e,t]),t?null:f.createElement(Kp,{features:Wp.Hidden,as:`input`,type:`hidden`,hidden:!0,readOnly:!0,ref:t=>{if(!t)return;let n=t.closest(`form`);n&&e(n)}})}var Zp=(0,f.createContext)(void 0);function Qp(){return(0,f.useContext)(Zp)}function $p(e){return typeof e!=`object`||!e?!1:`nodeType`in e}function em(e){return $p(e)&&`tagName`in e}function tm(e){return em(e)&&`accessKey`in e}function nm(e){return em(e)&&`tabIndex`in e}function rm(e){return em(e)&&`style`in e}function im(e){return tm(e)&&e.nodeName===`IFRAME`}function am(e){return tm(e)&&e.nodeName===`INPUT`}function om(e){return tm(e)&&e.nodeName===`LABEL`}function sm(e){return tm(e)&&e.nodeName===`FIELDSET`}function cm(e){return tm(e)&&e.nodeName===`LEGEND`}function lm(e){return em(e)?e.matches(`a[href],audio[controls],button,details,embed,iframe,img[usemap],input:not([type="hidden"]),label,select,textarea,video[controls]`):!1}function um(e){let t=e.parentElement,n=null;for(;t&&!sm(t);)cm(t)&&(n=t),t=t.parentElement;let r=t?.getAttribute(`disabled`)===``;return r&&dm(n)?!1:r}function dm(e){if(!e)return!1;let t=e.previousElementSibling;for(;t!==null;){if(cm(t))return!1;t=t.previousElementSibling}return!0}var fm=Symbol();function pm(e,t=!0){return Object.assign(e,{[fm]:t})}function mm(...e){let t=(0,f.useRef)(e);(0,f.useEffect)(()=>{t.current=e},[e]);let n=q(e=>{for(let n of t.current)n!=null&&(typeof n==`function`?n(e):n.current=e)});return e.every(e=>e==null||e?.[fm])?void 0:n}var hm=(0,f.createContext)(null);hm.displayName=`DescriptionContext`;function gm(){let e=(0,f.useContext)(hm);if(e===null){let e=Error(`You used a component, but it is not inside a relevant parent.`);throw Error.captureStackTrace&&Error.captureStackTrace(e,gm),e}return e}function _m(){return(0,f.useContext)(hm)?.value??void 0}function vm(){let[e,t]=(0,f.useState)([]);return[e.length>0?e.join(` `):void 0,(0,f.useMemo)(()=>function(e){let n=q(e=>(t(t=>[...t,e]),()=>t(t=>{let n=t.slice(),r=n.indexOf(e);return r!==-1&&n.splice(r,1),n}))),r=(0,f.useMemo)(()=>({register:n,slot:e.slot,name:e.name,props:e.props,value:e.value}),[n,e.slot,e.name,e.props,e.value]);return f.createElement(hm.Provider,{value:r},e.children)},[t])]}var ym=`p`;function bm(e,t){let n=(0,f.useId)(),r=yp(),{id:i=`headlessui-description-${n}`,...a}=e,o=gm(),s=mm(t);K(()=>o.register(i),[i,o.register]);let c=_p({...o.slot,disabled:r||!1}),l={ref:s,...o.props,id:i};return J()({ourProps:l,theirProps:a,slot:c,defaultTag:ym,name:o.name||`Description`})}var xm=Y(bm),Sm=Object.assign(xm,{}),X=(e=>(e.Space=` `,e.Enter=`Enter`,e.Escape=`Escape`,e.Backspace=`Backspace`,e.Delete=`Delete`,e.ArrowLeft=`ArrowLeft`,e.ArrowUp=`ArrowUp`,e.ArrowRight=`ArrowRight`,e.ArrowDown=`ArrowDown`,e.Home=`Home`,e.End=`End`,e.PageUp=`PageUp`,e.PageDown=`PageDown`,e.Tab=`Tab`,e))(X||{}),Cm=(0,f.createContext)(null);Cm.displayName=`LabelContext`;function wm(){let e=(0,f.useContext)(Cm);if(e===null){let e=Error(`You used a