Merge pull request #122 from pollockjj/wvw
Pull Request: Refresh WanVideoWrapper MultiGPU support to latest upstream
This commit is contained in:
+2
-1
@@ -2,4 +2,5 @@
|
||||
__pycache__/
|
||||
.clinerules
|
||||
.vscode
|
||||
memory-bank/
|
||||
memory-bank/
|
||||
.github/
|
||||
+132
-27
@@ -3,6 +3,8 @@ import logging
|
||||
import weakref
|
||||
import os
|
||||
import copy
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import folder_paths
|
||||
import comfy.model_management as mm
|
||||
@@ -21,12 +23,22 @@ from .model_management_mgpu import (
|
||||
)
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
MGPU_MM_LOG = False
|
||||
MGPU_MM_LOG = True
|
||||
DEBUG_LOG = False
|
||||
|
||||
logger = logging.getLogger("MultiGPU")
|
||||
logger.propagate = False
|
||||
|
||||
FOCUS_LOG_LEVEL = logging.INFO + 5
|
||||
logging.addLevelName(FOCUS_LOG_LEVEL, "FOCUS")
|
||||
|
||||
if not hasattr(logging.Logger, "focus"):
|
||||
def focus(self, message, *args, **kwargs):
|
||||
if self.isEnabledFor(FOCUS_LOG_LEVEL):
|
||||
self._log(FOCUS_LOG_LEVEL, message, args, **kwargs)
|
||||
|
||||
logging.Logger.focus = focus # type: ignore[attr-defined]
|
||||
|
||||
if not logger.handlers:
|
||||
log_level = logging.DEBUG if DEBUG_LOG else logging.INFO
|
||||
handler = logging.StreamHandler()
|
||||
@@ -35,10 +47,93 @@ if not logger.handlers:
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(log_level)
|
||||
|
||||
json_log_path = os.environ.get("MGPU_JSON_LOG_PATH")
|
||||
json_static_fields = {}
|
||||
if json_log_path:
|
||||
try:
|
||||
json_static_fields = json.loads(os.environ.get("MGPU_JSON_STATIC_FIELDS", "{}"))
|
||||
except json.JSONDecodeError:
|
||||
json_static_fields = {}
|
||||
|
||||
level_aliases = {
|
||||
"CRITICAL": logging.CRITICAL,
|
||||
"ERROR": logging.ERROR,
|
||||
"WARNING": logging.WARNING,
|
||||
"FOCUS": FOCUS_LOG_LEVEL,
|
||||
"INFO": logging.INFO,
|
||||
"DEBUG": logging.DEBUG,
|
||||
}
|
||||
|
||||
json_min_level = FOCUS_LOG_LEVEL
|
||||
configured_min_level = os.environ.get("MGPU_JSON_MIN_LEVEL")
|
||||
if configured_min_level:
|
||||
value = configured_min_level.strip()
|
||||
upper_value = value.upper()
|
||||
if upper_value in level_aliases:
|
||||
json_min_level = level_aliases[upper_value]
|
||||
else:
|
||||
try:
|
||||
json_min_level = int(value)
|
||||
except ValueError:
|
||||
json_min_level = FOCUS_LOG_LEVEL
|
||||
|
||||
class JsonLineFileHandler(logging.Handler):
|
||||
def __init__(self, path, static_fields, min_level, overwrite):
|
||||
super().__init__()
|
||||
self.path = Path(path)
|
||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.static_fields = static_fields
|
||||
self.setLevel(min_level)
|
||||
if overwrite:
|
||||
try:
|
||||
with self.path.open("w", encoding="utf-8") as handle:
|
||||
handle.write("")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def emit(self, record):
|
||||
message = record.getMessage()
|
||||
category = None
|
||||
if message.startswith("[") and "]" in message:
|
||||
bracket_split = message.split("]", 1)
|
||||
category = bracket_split[0].strip("[]")
|
||||
payload = {
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
"level": record.levelname,
|
||||
"name": record.name,
|
||||
"message": message,
|
||||
}
|
||||
if category:
|
||||
payload["event_category"] = category
|
||||
if hasattr(record, "mgpu_context") and isinstance(record.mgpu_context, dict):
|
||||
payload.update(record.mgpu_context)
|
||||
workflow_id = os.environ.get("MGPU_JSON_WORKFLOW")
|
||||
prompt_id = os.environ.get("MGPU_JSON_PROMPT")
|
||||
if workflow_id:
|
||||
payload.setdefault("workflow_id", workflow_id)
|
||||
if prompt_id:
|
||||
payload.setdefault("prompt_id", prompt_id)
|
||||
if self.static_fields:
|
||||
payload.update(self.static_fields)
|
||||
try:
|
||||
with self.path.open("a", encoding="utf-8") as handle:
|
||||
handle.write(json.dumps(payload, ensure_ascii=True) + "\n")
|
||||
except OSError:
|
||||
# Fail silently for JSON logging so primary logging continues.
|
||||
pass
|
||||
|
||||
overwrite_value = os.environ.get("MGPU_JSON_OVERWRITE", "true").strip().lower()
|
||||
overwrite_enabled = overwrite_value not in {"0", "false", "no"}
|
||||
|
||||
logger.addHandler(JsonLineFileHandler(json_log_path, json_static_fields, json_min_level, overwrite_enabled))
|
||||
|
||||
def mgpu_mm_log_method(self, msg):
|
||||
"""Add MultiGPU model management logging method to logger instance."""
|
||||
if MGPU_MM_LOG:
|
||||
self.info(f"[MultiGPU Model Management] {msg}")
|
||||
self.focus(
|
||||
f"[MultiGPU Model Management] {msg}",
|
||||
extra={"mgpu_context": {"component": "model_management"}},
|
||||
)
|
||||
logger.mgpu_mm_log = mgpu_mm_log_method.__get__(logger, type(logger))
|
||||
|
||||
def check_module_exists(module_path):
|
||||
@@ -95,8 +190,6 @@ mm.get_torch_device = get_torch_device_patched
|
||||
mm.text_encoder_device = text_encoder_device_patched
|
||||
|
||||
from .nodes import (
|
||||
DeviceSelectorMultiGPU,
|
||||
HunyuanVideoEmbeddingsAdapter,
|
||||
UnetLoaderGGUF,
|
||||
UnetLoaderGGUFAdvanced,
|
||||
CLIPLoaderGGUF,
|
||||
@@ -114,21 +207,30 @@ from .nodes import (
|
||||
PulidModelLoader,
|
||||
PulidInsightFaceLoader,
|
||||
PulidEvaClipLoader,
|
||||
HyVideoModelLoader,
|
||||
HyVideoVAELoader,
|
||||
DownloadAndLoadHyVideoTextEncoder,
|
||||
UNetLoaderLP,
|
||||
)
|
||||
|
||||
from .wanvideo import (
|
||||
WanVideoModelLoader,
|
||||
WanVideoModelLoader_2,
|
||||
WanVideoVAELoader,
|
||||
LoadWanVideoT5TextEncoder,
|
||||
LoadWanVideoClipTextEncoder,
|
||||
WanVideoTextEncode,
|
||||
WanVideoTextEncodeCached,
|
||||
WanVideoTextEncodeSingle,
|
||||
WanVideoVAELoader,
|
||||
WanVideoTinyVAELoader,
|
||||
WanVideoBlockSwap,
|
||||
WanVideoSampler
|
||||
WanVideoImageToVideoEncode,
|
||||
WanVideoDecode,
|
||||
WanVideoModelLoader,
|
||||
WanVideoSampler,
|
||||
WanVideoVACEEncode,
|
||||
WanVideoEncode,
|
||||
LoadWanVideoClipTextEncoder,
|
||||
WanVideoClipVisionEncode,
|
||||
WanVideoControlnetLoader,
|
||||
FantasyTalkingModelLoader,
|
||||
Wav2VecModelLoader,
|
||||
WanVideoUni3C_ControlnetLoader,
|
||||
DownloadAndLoadWav2VecModel,
|
||||
)
|
||||
|
||||
from .wrappers import (
|
||||
@@ -158,8 +260,6 @@ from .checkpoint_multigpu import (
|
||||
)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"DeviceSelectorMultiGPU": DeviceSelectorMultiGPU,
|
||||
"HunyuanVideoEmbeddingsAdapter": HunyuanVideoEmbeddingsAdapter,
|
||||
"CheckpointLoaderAdvancedMultiGPU": CheckpointLoaderAdvancedMultiGPU,
|
||||
"CheckpointLoaderAdvancedDisTorch2MultiGPU": CheckpointLoaderAdvancedDisTorch2MultiGPU,
|
||||
"UNetLoaderLP": UNetLoaderLP,
|
||||
@@ -266,22 +366,27 @@ pulid_nodes = {
|
||||
}
|
||||
register_and_count(["PuLID_ComfyUI", "pulid_comfyui"], pulid_nodes)
|
||||
|
||||
hunyuan_nodes = {
|
||||
"HyVideoModelLoaderMultiGPU": override_class(HyVideoModelLoader),
|
||||
"HyVideoVAELoaderMultiGPU": override_class(HyVideoVAELoader),
|
||||
"DownloadAndLoadHyVideoTextEncoderMultiGPU": override_class(DownloadAndLoadHyVideoTextEncoder)
|
||||
}
|
||||
register_and_count(["ComfyUI-HunyuanVideoWrapper", "comfyui-hunyuanvideowrapper"], hunyuan_nodes)
|
||||
|
||||
wanvideo_nodes = {
|
||||
"WanVideoModelLoaderMultiGPU": WanVideoModelLoader,
|
||||
"WanVideoModelLoaderMultiGPU_2": WanVideoModelLoader_2,
|
||||
"WanVideoVAELoaderMultiGPU": WanVideoVAELoader,
|
||||
"LoadWanVideoT5TextEncoderMultiGPU": LoadWanVideoT5TextEncoder,
|
||||
"LoadWanVideoClipTextEncoderMultiGPU": LoadWanVideoClipTextEncoder,
|
||||
"WanVideoTextEncodeMultiGPU": WanVideoTextEncode,
|
||||
"WanVideoTextEncodeCachedMultiGPU": WanVideoTextEncodeCached,
|
||||
"WanVideoTextEncodeSingleMultiGPU": WanVideoTextEncodeSingle,
|
||||
"WanVideoVAELoaderMultiGPU": WanVideoVAELoader,
|
||||
"WanVideoTinyVAELoaderMultiGPU": WanVideoTinyVAELoader,
|
||||
"WanVideoBlockSwapMultiGPU": WanVideoBlockSwap,
|
||||
"WanVideoSamplerMultiGPU": WanVideoSampler
|
||||
"WanVideoImageToVideoEncodeMultiGPU": WanVideoImageToVideoEncode,
|
||||
"WanVideoDecodeMultiGPU": WanVideoDecode,
|
||||
"WanVideoModelLoaderMultiGPU": WanVideoModelLoader,
|
||||
"WanVideoSamplerMultiGPU": WanVideoSampler,
|
||||
"WanVideoVACEEncodeMultiGPU": WanVideoVACEEncode,
|
||||
"WanVideoEncodeMultiGPU": WanVideoEncode,
|
||||
"LoadWanVideoClipTextEncoderMultiGPU": LoadWanVideoClipTextEncoder,
|
||||
"WanVideoClipVisionEncodeMultiGPU": WanVideoClipVisionEncode,
|
||||
"WanVideoControlnetLoaderMultiGPU": WanVideoControlnetLoader,
|
||||
"FantasyTalkingModelLoaderMultiGPU": FantasyTalkingModelLoader,
|
||||
"Wav2VecModelLoaderMultiGPU": Wav2VecModelLoader,
|
||||
"WanVideoUni3C_ControlnetLoaderMultiGPU": WanVideoUni3C_ControlnetLoader,
|
||||
"DownloadAndLoadWav2VecModelMultiGPU": DownloadAndLoadWav2VecModel,
|
||||
}
|
||||
register_and_count(["ComfyUI-WanVideoWrapper", "comfyui-wanvideowrapper"], wanvideo_nodes)
|
||||
|
||||
@@ -289,4 +394,4 @@ for item in registration_data:
|
||||
logger.info(fmt_reg.format(item['name'], item['found'], str(item['count'])))
|
||||
logger.info(dash_line)
|
||||
|
||||
logger.info(f"[MultiGPU] Registration complete. Final mappings: {', '.join(NODE_CLASS_MAPPINGS.keys())}")
|
||||
logger.info(f"[MultiGPU] Registration complete. Final mappings: {', '.join(NODE_CLASS_MAPPINGS.keys())}")
|
||||
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 738 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 768 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 547 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 731 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 623 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Filter MultiGPU JSON logs for allocation summaries."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Iterator, Dict, Any
|
||||
|
||||
|
||||
def load_json_lines(path: Path) -> Iterator[Dict[str, Any]]:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
yield json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
|
||||
def is_allocation_event(entry: Dict[str, Any], keywords: Iterable[str]) -> bool:
|
||||
message = entry.get("message", "")
|
||||
return any(keyword in message for keyword in keywords)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Extract allocation-related events from MultiGPU JSON logs")
|
||||
parser.add_argument("logfile", type=Path, help="Path to JSONL log produced by MGPU_JSON_LOG_PATH")
|
||||
parser.add_argument(
|
||||
"--keywords",
|
||||
nargs="*",
|
||||
default=["Final Allocation String", "Total memory", "Virtual VRAM"],
|
||||
help="Keywords that mark allocation events",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
entries = list(load_json_lines(args.logfile))
|
||||
if not entries:
|
||||
print("No entries found in log file.")
|
||||
return 0
|
||||
|
||||
matched = [entry for entry in entries if is_allocation_event(entry, args.keywords)]
|
||||
if not matched:
|
||||
print("No allocation events matched provided keywords.")
|
||||
return 0
|
||||
|
||||
for entry in matched:
|
||||
timestamp = entry.get("timestamp", "unknown")
|
||||
category = entry.get("event_category", "")
|
||||
component = entry.get("component", "")
|
||||
header_bits = [bit for bit in (timestamp, category, component) if bit]
|
||||
header = " | ".join(header_bits) if header_bits else "allocation"
|
||||
print(f"## {header}")
|
||||
print(entry.get("message", ""))
|
||||
print()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal ComfyUI workflow runner for CI smoke tests."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Iterable, Optional
|
||||
|
||||
import requests
|
||||
import websocket
|
||||
|
||||
DEFAULT_HOST = os.environ.get("COMFYUI_HOST", "127.0.0.1")
|
||||
DEFAULT_PORT = int(os.environ.get("COMFYUI_PORT", "8188"))
|
||||
DEFAULT_CONNECT_TIMEOUT = int(os.environ.get("COMFYUI_CONNECT_TIMEOUT", "60"))
|
||||
DEFAULT_WORKFLOW_TIMEOUT = int(os.environ.get("COMFYUI_WORKFLOW_TIMEOUT", "900"))
|
||||
|
||||
|
||||
class ComfyWorkflowRunner:
|
||||
def __init__(self, host: str, port: int, connect_timeout: int, workflow_timeout: int, secure: bool = False) -> None:
|
||||
self.host = host
|
||||
self.port = port
|
||||
protocol_http = "https" if secure else "http"
|
||||
protocol_ws = "wss" if secure else "ws"
|
||||
self.base_http = f"{protocol_http}://{host}:{port}"
|
||||
self.base_ws = f"{protocol_ws}://{host}:{port}/ws"
|
||||
self.connect_timeout = connect_timeout
|
||||
self.workflow_timeout = workflow_timeout
|
||||
self.client_id = str(uuid.uuid4())
|
||||
self.session = requests.Session()
|
||||
self.websocket: Optional[websocket.WebSocket] = None
|
||||
|
||||
def wait_for_server(self) -> None:
|
||||
deadline = time.monotonic() + self.connect_timeout
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
response = self.session.get(f"{self.base_http}/system_stats", timeout=5)
|
||||
if response.status_code == 200:
|
||||
return
|
||||
except requests.RequestException:
|
||||
time.sleep(1)
|
||||
raise TimeoutError(f"ComfyUI server not reachable at {self.base_http}")
|
||||
|
||||
def open_websocket(self) -> None:
|
||||
ws = websocket.WebSocket()
|
||||
ws.settimeout(5)
|
||||
ws.connect(f"{self.base_ws}?clientId={self.client_id}")
|
||||
self.websocket = ws
|
||||
|
||||
def close_websocket(self) -> None:
|
||||
if self.websocket:
|
||||
try:
|
||||
self.websocket.close()
|
||||
finally:
|
||||
self.websocket = None
|
||||
|
||||
def queue_prompt(self, prompt: dict) -> str:
|
||||
payload = {"prompt": prompt, "client_id": self.client_id}
|
||||
response = self.session.post(f"{self.base_http}/prompt", json=payload, timeout=15)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
prompt_id = data.get("prompt_id")
|
||||
if not prompt_id:
|
||||
raise RuntimeError("No prompt_id returned from ComfyUI")
|
||||
return prompt_id
|
||||
|
||||
def wait_for_completion(self, prompt_id: str) -> bool:
|
||||
if not self.websocket:
|
||||
raise RuntimeError("WebSocket connection not established")
|
||||
deadline = time.monotonic() + self.workflow_timeout
|
||||
ws = self.websocket
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
message = ws.recv()
|
||||
except websocket.WebSocketTimeoutException:
|
||||
continue
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"WebSocket error: {exc}", file=sys.stderr, flush=True)
|
||||
return False
|
||||
|
||||
if isinstance(message, bytes):
|
||||
continue
|
||||
|
||||
try:
|
||||
payload = json.loads(message)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
message_type = payload.get("type")
|
||||
data = payload.get("data", {})
|
||||
|
||||
if message_type == "execution_error":
|
||||
if data.get("prompt_id") == prompt_id:
|
||||
print(f"Execution error: {payload}", file=sys.stderr, flush=True)
|
||||
return False
|
||||
elif message_type == "status" and data.get("status") == "error":
|
||||
if data.get("prompt_id") == prompt_id:
|
||||
print(f"Status error: {payload}", file=sys.stderr, flush=True)
|
||||
return False
|
||||
elif message_type == "executing":
|
||||
if data.get("prompt_id") == prompt_id and data.get("node") is None:
|
||||
return True
|
||||
print("Workflow timed out", file=sys.stderr, flush=True)
|
||||
return False
|
||||
|
||||
def run_workflow(self, workflow_path: Path) -> bool:
|
||||
previous_workflow = os.environ.get("MGPU_JSON_WORKFLOW")
|
||||
previous_prompt = os.environ.get("MGPU_JSON_PROMPT")
|
||||
|
||||
def restore_env() -> None:
|
||||
if previous_workflow is None:
|
||||
os.environ.pop("MGPU_JSON_WORKFLOW", None)
|
||||
else:
|
||||
os.environ["MGPU_JSON_WORKFLOW"] = previous_workflow
|
||||
if previous_prompt is None:
|
||||
os.environ.pop("MGPU_JSON_PROMPT", None)
|
||||
else:
|
||||
os.environ["MGPU_JSON_PROMPT"] = previous_prompt
|
||||
|
||||
if workflow_path:
|
||||
os.environ["MGPU_JSON_WORKFLOW"] = workflow_path.name
|
||||
try:
|
||||
with workflow_path.open("r", encoding="utf-8") as handle:
|
||||
workflow = json.load(handle)
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
print(f"Failed to load workflow {workflow_path}: {exc}", file=sys.stderr, flush=True)
|
||||
restore_env()
|
||||
return False
|
||||
|
||||
print(f"Running workflow {workflow_path}", flush=True)
|
||||
start = time.monotonic()
|
||||
try:
|
||||
prompt_id = self.queue_prompt(workflow)
|
||||
os.environ["MGPU_JSON_PROMPT"] = prompt_id
|
||||
except requests.HTTPError as exc:
|
||||
print(f"HTTP error while queueing workflow: {exc}", file=sys.stderr, flush=True)
|
||||
restore_env()
|
||||
return False
|
||||
except requests.RequestException as exc:
|
||||
print(f"Request error while queueing workflow: {exc}", file=sys.stderr, flush=True)
|
||||
restore_env()
|
||||
return False
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), file=sys.stderr, flush=True)
|
||||
restore_env()
|
||||
return False
|
||||
|
||||
try:
|
||||
if not self.wait_for_completion(prompt_id):
|
||||
return False
|
||||
duration = time.monotonic() - start
|
||||
print(f"Workflow {workflow_path} completed in {duration:.2f}s", flush=True)
|
||||
return True
|
||||
finally:
|
||||
restore_env()
|
||||
|
||||
def run_suite(self, workflows: Iterable[Path], fail_fast: bool) -> bool:
|
||||
self.wait_for_server()
|
||||
self.open_websocket()
|
||||
try:
|
||||
overall = True
|
||||
for workflow in workflows:
|
||||
ok = self.run_workflow(workflow)
|
||||
if not ok:
|
||||
overall = False
|
||||
if fail_fast:
|
||||
break
|
||||
return overall
|
||||
finally:
|
||||
self.close_websocket()
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Run ComfyUI workflows via the HTTP/WebSocket API")
|
||||
parser.add_argument("workflows", nargs="+", type=Path, help="Workflow files in ComfyUI API JSON format")
|
||||
parser.add_argument("--host", default=DEFAULT_HOST, help="ComfyUI HTTP host")
|
||||
parser.add_argument("--port", type=int, default=DEFAULT_PORT, help="ComfyUI HTTP port")
|
||||
parser.add_argument("--connect-timeout", type=int, default=DEFAULT_CONNECT_TIMEOUT, help="Seconds to wait for the server to come online")
|
||||
parser.add_argument("--workflow-timeout", type=int, default=DEFAULT_WORKFLOW_TIMEOUT, help="Seconds to wait for each workflow to finish")
|
||||
parser.add_argument("--fail-fast", action="store_true", help="Stop on first workflow failure")
|
||||
parser.add_argument("--secure", action="store_true", help="Use secure HTTPS/WSS connections (default: insecure for localhost)")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
runner = ComfyWorkflowRunner(
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
connect_timeout=args.connect_timeout,
|
||||
workflow_timeout=args.workflow_timeout,
|
||||
secure=args.secure,
|
||||
)
|
||||
success = runner.run_suite(args.workflows, fail_fast=args.fail_fast)
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: COMFYUI_HOME=/path/to/ComfyUI ci/smoke_test.sh <workflow.json> [<workflow.json>...]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${COMFYUI_HOME:-}" ]]; then
|
||||
echo "COMFYUI_HOME environment variable must point to the ComfyUI checkout" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PYTHON_BIN=${PYTHON_BIN:-python3}
|
||||
HOST=${COMFYUI_HOST:-127.0.0.1}
|
||||
PORT=${COMFYUI_PORT:-8188}
|
||||
LOG_FILE=${COMFYUI_LOG:-comfyui_ci.log}
|
||||
|
||||
pushd "${COMFYUI_HOME}" >/dev/null
|
||||
|
||||
${PYTHON_BIN} -m pip install --upgrade pip >/dev/null
|
||||
${PYTHON_BIN} -m pip install -r requirements.txt >/dev/null
|
||||
|
||||
${PYTHON_BIN} main.py --disable-auto-launch --listen "${HOST}" --port "${PORT}" >"${LOG_FILE}" 2>&1 &
|
||||
SERVER_PID=$!
|
||||
trap 'kill ${SERVER_PID} >/dev/null 2>&1 || true' EXIT
|
||||
|
||||
popd >/dev/null
|
||||
|
||||
"${PYTHON_BIN}" "$(dirname "$0")/run_workflows.py" --host "${HOST}" --port "${PORT}" "$@"
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert MultiGPU JSON log into a Markdown summary."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Iterator, Dict, Any
|
||||
|
||||
|
||||
def load_json_lines(path: Path) -> Iterator[Dict[str, Any]]:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
yield json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Summarize MultiGPU JSON logs into Markdown")
|
||||
parser.add_argument("logfile", type=Path, help="Path to JSONL log produced by MGPU_JSON_LOG_PATH")
|
||||
parser.add_argument("--severity", nargs="*", help="Optional severity levels to include (e.g. INFO WARN ERROR)")
|
||||
parser.add_argument(
|
||||
"--component",
|
||||
nargs="*",
|
||||
help="Optional component names to include (matches component or event_category fields)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
entries = list(load_json_lines(args.logfile))
|
||||
if not entries:
|
||||
print("No entries found in log file.")
|
||||
return 0
|
||||
|
||||
print("| Timestamp | Level | Component | Message |")
|
||||
print("| --- | --- | --- | --- |")
|
||||
for entry in entries:
|
||||
level = entry.get("level", "")
|
||||
if args.severity and level not in args.severity:
|
||||
continue
|
||||
component_values = {
|
||||
entry.get("component", ""),
|
||||
entry.get("event_category", ""),
|
||||
}
|
||||
component = next((value for value in component_values if value), "")
|
||||
if args.component and component not in args.component:
|
||||
continue
|
||||
timestamp = entry.get("timestamp", "")
|
||||
message = entry.get("message", "").replace("|", "\u2502")
|
||||
print(f"| {timestamp} | {level} | {component} | {message} |")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+15
-21
@@ -17,7 +17,7 @@ def get_device_list():
|
||||
|
||||
Returns a comprehensive list of all available devices across all types:
|
||||
- CPU (always available)
|
||||
- CUDA devices (NVIDIA GPUs)
|
||||
- CUDA devices (NVIDIA GPUs + AMD w/ ROCm GPUs)
|
||||
- XPU devices (Intel GPUs)
|
||||
- NPU devices (Ascend NPUs from Huawei)
|
||||
- MLU devices (Cambricon MLUs)
|
||||
@@ -237,32 +237,28 @@ def soft_empty_cache_distorch2_patched(force=False):
|
||||
from .model_management_mgpu import multigpu_memory_log, check_cpu_memory_threshold, trigger_executor_cache_reset
|
||||
from .distorch_2 import safetensor_allocation_store, create_safetensor_model_hash
|
||||
|
||||
multigpu_memory_log("patched_soft_empty", f"start:force={force}")
|
||||
is_distorch_active = False
|
||||
|
||||
# Detect DisTorch2-managed models
|
||||
logger.mgpu_mm_log(f"[DETECT_DEBUG] Checking DisTorch2 active status - loaded models: {len(mm.current_loaded_models)}, store entries: {len(safetensor_allocation_store)}")
|
||||
# logger.mgpu_mm_log(f"[DETECT_DEBUG] Checking DisTorch2 active status - loaded models: {len(mm.current_loaded_models)}, store entries: {len(safetensor_allocation_store)}")
|
||||
|
||||
for i, lm in enumerate(mm.current_loaded_models):
|
||||
mp = lm.model # weakref call to ModelPatcher
|
||||
if mp is not None:
|
||||
try:
|
||||
model_hash = create_safetensor_model_hash(mp, "cache_patch_check")
|
||||
in_store = model_hash in safetensor_allocation_store
|
||||
alloc_value = safetensor_allocation_store.get(model_hash, "")
|
||||
model_name = type(getattr(mp, 'model', mp)).__name__
|
||||
unload_distorch_model = getattr(getattr(mp, 'model', None), '_mgpu_unload_distorch_model', False)
|
||||
|
||||
logger.mgpu_mm_log(f"[DETECT_DEBUG] Model {i}: {model_name}, hash={model_hash[:8]}, in_store={in_store}, alloc_value='{alloc_value}', unload_distorch_model={unload_distorch_model}")
|
||||
|
||||
if in_store and alloc_value:
|
||||
is_distorch_active = True
|
||||
logger.mgpu_mm_log(f"[DETECT_DEBUG] DisTorch2 ACTIVE detected on model: {model_name}")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.mgpu_mm_log(f"[DETECT_DEBUG] Model {i}: Error during detection - {e}")
|
||||
model_hash = create_safetensor_model_hash(mp, "cache_patch_check")
|
||||
in_store = model_hash in safetensor_allocation_store
|
||||
alloc_value = safetensor_allocation_store.get(model_hash, "")
|
||||
model_name = type(getattr(mp, 'model', mp)).__name__
|
||||
unload_distorch_model = getattr(getattr(mp, 'model', None), '_mgpu_unload_distorch_model', False)
|
||||
|
||||
#logger.mgpu_mm_log(f"[DETECT_DEBUG] Model {i}: {model_name}, hash={model_hash[:8]}, in_store={in_store}, alloc_value='{alloc_value}', unload_distorch_model={unload_distorch_model}")
|
||||
|
||||
if in_store and alloc_value:
|
||||
is_distorch_active = True
|
||||
#logger.mgpu_mm_log(f"[DETECT_DEBUG] DisTorch2 ACTIVE detected on model: {model_name}")
|
||||
break
|
||||
|
||||
logger.mgpu_mm_log(f"[DETECT_DEBUG] Final DisTorch2 active status: {is_distorch_active}")
|
||||
#logger.mgpu_mm_log(f"[DETECT_DEBUG] Final DisTorch2 active status: {is_distorch_active}")
|
||||
|
||||
# Phase 2: adaptive CPU memory management
|
||||
check_cpu_memory_threshold()
|
||||
@@ -272,7 +268,6 @@ def soft_empty_cache_distorch2_patched(force=False):
|
||||
logger.mgpu_mm_log("DisTorch2 active: clearing allocator caches on all devices (VRAM)")
|
||||
soft_empty_cache_multigpu()
|
||||
else:
|
||||
logger.mgpu_mm_log("DisTorch2 not active: delegating allocator cache clear (VRAM) to original mm.soft_empty_cache")
|
||||
original_soft_empty_cache(force)
|
||||
# Optional: return CPU heap to OS (not part of Comfy Core)
|
||||
|
||||
@@ -280,7 +275,6 @@ def soft_empty_cache_distorch2_patched(force=False):
|
||||
if force:
|
||||
logger.mgpu_mm_log("Force flag active: triggering executor cache reset (CPU)")
|
||||
trigger_executor_cache_reset(reason="forced_soft_empty", force=True)
|
||||
multigpu_memory_log("patched_soft_empty", "end")
|
||||
|
||||
mm.soft_empty_cache = soft_empty_cache_distorch2_patched
|
||||
|
||||
|
||||
@@ -1,717 +0,0 @@
|
||||
{
|
||||
"last_node_id": 51,
|
||||
"last_link_id": 70,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "HyVideoVAELoader",
|
||||
"pos": [
|
||||
-980.2922973632812,
|
||||
-830.076171875
|
||||
],
|
||||
"size": [
|
||||
379.166748046875,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 4,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "compile_args",
|
||||
"type": "COMPILEARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"links": [],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoVAELoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_vae_bf16.safetensors",
|
||||
"bf16"
|
||||
],
|
||||
"color": "#332922",
|
||||
"bgcolor": "#593930"
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"type": "HyVideoTextEncode",
|
||||
"pos": [
|
||||
-194.8070831298828,
|
||||
-79.95932006835938
|
||||
],
|
||||
"size": [
|
||||
425.64068603515625,
|
||||
286.85968017578125
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text_encoders",
|
||||
"type": "HYVIDTEXTENCODER",
|
||||
"link": 66
|
||||
},
|
||||
{
|
||||
"name": "custom_prompt_template",
|
||||
"type": "PROMPT_TEMPLATE",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "clip_l",
|
||||
"type": "CLIP",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "hyvid_cfg",
|
||||
"type": "HYVID_CFG",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "hyvid_embeds",
|
||||
"type": "HYVIDEMBEDS",
|
||||
"links": [
|
||||
36
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"A serene Minnesota lake stretches out at sunset, the water's surface a mirror reflecting the vibrant orange and pink sky. In the foreground, a pair of loons glide gracefully across the water, their sleek black and white feathers contrasting with the warm hues of the sunset. The loons' long, slender necks curve elegantly as they dip their heads into the water, searching for fish. The camera pans slowly from left to right, capturing the tranquil scene. The shoreline is visible in the distance, lined with tall pine trees that cast long shadows across the water. The loons' haunting calls echo across the lake, adding to the peaceful atmosphere.",
|
||||
false,
|
||||
"video"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"type": "DownloadAndLoadHyVideoTextEncoderMultiGPU",
|
||||
"pos": [
|
||||
-745.2869262695312,
|
||||
-80.3648452758789
|
||||
],
|
||||
"size": [
|
||||
516.5999755859375,
|
||||
202
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "hyvid_text_encoder",
|
||||
"type": "HYVIDTEXTENCODER",
|
||||
"links": [
|
||||
66
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DownloadAndLoadHyVideoTextEncoderMultiGPU"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Kijai/llava-llama-3-8b-text-encoder-tokenizer",
|
||||
"disabled",
|
||||
"bf16",
|
||||
false,
|
||||
2,
|
||||
"disabled",
|
||||
"cuda:1"
|
||||
],
|
||||
"color": "#233",
|
||||
"bgcolor": "#355"
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"type": "HyVideoModelLoaderMultiGPU",
|
||||
"pos": [
|
||||
-338.0295715332031,
|
||||
-403.1601257324219
|
||||
],
|
||||
"size": [
|
||||
497.3603210449219,
|
||||
252.03509521484375
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "compile_args",
|
||||
"type": "COMPILEARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "block_swap_args",
|
||||
"type": "BLOCKSWAPARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "lora",
|
||||
"type": "HYVIDLORA",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "HYVIDEOMODEL",
|
||||
"links": [
|
||||
65
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoModelLoaderMultiGPU"
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_720_cfgdistill_fp8_e4m3fn.safetensors",
|
||||
"fp32",
|
||||
"fp8_e4m3fn",
|
||||
"main_device",
|
||||
"sdpa",
|
||||
false,
|
||||
"cuda:0"
|
||||
],
|
||||
"color": "#233",
|
||||
"bgcolor": "#355"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "DownloadAndLoadHyVideoTextEncoder",
|
||||
"pos": [
|
||||
-1011.1117553710938,
|
||||
-1076.6143798828125
|
||||
],
|
||||
"size": [
|
||||
441,
|
||||
178
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 4,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "hyvid_text_encoder",
|
||||
"type": "HYVIDTEXTENCODER",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DownloadAndLoadHyVideoTextEncoder"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Kijai/llava-llama-3-8b-text-encoder-tokenizer",
|
||||
"openai/clip-vit-large-patch14",
|
||||
"fp16",
|
||||
false,
|
||||
2,
|
||||
"disabled"
|
||||
],
|
||||
"color": "#332922",
|
||||
"bgcolor": "#593930"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "HyVideoModelLoader",
|
||||
"pos": [
|
||||
-557.619384765625,
|
||||
-1092.4908447265625
|
||||
],
|
||||
"size": [
|
||||
435.37628173828125,
|
||||
221.34506225585938
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 4,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "compile_args",
|
||||
"type": "COMPILEARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "block_swap_args",
|
||||
"type": "BLOCKSWAPARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "lora",
|
||||
"type": "HYVIDLORA",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "HYVIDEOMODEL",
|
||||
"links": [],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoModelLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_720_cfgdistill_fp8_e4m3fn.safetensors",
|
||||
"bf16",
|
||||
"fp8_e4m3fn",
|
||||
"main_device",
|
||||
"sdpa",
|
||||
false
|
||||
],
|
||||
"color": "#332922",
|
||||
"bgcolor": "#593930"
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"type": "VHS_VideoCombine",
|
||||
"pos": [
|
||||
847.0758666992188,
|
||||
-415.1882629394531
|
||||
],
|
||||
"size": [
|
||||
580.7774658203125,
|
||||
698.4859008789062
|
||||
],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 69
|
||||
},
|
||||
{
|
||||
"name": "audio",
|
||||
"type": "AUDIO",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "meta_batch",
|
||||
"type": "VHS_BatchManager",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "Filenames",
|
||||
"type": "VHS_FILENAMES",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VHS_VideoCombine"
|
||||
},
|
||||
"widgets_values": {
|
||||
"frame_rate": 24,
|
||||
"loop_count": 0,
|
||||
"filename_prefix": "HunyuanVideo",
|
||||
"format": "video/h264-mp4",
|
||||
"pix_fmt": "yuv420p",
|
||||
"crf": 19,
|
||||
"save_metadata": true,
|
||||
"trim_to_audio": false,
|
||||
"pingpong": false,
|
||||
"save_output": true,
|
||||
"videopreview": {
|
||||
"hidden": false,
|
||||
"paused": false,
|
||||
"params": {
|
||||
"filename": "HunyuanVideo_00161.mp4",
|
||||
"subfolder": "",
|
||||
"type": "output",
|
||||
"format": "video/h264-mp4",
|
||||
"frame_rate": 24,
|
||||
"workflow": "HunyuanVideo_00161.png",
|
||||
"fullpath": "/home/johnj/ComfyUI/output/HunyuanVideo_00161.mp4"
|
||||
},
|
||||
"muted": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"type": "VAEDecodeTiled",
|
||||
"pos": [
|
||||
532.4385986328125,
|
||||
-644.8389282226562
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 70
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 56
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
69
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecodeTiled"
|
||||
},
|
||||
"widgets_values": [
|
||||
256,
|
||||
64,
|
||||
64,
|
||||
8
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"type": "VAELoaderMultiGPU",
|
||||
"pos": [
|
||||
-230.99607849121094,
|
||||
-639.45654296875
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
56
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAELoaderMultiGPU"
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_vae_bf16.safetensors",
|
||||
"cuda:1"
|
||||
],
|
||||
"color": "#233",
|
||||
"bgcolor": "#355"
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"type": "HyVideoVAELoaderMultiGPU",
|
||||
"pos": [
|
||||
-512.9594116210938,
|
||||
-822.8096313476562
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
106
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 4,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "compile_args",
|
||||
"type": "COMPILEARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
64
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoVAELoaderMultiGPU"
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_vae_bf16.safetensors",
|
||||
"bf16",
|
||||
"cuda:1"
|
||||
],
|
||||
"color": "#233",
|
||||
"bgcolor": "#355"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "HyVideoDecode",
|
||||
"pos": [
|
||||
-106.15780639648438,
|
||||
-843.2271728515625
|
||||
],
|
||||
"size": [
|
||||
345.4285888671875,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 4,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 64
|
||||
},
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"links": [],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoDecode"
|
||||
},
|
||||
"widgets_values": [
|
||||
true,
|
||||
86,
|
||||
256,
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "HyVideoSampler",
|
||||
"pos": [
|
||||
255.96482849121094,
|
||||
-403.58502197265625
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
630
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "HYVIDEOMODEL",
|
||||
"link": 65
|
||||
},
|
||||
{
|
||||
"name": "hyvid_embeds",
|
||||
"type": "HYVIDEMBEDS",
|
||||
"link": 36
|
||||
},
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "stg_args",
|
||||
"type": "STGARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "context_options",
|
||||
"type": "COGCONTEXT",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "feta_args",
|
||||
"type": "FETAARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "teacache_args",
|
||||
"type": "TEACACHEARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
70
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
320,
|
||||
85,
|
||||
20,
|
||||
6,
|
||||
9,
|
||||
5770521,
|
||||
"fixed",
|
||||
false,
|
||||
1,
|
||||
"FlowMatchDiscreteScheduler"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
449.2930908203125,
|
||||
-955.3724975585938
|
||||
],
|
||||
"size": [
|
||||
1160.6077880859375,
|
||||
211.52166748046875
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"kijai already does an impressive amount of memory management in these nodes, so it is important for MultiGPU to \"play nice\" and vice-versa.\n\nFor this version of the workflow:\n\n• Only two of kijai's three nodes are used - model and text. For VAE we relying on Comfy's native VAE Loader node wrapped by MultiGPU.\n\n• HunyuanVideo TextEncode: \"force_offload\" is set to \"false\". Setting this option to \"true\" would defeat the purpose of selecting a different main_device to load to.\n\n• The main model and VAE devices are set to different cuda devices (cuda0 and cuda1). This is possible because the HunyuanVideo Sampler node outputs latents compatible with Comfy's native TiledVAE Decode.\n• Consequentially, now that we no longer have any other component on cuda0, we set \"force_offload\" to \"false\" on the \"HunyuanVideo Sampler\" node. This keeps the main model in VRAM, eliminating load times for the subsequent generations.\n\n**NOTE** This the **OPTIMAL** way to use MultiGPU with kijai's awesome nodes. If you want to use all three of kijai's loader nodes, please see device_selector_lowvram_flux_controlnet.json in ./examples"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
36,
|
||||
30,
|
||||
0,
|
||||
3,
|
||||
1,
|
||||
"HYVIDEMBEDS"
|
||||
],
|
||||
[
|
||||
56,
|
||||
44,
|
||||
0,
|
||||
45,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
64,
|
||||
47,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
65,
|
||||
48,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"HYVIDEOMODEL"
|
||||
],
|
||||
[
|
||||
66,
|
||||
49,
|
||||
0,
|
||||
30,
|
||||
0,
|
||||
"HYVIDTEXTENCODER"
|
||||
],
|
||||
[
|
||||
69,
|
||||
45,
|
||||
0,
|
||||
34,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
70,
|
||||
3,
|
||||
0,
|
||||
45,
|
||||
0,
|
||||
"LATENT"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8769226950001201,
|
||||
"offset": [
|
||||
895.3038892918638,
|
||||
1121.109618095784
|
||||
]
|
||||
},
|
||||
"ue_links": [],
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -1,784 +0,0 @@
|
||||
{
|
||||
"last_node_id": 51,
|
||||
"last_link_id": 68,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "HyVideoVAELoader",
|
||||
"pos": [
|
||||
-980.2922973632812,
|
||||
-830.076171875
|
||||
],
|
||||
"size": [
|
||||
379.166748046875,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 4,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "compile_args",
|
||||
"type": "COMPILEARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"links": [],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoVAELoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_vae_bf16.safetensors",
|
||||
"bf16"
|
||||
],
|
||||
"color": "#332922",
|
||||
"bgcolor": "#593930"
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"type": "HyVideoTextEncode",
|
||||
"pos": [
|
||||
-194.8070831298828,
|
||||
-79.95932006835938
|
||||
],
|
||||
"size": [
|
||||
425.64068603515625,
|
||||
286.85968017578125
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text_encoders",
|
||||
"type": "HYVIDTEXTENCODER",
|
||||
"link": 66
|
||||
},
|
||||
{
|
||||
"name": "custom_prompt_template",
|
||||
"type": "PROMPT_TEMPLATE",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "clip_l",
|
||||
"type": "CLIP",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "hyvid_cfg",
|
||||
"type": "HYVID_CFG",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "hyvid_embeds",
|
||||
"type": "HYVIDEMBEDS",
|
||||
"links": [
|
||||
36
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"A serene Minnesota lake stretches out at sunset, the water's surface a mirror reflecting the vibrant orange and pink sky. In the foreground, a pair of loons glide gracefully across the water, their sleek black and white feathers contrasting with the warm hues of the sunset. The loons' long, slender necks curve elegantly as they dip their heads into the water, searching for fish. The camera pans slowly from left to right, capturing the tranquil scene. The shoreline is visible in the distance, lined with tall pine trees that cast long shadows across the water. The loons' haunting calls echo across the lake, adding to the peaceful atmosphere.",
|
||||
false,
|
||||
"video"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"type": "DownloadAndLoadHyVideoTextEncoderMultiGPU",
|
||||
"pos": [
|
||||
-745.2869262695312,
|
||||
-80.3648452758789
|
||||
],
|
||||
"size": [
|
||||
516.5999755859375,
|
||||
202
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "hyvid_text_encoder",
|
||||
"type": "HYVIDTEXTENCODER",
|
||||
"links": [
|
||||
66
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DownloadAndLoadHyVideoTextEncoderMultiGPU"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Kijai/llava-llama-3-8b-text-encoder-tokenizer",
|
||||
"disabled",
|
||||
"bf16",
|
||||
false,
|
||||
2,
|
||||
"disabled",
|
||||
"cuda:1"
|
||||
],
|
||||
"color": "#233",
|
||||
"bgcolor": "#355"
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"type": "HyVideoModelLoaderMultiGPU",
|
||||
"pos": [
|
||||
-338.0295715332031,
|
||||
-403.1601257324219
|
||||
],
|
||||
"size": [
|
||||
497.3603210449219,
|
||||
252.03509521484375
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "compile_args",
|
||||
"type": "COMPILEARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "block_swap_args",
|
||||
"type": "BLOCKSWAPARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "lora",
|
||||
"type": "HYVIDLORA",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "device",
|
||||
"type": "COMBO",
|
||||
"link": 67,
|
||||
"widget": {
|
||||
"name": "device"
|
||||
},
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "HYVIDEOMODEL",
|
||||
"links": [
|
||||
65
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoModelLoaderMultiGPU"
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_720_cfgdistill_fp8_e4m3fn.safetensors",
|
||||
"fp32",
|
||||
"fp8_e4m3fn",
|
||||
"main_device",
|
||||
"sdpa",
|
||||
false,
|
||||
"cuda:0"
|
||||
],
|
||||
"color": "#233",
|
||||
"bgcolor": "#355"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "DownloadAndLoadHyVideoTextEncoder",
|
||||
"pos": [
|
||||
-1011.1117553710938,
|
||||
-1076.6143798828125
|
||||
],
|
||||
"size": [
|
||||
441,
|
||||
178
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 4,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "hyvid_text_encoder",
|
||||
"type": "HYVIDTEXTENCODER",
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DownloadAndLoadHyVideoTextEncoder"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Kijai/llava-llama-3-8b-text-encoder-tokenizer",
|
||||
"openai/clip-vit-large-patch14",
|
||||
"fp16",
|
||||
false,
|
||||
2,
|
||||
"disabled"
|
||||
],
|
||||
"color": "#332922",
|
||||
"bgcolor": "#593930"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "HyVideoModelLoader",
|
||||
"pos": [
|
||||
-557.619384765625,
|
||||
-1092.4908447265625
|
||||
],
|
||||
"size": [
|
||||
435.37628173828125,
|
||||
221.34506225585938
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 4,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "compile_args",
|
||||
"type": "COMPILEARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "block_swap_args",
|
||||
"type": "BLOCKSWAPARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "lora",
|
||||
"type": "HYVIDLORA",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "HYVIDEOMODEL",
|
||||
"links": [],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoModelLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_720_cfgdistill_fp8_e4m3fn.safetensors",
|
||||
"bf16",
|
||||
"fp8_e4m3fn",
|
||||
"main_device",
|
||||
"sdpa",
|
||||
false
|
||||
],
|
||||
"color": "#332922",
|
||||
"bgcolor": "#593930"
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"type": "VHS_VideoCombine",
|
||||
"pos": [
|
||||
847.0758666992188,
|
||||
-415.1882629394531
|
||||
],
|
||||
"size": [
|
||||
580.7774658203125,
|
||||
698.4859008789062
|
||||
],
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 63
|
||||
},
|
||||
{
|
||||
"name": "audio",
|
||||
"type": "AUDIO",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "meta_batch",
|
||||
"type": "VHS_BatchManager",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "Filenames",
|
||||
"type": "VHS_FILENAMES",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VHS_VideoCombine"
|
||||
},
|
||||
"widgets_values": {
|
||||
"frame_rate": 24,
|
||||
"loop_count": 0,
|
||||
"filename_prefix": "HunyuanVideo",
|
||||
"format": "video/h264-mp4",
|
||||
"pix_fmt": "yuv420p",
|
||||
"crf": 19,
|
||||
"save_metadata": true,
|
||||
"trim_to_audio": false,
|
||||
"pingpong": false,
|
||||
"save_output": true,
|
||||
"videopreview": {
|
||||
"hidden": false,
|
||||
"paused": false,
|
||||
"params": {
|
||||
"filename": "HunyuanVideo_00161.mp4",
|
||||
"subfolder": "",
|
||||
"type": "output",
|
||||
"format": "video/h264-mp4",
|
||||
"frame_rate": 24,
|
||||
"workflow": "HunyuanVideo_00161.png",
|
||||
"fullpath": "/home/johnj/ComfyUI/output/HunyuanVideo_00161.mp4"
|
||||
},
|
||||
"muted": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "HyVideoDecode",
|
||||
"pos": [
|
||||
538.94189453125,
|
||||
-625.3562622070312
|
||||
],
|
||||
"size": [
|
||||
345.4285888671875,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 64
|
||||
},
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
63
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoDecode"
|
||||
},
|
||||
"widgets_values": [
|
||||
true,
|
||||
86,
|
||||
256,
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"type": "DeviceSelectorMultiGPU",
|
||||
"pos": [
|
||||
-716.0182495117188,
|
||||
-492.63983154296875
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "device",
|
||||
"type": "COMBO",
|
||||
"links": [
|
||||
67,
|
||||
68
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DeviceSelectorMultiGPU"
|
||||
},
|
||||
"widgets_values": [
|
||||
"cuda:0"
|
||||
],
|
||||
"color": "#233",
|
||||
"bgcolor": "#355"
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"type": "HyVideoVAELoaderMultiGPU",
|
||||
"pos": [
|
||||
-313.7779846191406,
|
||||
-620.58740234375
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
106
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "compile_args",
|
||||
"type": "COMPILEARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "device",
|
||||
"type": "COMBO",
|
||||
"link": 68,
|
||||
"widget": {
|
||||
"name": "device"
|
||||
},
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
64
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoVAELoaderMultiGPU"
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_vae_bf16.safetensors",
|
||||
"bf16",
|
||||
"cuda:1"
|
||||
],
|
||||
"color": "#233",
|
||||
"bgcolor": "#355"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "HyVideoSampler",
|
||||
"pos": [
|
||||
255.96482849121094,
|
||||
-403.58502197265625
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
630
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "HYVIDEOMODEL",
|
||||
"link": 65
|
||||
},
|
||||
{
|
||||
"name": "hyvid_embeds",
|
||||
"type": "HYVIDEMBEDS",
|
||||
"link": 36
|
||||
},
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "stg_args",
|
||||
"type": "STGARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "context_options",
|
||||
"type": "COGCONTEXT",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "feta_args",
|
||||
"type": "FETAARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "teacache_args",
|
||||
"type": "TEACACHEARGS",
|
||||
"link": null,
|
||||
"shape": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "HyVideoSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
320,
|
||||
85,
|
||||
20,
|
||||
6,
|
||||
9,
|
||||
5770521,
|
||||
"fixed",
|
||||
true,
|
||||
1,
|
||||
"FlowMatchDiscreteScheduler"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"type": "VAELoaderMultiGPU",
|
||||
"pos": [
|
||||
-556.3764038085938,
|
||||
-809.7488403320312
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 4,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
56
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAELoaderMultiGPU"
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_vae_bf16.safetensors",
|
||||
"cuda:0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"type": "VAEDecodeTiled",
|
||||
"pos": [
|
||||
-195.86590576171875,
|
||||
-828.8155517578125
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 4,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 56
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecodeTiled"
|
||||
},
|
||||
"widgets_values": [
|
||||
256,
|
||||
64,
|
||||
64,
|
||||
8
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
217.09609985351562,
|
||||
-905.6065063476562
|
||||
],
|
||||
"size": [
|
||||
1160.6077880859375,
|
||||
211.52166748046875
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"kijai already does an impressive amount of memory management in these nodes, so it is important for MultiGPU to \"play nice\" and vice-versa.\n\nFor this version of the workflow:\n\n• All three of kijai's nodes are used - model, text, and VAE\n\n• HunyuanVideo TextEncode: \"force_offload\" is set to \"false\". Setting this option to \"true\" would defeat the purpose of selecting a different main_device to load to.\n\n• The main model and VAE devices are linked. This is because kijai's \"HunyuanVideo Decode\" expects both the model and the VAE to be on the same device. \n• Consequentially, to eliminate out-of-memory errors, \"force_offload\" is set to \"true\" on the \"HunyuanVideo Sampler\" node.\n\n\n**NOTE** This is not the optimial way to use MultiGPU. Please see the workflow at for an example of loading the VAE to a different cuda device using the native VAE loader and tiled decode."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
4,
|
||||
3,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
36,
|
||||
30,
|
||||
0,
|
||||
3,
|
||||
1,
|
||||
"HYVIDEMBEDS"
|
||||
],
|
||||
[
|
||||
56,
|
||||
44,
|
||||
0,
|
||||
45,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
63,
|
||||
5,
|
||||
0,
|
||||
34,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
64,
|
||||
47,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
65,
|
||||
48,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"HYVIDEOMODEL"
|
||||
],
|
||||
[
|
||||
66,
|
||||
49,
|
||||
0,
|
||||
30,
|
||||
0,
|
||||
"HYVIDTEXTENCODER"
|
||||
],
|
||||
[
|
||||
67,
|
||||
50,
|
||||
0,
|
||||
48,
|
||||
3,
|
||||
"COMBO"
|
||||
],
|
||||
[
|
||||
68,
|
||||
50,
|
||||
0,
|
||||
47,
|
||||
1,
|
||||
"COMBO"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.7972024500001089,
|
||||
"offset": [
|
||||
1218.3831555808085,
|
||||
1163.8844215880747
|
||||
]
|
||||
},
|
||||
"ue_links": [],
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,60 +5,6 @@ from nodes import NODE_CLASS_MAPPINGS
|
||||
from .device_utils import get_device_list
|
||||
from .model_management_mgpu import force_full_system_cleanup
|
||||
|
||||
class DeviceSelectorMultiGPU:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
devices = get_device_list()
|
||||
return {
|
||||
"required": {
|
||||
"device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0]})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (get_device_list(),)
|
||||
RETURN_NAMES = ("device",)
|
||||
FUNCTION = "select_device"
|
||||
CATEGORY = "multigpu"
|
||||
|
||||
def select_device(self, device):
|
||||
"""Select target device from available device list."""
|
||||
return (device,)
|
||||
|
||||
|
||||
class HunyuanVideoEmbeddingsAdapter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"hyvid_embeds": ("HYVIDEMBEDS",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
FUNCTION = "adapt_embeddings"
|
||||
CATEGORY = "multigpu"
|
||||
|
||||
def adapt_embeddings(self, hyvid_embeds):
|
||||
"""Adapt HunyuanVideo embeddings to standard ComfyUI conditioning format."""
|
||||
cond = hyvid_embeds["prompt_embeds"]
|
||||
|
||||
pooled_dict = {
|
||||
"pooled_output": hyvid_embeds["prompt_embeds_2"],
|
||||
"cross_attn": hyvid_embeds["prompt_embeds"],
|
||||
"attention_mask": hyvid_embeds["attention_mask"],
|
||||
}
|
||||
|
||||
if hyvid_embeds["attention_mask_2"] is not None:
|
||||
pooled_dict["attention_mask_controlnet"] = hyvid_embeds["attention_mask_2"]
|
||||
|
||||
if hyvid_embeds["cfg"] is not None:
|
||||
pooled_dict["guidance"] = float(hyvid_embeds["cfg"])
|
||||
pooled_dict["start_percent"] = float(hyvid_embeds["start_percent"]) if hyvid_embeds["start_percent"] is not None else 0.0
|
||||
pooled_dict["end_percent"] = float(hyvid_embeds["end_percent"]) if hyvid_embeds["end_percent"] is not None else 1.0
|
||||
|
||||
return ([[cond, pooled_dict]],)
|
||||
|
||||
|
||||
class UnetLoaderGGUF:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -465,97 +411,6 @@ class PulidEvaClipLoader:
|
||||
original_loader = NODE_CLASS_MAPPINGS["PulidEvaClipLoader"]()
|
||||
return original_loader.load_eva_clip()
|
||||
|
||||
|
||||
class HyVideoModelLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"model": (folder_paths.get_filename_list("diffusion_models"), {"tooltip": "These models are loaded from the 'ComfyUI/models/diffusion_models' -folder",}),
|
||||
"base_precision": (["fp32", "bf16"], {"default": "bf16"}),
|
||||
"quantization": (['disabled', 'fp8_e4m3fn', 'fp8_e4m3fn_fast', 'fp8_scaled', 'torchao_fp8dq', "torchao_fp8dqrow", "torchao_int8dq", "torchao_fp6", "torchao_int4", "torchao_int8"], {"default": 'disabled', "tooltip": "optional quantization method"}),
|
||||
"load_device": (["main_device"], {"default": "main_device"}),
|
||||
},
|
||||
"optional": {
|
||||
"attention_mode": ([
|
||||
"sdpa",
|
||||
"flash_attn_varlen",
|
||||
"sageattn_varlen",
|
||||
"comfy",
|
||||
], {"default": "flash_attn"}),
|
||||
"compile_args": ("COMPILEARGS", ),
|
||||
"block_swap_args": ("BLOCKSWAPARGS", ),
|
||||
"lora": ("HYVIDLORA", {"default": None}),
|
||||
"auto_cpu_offload": ("BOOLEAN", {"default": False, "tooltip": "Enable auto offloading for reduced VRAM usage, implementation from DiffSynth-Studio, slightly different from block swapping and uses even less VRAM, but can be slower as you can't define how much VRAM to use"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("HYVIDEOMODEL",)
|
||||
RETURN_NAMES = ("model", )
|
||||
FUNCTION = "loadmodel"
|
||||
CATEGORY = "HunyuanVideoWrapper"
|
||||
|
||||
def loadmodel(self, model, base_precision, load_device, quantization, compile_args=None, attention_mode="sdpa", block_swap_args=None, lora=None, auto_cpu_offload=False):
|
||||
"""Load HunyuanVideo model with specified precision and quantization."""
|
||||
original_loader = NODE_CLASS_MAPPINGS["HyVideoModelLoader"]()
|
||||
return original_loader.loadmodel(model, base_precision, load_device, quantization, compile_args, attention_mode, block_swap_args, lora, auto_cpu_offload)
|
||||
|
||||
class HyVideoVAELoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"model_name": (folder_paths.get_filename_list("vae"), {"tooltip": "These models are loaded from 'ComfyUI/models/vae'"}),
|
||||
},
|
||||
"optional": {
|
||||
"precision": (["fp16", "fp32", "bf16"],
|
||||
{"default": "bf16"}
|
||||
),
|
||||
"compile_args":("COMPILEARGS", ),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("VAE",)
|
||||
RETURN_NAMES = ("vae", )
|
||||
FUNCTION = "loadmodel"
|
||||
CATEGORY = "HunyuanVideoWrapper"
|
||||
DESCRIPTION = "Loads Hunyuan VAE model from 'ComfyUI/models/vae'"
|
||||
|
||||
def loadmodel(self, model_name, precision, compile_args=None):
|
||||
"""Load HunyuanVideo VAE model."""
|
||||
original_loader = NODE_CLASS_MAPPINGS["HyVideoVAELoader"]()
|
||||
return original_loader.loadmodel(model_name, precision, compile_args)
|
||||
|
||||
class DownloadAndLoadHyVideoTextEncoder:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"llm_model": (["Kijai/llava-llama-3-8b-text-encoder-tokenizer","xtuner/llava-llama-3-8b-v1_1-transformers"],),
|
||||
"clip_model": (["disabled","openai/clip-vit-large-patch14",],),
|
||||
"precision": (["fp16", "fp32", "bf16"],
|
||||
{"default": "bf16"}
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"apply_final_norm": ("BOOLEAN", {"default": False}),
|
||||
"hidden_state_skip_layer": ("INT", {"default": 2}),
|
||||
"quantization": (['disabled', 'bnb_nf4', "fp8_e4m3fn"], {"default": 'disabled'}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("HYVIDTEXTENCODER",)
|
||||
RETURN_NAMES = ("hyvid_text_encoder", )
|
||||
FUNCTION = "loadmodel"
|
||||
CATEGORY = "HunyuanVideoWrapper"
|
||||
DESCRIPTION = "Loads Hunyuan text_encoder model from 'ComfyUI/models/LLM'"
|
||||
|
||||
def loadmodel(self, llm_model, clip_model, precision, apply_final_norm=False, hidden_state_skip_layer=2, quantization="disabled"):
|
||||
"""Download and load HunyuanVideo text encoder from HuggingFace."""
|
||||
original_loader = NODE_CLASS_MAPPINGS["DownloadAndLoadHyVideoTextEncoder"]()
|
||||
return original_loader.loadmodel(llm_model, clip_model, precision, apply_final_norm, hidden_state_skip_layer, quantization)
|
||||
|
||||
|
||||
class UNetLoaderLP:
|
||||
"""UNet Loader (Low Precision) - sets LoRA precision to False for CPU storage optimization"""
|
||||
@classmethod
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-multigpu"
|
||||
description = "Provides a suite of custom nodes to manage multiple GPUs for ComfyUI, including advanced model offloading for both GGUF and Safetensor formats with DisTorch, and bespoke MultiGPU support for WanVideoWrapper and other custom nodes."
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
|
||||
+759
-406
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user