mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-04-19 23:12:40 +08:00
Merge upstream/master, keep local README.md
This commit is contained in:
commit
4107bebfe5
@ -17,7 +17,7 @@ from importlib.metadata import version
|
|||||||
import requests
|
import requests
|
||||||
from typing_extensions import NotRequired
|
from typing_extensions import NotRequired
|
||||||
|
|
||||||
from utils.install_util import get_missing_requirements_message, requirements_path
|
from utils.install_util import get_missing_requirements_message, get_required_packages_versions
|
||||||
|
|
||||||
from comfy.cli_args import DEFAULT_VERSION_STRING
|
from comfy.cli_args import DEFAULT_VERSION_STRING
|
||||||
import app.logger
|
import app.logger
|
||||||
@ -45,25 +45,7 @@ def get_installed_frontend_version():
|
|||||||
|
|
||||||
|
|
||||||
def get_required_frontend_version():
|
def get_required_frontend_version():
|
||||||
"""Get the required frontend version from requirements.txt."""
|
return get_required_packages_versions().get("comfyui-frontend-package", None)
|
||||||
try:
|
|
||||||
with open(requirements_path, "r", encoding="utf-8") as f:
|
|
||||||
for line in f:
|
|
||||||
line = line.strip()
|
|
||||||
if line.startswith("comfyui-frontend-package=="):
|
|
||||||
version_str = line.split("==")[-1]
|
|
||||||
if not is_valid_version(version_str):
|
|
||||||
logging.error(f"Invalid version format in requirements.txt: {version_str}")
|
|
||||||
return None
|
|
||||||
return version_str
|
|
||||||
logging.error("comfyui-frontend-package not found in requirements.txt")
|
|
||||||
return None
|
|
||||||
except FileNotFoundError:
|
|
||||||
logging.error("requirements.txt not found. Cannot determine required frontend version.")
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Error reading requirements.txt: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def check_frontend_version():
|
def check_frontend_version():
|
||||||
@ -217,25 +199,7 @@ class FrontendManager:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_required_templates_version(cls) -> str:
|
def get_required_templates_version(cls) -> str:
|
||||||
"""Get the required workflow templates version from requirements.txt."""
|
return get_required_packages_versions().get("comfyui-workflow-templates", None)
|
||||||
try:
|
|
||||||
with open(requirements_path, "r", encoding="utf-8") as f:
|
|
||||||
for line in f:
|
|
||||||
line = line.strip()
|
|
||||||
if line.startswith("comfyui-workflow-templates=="):
|
|
||||||
version_str = line.split("==")[-1]
|
|
||||||
if not is_valid_version(version_str):
|
|
||||||
logging.error(f"Invalid templates version format in requirements.txt: {version_str}")
|
|
||||||
return None
|
|
||||||
return version_str
|
|
||||||
logging.error("comfyui-workflow-templates not found in requirements.txt")
|
|
||||||
return None
|
|
||||||
except FileNotFoundError:
|
|
||||||
logging.error("requirements.txt not found. Cannot determine required templates version.")
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Error reading requirements.txt: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_frontend_path(cls) -> str:
|
def default_frontend_path(cls) -> str:
|
||||||
|
|||||||
@ -260,4 +260,4 @@ else:
|
|||||||
args.fast = set(args.fast)
|
args.fast = set(args.fast)
|
||||||
|
|
||||||
def enables_dynamic_vram():
|
def enables_dynamic_vram():
|
||||||
return not args.disable_dynamic_vram and not args.highvram and not args.gpu_only and not args.novram and not args.cpu
|
return not args.disable_dynamic_vram and not args.highvram and not args.gpu_only and not args.novram and not args.cpu and not args.disable_smart_memory
|
||||||
|
|||||||
@ -214,7 +214,7 @@ class IndexListContextHandler(ContextHandlerABC):
|
|||||||
mask = torch.isclose(model_options["transformer_options"]["sample_sigmas"], timestep[0], rtol=0.0001)
|
mask = torch.isclose(model_options["transformer_options"]["sample_sigmas"], timestep[0], rtol=0.0001)
|
||||||
matches = torch.nonzero(mask)
|
matches = torch.nonzero(mask)
|
||||||
if torch.numel(matches) == 0:
|
if torch.numel(matches) == 0:
|
||||||
raise Exception("No sample_sigmas matched current timestep; something went wrong.")
|
return # substep from multi-step sampler: keep self._step from the last full step
|
||||||
self._step = int(matches[0].item())
|
self._step = int(matches[0].item())
|
||||||
|
|
||||||
def get_context_windows(self, model: BaseModel, x_in: torch.Tensor, model_options: dict[str]) -> list[IndexListContextWindow]:
|
def get_context_windows(self, model: BaseModel, x_in: torch.Tensor, model_options: dict[str]) -> list[IndexListContextWindow]:
|
||||||
|
|||||||
@ -180,6 +180,14 @@ def is_ixuca():
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def is_wsl():
|
||||||
|
version = platform.uname().release
|
||||||
|
if version.endswith("-Microsoft"):
|
||||||
|
return True
|
||||||
|
elif version.endswith("microsoft-standard-WSL2"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def get_torch_device():
|
def get_torch_device():
|
||||||
global directml_enabled
|
global directml_enabled
|
||||||
global cpu_state
|
global cpu_state
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class JobStatus:
|
|||||||
|
|
||||||
|
|
||||||
# Media types that can be previewed in the frontend
|
# Media types that can be previewed in the frontend
|
||||||
PREVIEWABLE_MEDIA_TYPES = frozenset({'images', 'video', 'audio', '3d'})
|
PREVIEWABLE_MEDIA_TYPES = frozenset({'images', 'video', 'audio', '3d', 'text'})
|
||||||
|
|
||||||
# 3D file extensions for preview fallback (no dedicated media_type exists)
|
# 3D file extensions for preview fallback (no dedicated media_type exists)
|
||||||
THREE_D_EXTENSIONS = frozenset({'.obj', '.fbx', '.gltf', '.glb', '.usdz'})
|
THREE_D_EXTENSIONS = frozenset({'.obj', '.fbx', '.gltf', '.glb', '.usdz'})
|
||||||
@ -75,6 +75,23 @@ def normalize_outputs(outputs: dict) -> dict:
|
|||||||
normalized[node_id] = normalized_node
|
normalized[node_id] = normalized_node
|
||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
|
# Text preview truncation limit (1024 characters) to prevent preview_output bloat
|
||||||
|
TEXT_PREVIEW_MAX_LENGTH = 1024
|
||||||
|
|
||||||
|
|
||||||
|
def _create_text_preview(value: str) -> dict:
|
||||||
|
"""Create a text preview dict with optional truncation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict with 'content' and optionally 'truncated' flag
|
||||||
|
"""
|
||||||
|
if len(value) <= TEXT_PREVIEW_MAX_LENGTH:
|
||||||
|
return {'content': value}
|
||||||
|
return {
|
||||||
|
'content': value[:TEXT_PREVIEW_MAX_LENGTH],
|
||||||
|
'truncated': True
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _extract_job_metadata(extra_data: dict) -> tuple[Optional[int], Optional[str]]:
|
def _extract_job_metadata(extra_data: dict) -> tuple[Optional[int], Optional[str]]:
|
||||||
"""Extract create_time and workflow_id from extra_data.
|
"""Extract create_time and workflow_id from extra_data.
|
||||||
@ -221,23 +238,43 @@ def get_outputs_summary(outputs: dict) -> tuple[int, Optional[dict]]:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
normalized = normalize_output_item(item)
|
if not isinstance(item, dict):
|
||||||
if normalized is None:
|
# Handle text outputs (non-dict items like strings or tuples)
|
||||||
continue
|
normalized = normalize_output_item(item)
|
||||||
|
if normalized is None:
|
||||||
|
# Not a 3D file string — check for text preview
|
||||||
|
if media_type == 'text':
|
||||||
|
count += 1
|
||||||
|
if preview_output is None:
|
||||||
|
if isinstance(item, tuple):
|
||||||
|
text_value = item[0] if item else ''
|
||||||
|
else:
|
||||||
|
text_value = str(item)
|
||||||
|
text_preview = _create_text_preview(text_value)
|
||||||
|
enriched = {
|
||||||
|
**text_preview,
|
||||||
|
'nodeId': node_id,
|
||||||
|
'mediaType': media_type
|
||||||
|
}
|
||||||
|
if fallback_preview is None:
|
||||||
|
fallback_preview = enriched
|
||||||
|
continue
|
||||||
|
# normalize_output_item returned a dict (e.g. 3D file)
|
||||||
|
item = normalized
|
||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
if preview_output is not None:
|
if preview_output is not None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(normalized, dict) and is_previewable(media_type, normalized):
|
if is_previewable(media_type, item):
|
||||||
enriched = {
|
enriched = {
|
||||||
**normalized,
|
**item,
|
||||||
'nodeId': node_id,
|
'nodeId': node_id,
|
||||||
}
|
}
|
||||||
if 'mediaType' not in normalized:
|
if 'mediaType' not in item:
|
||||||
enriched['mediaType'] = media_type
|
enriched['mediaType'] = media_type
|
||||||
if normalized.get('type') == 'output':
|
if item.get('type') == 'output':
|
||||||
preview_output = enriched
|
preview_output = enriched
|
||||||
elif fallback_preview is None:
|
elif fallback_preview is None:
|
||||||
fallback_preview = enriched
|
fallback_preview = enriched
|
||||||
|
|||||||
@ -10,7 +10,7 @@ class Mahiro(io.ComfyNode):
|
|||||||
def define_schema(cls):
|
def define_schema(cls):
|
||||||
return io.Schema(
|
return io.Schema(
|
||||||
node_id="Mahiro",
|
node_id="Mahiro",
|
||||||
display_name="Mahiro CFG",
|
display_name="Positive-Biased Guidance",
|
||||||
category="_for_testing",
|
category="_for_testing",
|
||||||
description="Modify the guidance to scale more on the 'direction' of the positive prompt rather than the difference between the negative prompt.",
|
description="Modify the guidance to scale more on the 'direction' of the positive prompt rather than the difference between the negative prompt.",
|
||||||
inputs=[
|
inputs=[
|
||||||
@ -20,27 +20,35 @@ class Mahiro(io.ComfyNode):
|
|||||||
io.Model.Output(display_name="patched_model"),
|
io.Model.Output(display_name="patched_model"),
|
||||||
],
|
],
|
||||||
is_experimental=True,
|
is_experimental=True,
|
||||||
|
search_aliases=[
|
||||||
|
"mahiro",
|
||||||
|
"mahiro cfg",
|
||||||
|
"similarity-adaptive guidance",
|
||||||
|
"positive-biased cfg",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, model) -> io.NodeOutput:
|
def execute(cls, model) -> io.NodeOutput:
|
||||||
m = model.clone()
|
m = model.clone()
|
||||||
|
|
||||||
def mahiro_normd(args):
|
def mahiro_normd(args):
|
||||||
scale: float = args['cond_scale']
|
scale: float = args["cond_scale"]
|
||||||
cond_p: torch.Tensor = args['cond_denoised']
|
cond_p: torch.Tensor = args["cond_denoised"]
|
||||||
uncond_p: torch.Tensor = args['uncond_denoised']
|
uncond_p: torch.Tensor = args["uncond_denoised"]
|
||||||
#naive leap
|
# naive leap
|
||||||
leap = cond_p * scale
|
leap = cond_p * scale
|
||||||
#sim with uncond leap
|
# sim with uncond leap
|
||||||
u_leap = uncond_p * scale
|
u_leap = uncond_p * scale
|
||||||
cfg = args["denoised"]
|
cfg = args["denoised"]
|
||||||
merge = (leap + cfg) / 2
|
merge = (leap + cfg) / 2
|
||||||
normu = torch.sqrt(u_leap.abs()) * u_leap.sign()
|
normu = torch.sqrt(u_leap.abs()) * u_leap.sign()
|
||||||
normm = torch.sqrt(merge.abs()) * merge.sign()
|
normm = torch.sqrt(merge.abs()) * merge.sign()
|
||||||
sim = F.cosine_similarity(normu, normm).mean()
|
sim = F.cosine_similarity(normu, normm).mean()
|
||||||
simsc = 2 * (sim+1)
|
simsc = 2 * (sim + 1)
|
||||||
wm = (simsc*cfg + (4-simsc)*leap) / 4
|
wm = (simsc * cfg + (4 - simsc) * leap) / 4
|
||||||
return wm
|
return wm
|
||||||
|
|
||||||
m.set_model_sampler_post_cfg_function(mahiro_normd)
|
m.set_model_sampler_post_cfg_function(mahiro_normd)
|
||||||
return io.NodeOutput(m)
|
return io.NodeOutput(m)
|
||||||
|
|
||||||
|
|||||||
2
main.py
2
main.py
@ -192,7 +192,7 @@ import hook_breaker_ac10a0
|
|||||||
import comfy.memory_management
|
import comfy.memory_management
|
||||||
import comfy.model_patcher
|
import comfy.model_patcher
|
||||||
|
|
||||||
if enables_dynamic_vram() and comfy.model_management.is_nvidia():
|
if enables_dynamic_vram() and comfy.model_management.is_nvidia() and not comfy.model_management.is_wsl():
|
||||||
if comfy.model_management.torch_version_numeric < (2, 8):
|
if comfy.model_management.torch_version_numeric < (2, 8):
|
||||||
logging.warning("Unsupported Pytorch detected. DynamicVRAM support requires Pytorch version 2.8 or later. Falling back to legacy ModelPatcher. VRAM estimates may be unreliable especially on Windows")
|
logging.warning("Unsupported Pytorch detected. DynamicVRAM support requires Pytorch version 2.8 or later. Falling back to legacy ModelPatcher. VRAM estimates may be unreliable especially on Windows")
|
||||||
elif comfy_aimdo.control.init_device(comfy.model_management.get_torch_device().index):
|
elif comfy_aimdo.control.init_device(comfy.model_management.get_torch_device().index):
|
||||||
|
|||||||
@ -22,7 +22,7 @@ alembic
|
|||||||
SQLAlchemy
|
SQLAlchemy
|
||||||
av>=14.2.0
|
av>=14.2.0
|
||||||
comfy-kitchen>=0.2.7
|
comfy-kitchen>=0.2.7
|
||||||
comfy-aimdo>=0.2.2
|
comfy-aimdo>=0.2.3
|
||||||
requests
|
requests
|
||||||
|
|
||||||
#non essential dependencies:
|
#non essential dependencies:
|
||||||
|
|||||||
@ -49,6 +49,12 @@ def mock_provider(mock_releases):
|
|||||||
return provider
|
return provider
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def clear_cache():
|
||||||
|
import utils.install_util
|
||||||
|
utils.install_util.PACKAGE_VERSIONS = {}
|
||||||
|
|
||||||
|
|
||||||
def test_get_release(mock_provider, mock_releases):
|
def test_get_release(mock_provider, mock_releases):
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
release = mock_provider.get_release(version)
|
release = mock_provider.get_release(version)
|
||||||
|
|||||||
@ -38,13 +38,13 @@ class TestIsPreviewable:
|
|||||||
"""Unit tests for is_previewable()"""
|
"""Unit tests for is_previewable()"""
|
||||||
|
|
||||||
def test_previewable_media_types(self):
|
def test_previewable_media_types(self):
|
||||||
"""Images, video, audio, 3d media types should be previewable."""
|
"""Images, video, audio, 3d, text media types should be previewable."""
|
||||||
for media_type in ['images', 'video', 'audio', '3d']:
|
for media_type in ['images', 'video', 'audio', '3d', 'text']:
|
||||||
assert is_previewable(media_type, {}) is True
|
assert is_previewable(media_type, {}) is True
|
||||||
|
|
||||||
def test_non_previewable_media_types(self):
|
def test_non_previewable_media_types(self):
|
||||||
"""Other media types should not be previewable."""
|
"""Other media types should not be previewable."""
|
||||||
for media_type in ['latents', 'text', 'metadata', 'files']:
|
for media_type in ['latents', 'metadata', 'files']:
|
||||||
assert is_previewable(media_type, {}) is False
|
assert is_previewable(media_type, {}) is False
|
||||||
|
|
||||||
def test_3d_extensions_previewable(self):
|
def test_3d_extensions_previewable(self):
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
# The path to the requirements.txt file
|
# The path to the requirements.txt file
|
||||||
requirements_path = Path(__file__).parents[1] / "requirements.txt"
|
requirements_path = Path(__file__).parents[1] / "requirements.txt"
|
||||||
@ -16,3 +18,34 @@ Please install the updated requirements.txt file by running:
|
|||||||
{sys.executable} {extra}-m pip install -r {requirements_path}
|
{sys.executable} {extra}-m pip install -r {requirements_path}
|
||||||
If you are on the portable package you can run: update\\update_comfyui.bat to solve this problem.
|
If you are on the portable package you can run: update\\update_comfyui.bat to solve this problem.
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_version(version: str) -> bool:
|
||||||
|
"""Validate if a string is a valid semantic version (X.Y.Z format)."""
|
||||||
|
pattern = r"^(\d+)\.(\d+)\.(\d+)$"
|
||||||
|
return bool(re.match(pattern, version))
|
||||||
|
|
||||||
|
|
||||||
|
PACKAGE_VERSIONS = {}
|
||||||
|
def get_required_packages_versions():
|
||||||
|
if len(PACKAGE_VERSIONS) > 0:
|
||||||
|
return PACKAGE_VERSIONS.copy()
|
||||||
|
out = PACKAGE_VERSIONS
|
||||||
|
try:
|
||||||
|
with open(requirements_path, "r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip().replace(">=", "==")
|
||||||
|
s = line.split("==")
|
||||||
|
if len(s) == 2:
|
||||||
|
version_str = s[-1]
|
||||||
|
if not is_valid_version(version_str):
|
||||||
|
logging.error(f"Invalid version format in requirements.txt: {version_str}")
|
||||||
|
continue
|
||||||
|
out[s[0]] = version_str
|
||||||
|
return out.copy()
|
||||||
|
except FileNotFoundError:
|
||||||
|
logging.error("requirements.txt not found.")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error reading requirements.txt: {e}")
|
||||||
|
return None
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user