Skip to content

Commit ad4b959

Browse files
committed
Merge branch 'master' into dr-support-pip-cm
2 parents b88c66b + 513b0c4 commit ad4b959

22 files changed

+390
-503
lines changed

comfy/cli_args.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class LatentPreviewMethod(enum.Enum):
105105
cache_group.add_argument("--cache-classic", action="store_true", help="Use the old style (aggressive) caching.")
106106
cache_group.add_argument("--cache-lru", type=int, default=0, help="Use LRU caching with a maximum of N node results cached. May use more RAM/VRAM.")
107107
cache_group.add_argument("--cache-none", action="store_true", help="Reduced RAM/VRAM usage at the expense of executing every node for each run.")
108+
cache_group.add_argument("--cache-ram", nargs='?', const=4.0, type=float, default=0, help="Use RAM pressure caching with the specified headroom threshold. If available RAM drops below the threhold the cache remove large items to free RAM. Default 4GB")
108109

109110
attn_group = parser.add_mutually_exclusive_group()
110111
attn_group.add_argument("--use-split-cross-attention", action="store_true", help="Use the split cross attention optimization. Ignored when xformers is used.")

comfy/model_patcher.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ def model_size(self):
276276
self.size = comfy.model_management.module_size(self.model)
277277
return self.size
278278

279+
def get_ram_usage(self):
280+
return self.model_size()
281+
279282
def loaded_size(self):
280283
return self.model.model_loaded_weight_memory
281284

@@ -655,6 +658,7 @@ def load(self, device_to=None, lowvram_model_memory=0, force_patch_weights=False
655658
mem_counter = 0
656659
patch_counter = 0
657660
lowvram_counter = 0
661+
lowvram_mem_counter = 0
658662
loading = self._load_list()
659663

660664
load_completely = []
@@ -675,6 +679,7 @@ def load(self, device_to=None, lowvram_model_memory=0, force_patch_weights=False
675679
if mem_counter + module_mem >= lowvram_model_memory:
676680
lowvram_weight = True
677681
lowvram_counter += 1
682+
lowvram_mem_counter += module_mem
678683
if hasattr(m, "prev_comfy_cast_weights"): #Already lowvramed
679684
continue
680685

@@ -748,10 +753,10 @@ def load(self, device_to=None, lowvram_model_memory=0, force_patch_weights=False
748753
self.pin_weight_to_device("{}.{}".format(n, param))
749754

750755
if lowvram_counter > 0:
751-
logging.info("loaded partially {} {} {}".format(lowvram_model_memory / (1024 * 1024), mem_counter / (1024 * 1024), patch_counter))
756+
logging.info("loaded partially; {:.2f} MB usable, {:.2f} MB loaded, {:.2f} MB offloaded, lowvram patches: {}".format(lowvram_model_memory / (1024 * 1024), mem_counter / (1024 * 1024), lowvram_mem_counter / (1024 * 1024), patch_counter))
752757
self.model.model_lowvram = True
753758
else:
754-
logging.info("loaded completely {} {} {}".format(lowvram_model_memory / (1024 * 1024), mem_counter / (1024 * 1024), full_load))
759+
logging.info("loaded completely; {:.2f} MB usable, {:.2f} MB loaded, full load: {}".format(lowvram_model_memory / (1024 * 1024), mem_counter / (1024 * 1024), full_load))
755760
self.model.model_lowvram = False
756761
if full_load:
757762
self.model.to(device_to)

comfy/ops.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,14 +421,18 @@ def fp8_linear(self, input):
421421

422422
if scale_input is None:
423423
scale_input = torch.ones((), device=input.device, dtype=torch.float32)
424+
input = torch.clamp(input, min=-448, max=448, out=input)
425+
input = input.reshape(-1, input_shape[2]).to(dtype).contiguous()
426+
layout_params_weight = {'scale': scale_input, 'orig_dtype': input_dtype}
427+
quantized_input = QuantizedTensor(input.reshape(-1, input_shape[2]).to(dtype).contiguous(), TensorCoreFP8Layout, layout_params_weight)
424428
else:
425429
scale_input = scale_input.to(input.device)
430+
quantized_input = QuantizedTensor.from_float(input.reshape(-1, input_shape[2]), TensorCoreFP8Layout, scale=scale_input, dtype=dtype)
426431

427432
# Wrap weight in QuantizedTensor - this enables unified dispatch
428433
# Call F.linear - __torch_dispatch__ routes to fp8_linear handler in quant_ops.py!
429434
layout_params_weight = {'scale': scale_weight, 'orig_dtype': input_dtype}
430435
quantized_weight = QuantizedTensor(w, TensorCoreFP8Layout, layout_params_weight)
431-
quantized_input = QuantizedTensor.from_float(input.reshape(-1, input_shape[2]), TensorCoreFP8Layout, scale=scale_input, dtype=dtype)
432436
o = torch.nn.functional.linear(quantized_input, quantized_weight, bias)
433437

434438
uncast_bias_weight(self, w, bias, offload_stream)

comfy/quant_ops.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,9 +357,10 @@ def quantize(cls, tensor, scale=None, dtype=torch.float8_e4m3fn):
357357
scale = torch.tensor(scale)
358358
scale = scale.to(device=tensor.device, dtype=torch.float32)
359359

360-
lp_amax = torch.finfo(dtype).max
361360
tensor_scaled = tensor * (1.0 / scale).to(tensor.dtype)
362-
torch.clamp(tensor_scaled, min=-lp_amax, max=lp_amax, out=tensor_scaled)
361+
# TODO: uncomment this if it's actually needed because the clamp has a small performance penality'
362+
# lp_amax = torch.finfo(dtype).max
363+
# torch.clamp(tensor_scaled, min=-lp_amax, max=lp_amax, out=tensor_scaled)
363364
qdata = tensor_scaled.to(dtype, memory_format=torch.contiguous_format)
364365

365366
layout_params = {

comfy/sd.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ def clone(self):
143143
n.apply_hooks_to_conds = self.apply_hooks_to_conds
144144
return n
145145

146+
def get_ram_usage(self):
147+
return self.patcher.get_ram_usage()
148+
146149
def add_patches(self, patches, strength_patch=1.0, strength_model=1.0):
147150
return self.patcher.add_patches(patches, strength_patch, strength_model)
148151

@@ -293,6 +296,7 @@ def __init__(self, sd=None, device=None, config=None, dtype=None, metadata=None)
293296
self.working_dtypes = [torch.bfloat16, torch.float32]
294297
self.disable_offload = False
295298
self.not_video = False
299+
self.size = None
296300

297301
self.downscale_index_formula = None
298302
self.upscale_index_formula = None
@@ -595,6 +599,16 @@ def estimate_memory(shape, dtype, num_layers = 16, kv_cache_multiplier = 2):
595599

596600
self.patcher = comfy.model_patcher.ModelPatcher(self.first_stage_model, load_device=self.device, offload_device=offload_device)
597601
logging.info("VAE load device: {}, offload device: {}, dtype: {}".format(self.device, offload_device, self.vae_dtype))
602+
self.model_size()
603+
604+
def model_size(self):
605+
if self.size is not None:
606+
return self.size
607+
self.size = comfy.model_management.module_size(self.first_stage_model)
608+
return self.size
609+
610+
def get_ram_usage(self):
611+
return self.model_size()
598612

599613
def throw_exception_if_invalid(self):
600614
if self.first_stage_model is None:

comfy_api_nodes/apinode_utils.py

Lines changed: 1 addition & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
from __future__ import annotations
22
import aiohttp
33
import mimetypes
4-
from typing import Optional, Union
5-
from comfy.utils import common_upscale
4+
from typing import Union
65
from server import PromptServer
7-
from comfy.cli_args import args
86

97
import numpy as np
108
from PIL import Image
119
import torch
12-
import math
1310
import base64
1411
from io import BytesIO
1512

@@ -60,85 +57,6 @@ async def validate_and_cast_response(
6057
return torch.stack(image_tensors, dim=0)
6158

6259

63-
def validate_aspect_ratio(
64-
aspect_ratio: str,
65-
minimum_ratio: float,
66-
maximum_ratio: float,
67-
minimum_ratio_str: str,
68-
maximum_ratio_str: str,
69-
) -> float:
70-
"""Validates and casts an aspect ratio string to a float.
71-
72-
Args:
73-
aspect_ratio: The aspect ratio string to validate.
74-
minimum_ratio: The minimum aspect ratio.
75-
maximum_ratio: The maximum aspect ratio.
76-
minimum_ratio_str: The minimum aspect ratio string.
77-
maximum_ratio_str: The maximum aspect ratio string.
78-
79-
Returns:
80-
The validated and cast aspect ratio.
81-
82-
Raises:
83-
Exception: If the aspect ratio is not valid.
84-
"""
85-
# get ratio values
86-
numbers = aspect_ratio.split(":")
87-
if len(numbers) != 2:
88-
raise TypeError(
89-
f"Aspect ratio must be in the format X:Y, such as 16:9, but was {aspect_ratio}."
90-
)
91-
try:
92-
numerator = int(numbers[0])
93-
denominator = int(numbers[1])
94-
except ValueError as exc:
95-
raise TypeError(
96-
f"Aspect ratio must contain numbers separated by ':', such as 16:9, but was {aspect_ratio}."
97-
) from exc
98-
calculated_ratio = numerator / denominator
99-
# if not close to minimum and maximum, check bounds
100-
if not math.isclose(calculated_ratio, minimum_ratio) or not math.isclose(
101-
calculated_ratio, maximum_ratio
102-
):
103-
if calculated_ratio < minimum_ratio:
104-
raise TypeError(
105-
f"Aspect ratio cannot reduce to any less than {minimum_ratio_str} ({minimum_ratio}), but was {aspect_ratio} ({calculated_ratio})."
106-
)
107-
if calculated_ratio > maximum_ratio:
108-
raise TypeError(
109-
f"Aspect ratio cannot reduce to any greater than {maximum_ratio_str} ({maximum_ratio}), but was {aspect_ratio} ({calculated_ratio})."
110-
)
111-
return aspect_ratio
112-
113-
114-
async def download_url_to_bytesio(
115-
url: str, timeout: int = None, auth_kwargs: Optional[dict[str, str]] = None
116-
) -> BytesIO:
117-
"""Downloads content from a URL using requests and returns it as BytesIO.
118-
119-
Args:
120-
url: The URL to download.
121-
timeout: Request timeout in seconds. Defaults to None (no timeout).
122-
123-
Returns:
124-
BytesIO object containing the downloaded content.
125-
"""
126-
headers = {}
127-
if url.startswith("/proxy/"):
128-
url = str(args.comfy_api_base).rstrip("/") + url
129-
auth_token = auth_kwargs.get("auth_token")
130-
comfy_api_key = auth_kwargs.get("comfy_api_key")
131-
if auth_token:
132-
headers["Authorization"] = f"Bearer {auth_token}"
133-
elif comfy_api_key:
134-
headers["X-API-KEY"] = comfy_api_key
135-
timeout_cfg = aiohttp.ClientTimeout(total=timeout) if timeout else None
136-
async with aiohttp.ClientSession(timeout=timeout_cfg) as session:
137-
async with session.get(url, headers=headers) as resp:
138-
resp.raise_for_status() # Raises HTTPError for bad responses (4XX or 5XX)
139-
return BytesIO(await resp.read())
140-
141-
14260
def text_filepath_to_base64_string(filepath: str) -> str:
14361
"""Converts a text file to a base64 string."""
14462
with open(filepath, "rb") as f:
@@ -153,28 +71,3 @@ def text_filepath_to_data_uri(filepath: str) -> str:
15371
if mime_type is None:
15472
mime_type = "application/octet-stream"
15573
return f"data:{mime_type};base64,{base64_string}"
156-
157-
158-
def resize_mask_to_image(
159-
mask: torch.Tensor,
160-
image: torch.Tensor,
161-
upscale_method="nearest-exact",
162-
crop="disabled",
163-
allow_gradient=True,
164-
add_channel_dim=False,
165-
):
166-
"""
167-
Resize mask to be the same dimensions as an image, while maintaining proper format for API calls.
168-
"""
169-
_, H, W, _ = image.shape
170-
mask = mask.unsqueeze(-1)
171-
mask = mask.movedim(-1, 1)
172-
mask = common_upscale(
173-
mask, width=W, height=H, upscale_method=upscale_method, crop=crop
174-
)
175-
mask = mask.movedim(1, -1)
176-
if not add_channel_dim:
177-
mask = mask.squeeze(-1)
178-
if not allow_gradient:
179-
mask = (mask > 0.5).float()
180-
return mask

comfy_api_nodes/nodes_bfl.py

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
from typing_extensions import override
66

77
from comfy_api.latest import IO, ComfyExtension
8-
from comfy_api_nodes.apinode_utils import (
9-
resize_mask_to_image,
10-
validate_aspect_ratio,
11-
)
128
from comfy_api_nodes.apis.bfl_api import (
139
BFLFluxExpandImageRequest,
1410
BFLFluxFillImageRequest,
@@ -23,8 +19,10 @@
2319
ApiEndpoint,
2420
download_url_to_image_tensor,
2521
poll_op,
22+
resize_mask_to_image,
2623
sync_op,
2724
tensor_to_base64_string,
25+
validate_aspect_ratio_string,
2826
validate_string,
2927
)
3028

@@ -43,11 +41,6 @@ class FluxProUltraImageNode(IO.ComfyNode):
4341
Generates images using Flux Pro 1.1 Ultra via api based on prompt and resolution.
4442
"""
4543

46-
MINIMUM_RATIO = 1 / 4
47-
MAXIMUM_RATIO = 4 / 1
48-
MINIMUM_RATIO_STR = "1:4"
49-
MAXIMUM_RATIO_STR = "4:1"
50-
5144
@classmethod
5245
def define_schema(cls) -> IO.Schema:
5346
return IO.Schema(
@@ -112,16 +105,7 @@ def define_schema(cls) -> IO.Schema:
112105

113106
@classmethod
114107
def validate_inputs(cls, aspect_ratio: str):
115-
try:
116-
validate_aspect_ratio(
117-
aspect_ratio,
118-
minimum_ratio=cls.MINIMUM_RATIO,
119-
maximum_ratio=cls.MAXIMUM_RATIO,
120-
minimum_ratio_str=cls.MINIMUM_RATIO_STR,
121-
maximum_ratio_str=cls.MAXIMUM_RATIO_STR,
122-
)
123-
except Exception as e:
124-
return str(e)
108+
validate_aspect_ratio_string(aspect_ratio, (1, 4), (4, 1))
125109
return True
126110

127111
@classmethod
@@ -145,13 +129,7 @@ async def execute(
145129
prompt=prompt,
146130
prompt_upsampling=prompt_upsampling,
147131
seed=seed,
148-
aspect_ratio=validate_aspect_ratio(
149-
aspect_ratio,
150-
minimum_ratio=cls.MINIMUM_RATIO,
151-
maximum_ratio=cls.MAXIMUM_RATIO,
152-
minimum_ratio_str=cls.MINIMUM_RATIO_STR,
153-
maximum_ratio_str=cls.MAXIMUM_RATIO_STR,
154-
),
132+
aspect_ratio=aspect_ratio,
155133
raw=raw,
156134
image_prompt=(image_prompt if image_prompt is None else tensor_to_base64_string(image_prompt)),
157135
image_prompt_strength=(None if image_prompt is None else round(image_prompt_strength, 2)),
@@ -180,11 +158,6 @@ class FluxKontextProImageNode(IO.ComfyNode):
180158
Edits images using Flux.1 Kontext [pro] via api based on prompt and aspect ratio.
181159
"""
182160

183-
MINIMUM_RATIO = 1 / 4
184-
MAXIMUM_RATIO = 4 / 1
185-
MINIMUM_RATIO_STR = "1:4"
186-
MAXIMUM_RATIO_STR = "4:1"
187-
188161
@classmethod
189162
def define_schema(cls) -> IO.Schema:
190163
return IO.Schema(
@@ -261,13 +234,7 @@ async def execute(
261234
seed=0,
262235
prompt_upsampling=False,
263236
) -> IO.NodeOutput:
264-
aspect_ratio = validate_aspect_ratio(
265-
aspect_ratio,
266-
minimum_ratio=cls.MINIMUM_RATIO,
267-
maximum_ratio=cls.MAXIMUM_RATIO,
268-
minimum_ratio_str=cls.MINIMUM_RATIO_STR,
269-
maximum_ratio_str=cls.MAXIMUM_RATIO_STR,
270-
)
237+
validate_aspect_ratio_string(aspect_ratio, (1, 4), (4, 1))
271238
if input_image is None:
272239
validate_string(prompt, strip_whitespace=False)
273240
initial_response = await sync_op(

comfy_api_nodes/nodes_bytedance.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
poll_op,
1818
sync_op,
1919
upload_images_to_comfyapi,
20-
validate_image_aspect_ratio_range,
20+
validate_image_aspect_ratio,
2121
validate_image_dimensions,
2222
validate_string,
2323
)
@@ -403,7 +403,7 @@ async def execute(
403403
validate_string(prompt, strip_whitespace=True, min_length=1)
404404
if get_number_of_images(image) != 1:
405405
raise ValueError("Exactly one input image is required.")
406-
validate_image_aspect_ratio_range(image, (1, 3), (3, 1))
406+
validate_image_aspect_ratio(image, (1, 3), (3, 1))
407407
source_url = (await upload_images_to_comfyapi(cls, image, max_images=1, mime_type="image/png"))[0]
408408
payload = Image2ImageTaskCreationRequest(
409409
model=model,
@@ -565,7 +565,7 @@ async def execute(
565565
reference_images_urls = []
566566
if n_input_images:
567567
for i in image:
568-
validate_image_aspect_ratio_range(i, (1, 3), (3, 1))
568+
validate_image_aspect_ratio(i, (1, 3), (3, 1))
569569
reference_images_urls = await upload_images_to_comfyapi(
570570
cls,
571571
image,
@@ -798,7 +798,7 @@ async def execute(
798798
validate_string(prompt, strip_whitespace=True, min_length=1)
799799
raise_if_text_params(prompt, ["resolution", "ratio", "duration", "seed", "camerafixed", "watermark"])
800800
validate_image_dimensions(image, min_width=300, min_height=300, max_width=6000, max_height=6000)
801-
validate_image_aspect_ratio_range(image, (2, 5), (5, 2), strict=False) # 0.4 to 2.5
801+
validate_image_aspect_ratio(image, (2, 5), (5, 2), strict=False) # 0.4 to 2.5
802802

803803
image_url = (await upload_images_to_comfyapi(cls, image, max_images=1))[0]
804804
prompt = (
@@ -923,7 +923,7 @@ async def execute(
923923
raise_if_text_params(prompt, ["resolution", "ratio", "duration", "seed", "camerafixed", "watermark"])
924924
for i in (first_frame, last_frame):
925925
validate_image_dimensions(i, min_width=300, min_height=300, max_width=6000, max_height=6000)
926-
validate_image_aspect_ratio_range(i, (2, 5), (5, 2), strict=False) # 0.4 to 2.5
926+
validate_image_aspect_ratio(i, (2, 5), (5, 2), strict=False) # 0.4 to 2.5
927927

928928
download_urls = await upload_images_to_comfyapi(
929929
cls,
@@ -1045,7 +1045,7 @@ async def execute(
10451045
raise_if_text_params(prompt, ["resolution", "ratio", "duration", "seed", "watermark"])
10461046
for image in images:
10471047
validate_image_dimensions(image, min_width=300, min_height=300, max_width=6000, max_height=6000)
1048-
validate_image_aspect_ratio_range(image, (2, 5), (5, 2), strict=False) # 0.4 to 2.5
1048+
validate_image_aspect_ratio(image, (2, 5), (5, 2), strict=False) # 0.4 to 2.5
10491049

10501050
image_urls = await upload_images_to_comfyapi(cls, images, max_images=4, mime_type="image/png")
10511051
prompt = (

0 commit comments

Comments
 (0)