commit fb949d19a00f91a90bfaa5f011828fb5b4ba384d Author: AllergicToTeeth <33663606+usrname0@users.noreply.github.com> Date: Sat May 10 19:34:04 2025 -0500 redo again why not diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba0430d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a6020b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 AllergicToTeeth + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f686250 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +## HoldUp +A ComfyUI node that waits for a GPU temp and/or a number of seconds. + +Completely redone but based off of gpucooldown: https://github.com/wmsouza/comfyui-gpucooldown + +## Installation +Clone to "custom_nodes" + +``` +git clone https://github.com/usrname0/comfyui-holdup.git +``` + +## Usage +Insert it between the KSampler and VAE Decode nodes. + +## Pics +Example Wan2.1 workflow (drag image into ComfyUI): +![ComfyUI Workflow](./example_workflows/ComfyUI-Wan2.1-HoldUp-Workflow.png) + +What this looks like in the terminal: +![ComfyUI Workflow](./example_workflows/HoldUp-Terminal.png) + +Project inspired by my deep and abiding mistrust of the 12VHPWR connector. +![ComfyUI Workflow](./example_workflows/temps.jpg) diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..1274659 --- /dev/null +++ b/__init__.py @@ -0,0 +1,9 @@ +from .holdup import HoldUp + +NODE_CLASS_MAPPINGS = { + "HoldUp": HoldUp, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "HoldUp": "HoldUp", +} \ No newline at end of file diff --git a/example_workflows/ComfyUI-Wan2.1-HoldUp-Workflow.png b/example_workflows/ComfyUI-Wan2.1-HoldUp-Workflow.png new file mode 100644 index 0000000..8ab2382 Binary files /dev/null and b/example_workflows/ComfyUI-Wan2.1-HoldUp-Workflow.png differ diff --git a/example_workflows/HoldUp-Terminal.png b/example_workflows/HoldUp-Terminal.png new file mode 100644 index 0000000..a6fbd73 Binary files /dev/null and b/example_workflows/HoldUp-Terminal.png differ diff --git a/example_workflows/temps.jpg b/example_workflows/temps.jpg new file mode 100644 index 0000000..f89daa7 Binary files /dev/null and b/example_workflows/temps.jpg differ diff --git a/holdup.py b/holdup.py new file mode 100644 index 0000000..12f298b --- /dev/null +++ b/holdup.py @@ -0,0 +1,155 @@ +import time +import GPUtil # Required to get GPU temp +import sys # Required for sys.stdout for the progress bar + +class HoldUp: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "latent": ("LATENT",), + "use_waitTemperature": ("BOOLEAN", {"default": True}), # Feature is on by default + "waitTemperature": ("INT", {"default": 50, "min": 45, "max": 90}), + "waitSeconds": ("INT", {"default": 0, "min": 0, "max": 120}), + } + } + + RETURN_TYPES = ("LATENT",) + FUNCTION = "execute_cool_down" # Matching the method name below for clarity + CATEGORY = "utils" + + def _display_temperature_progress(self, current_temp, target_temp, initial_peak_temp, bar_length=50): + """ + Displays a text-based progress bar for temperature. + + Args: + current_temp (float): The current highest temperature of the GPUs. + target_temp (float): The target cool-down temperature (waitTemperature). + initial_peak_temp (float): The highest temperature recorded when this + current cool-down cycle started or subsequently peaked. + bar_length (int): The character length of the progress bar. + """ + # Ensure target_temp is float for calculations + target_temp = float(target_temp) + + if initial_peak_temp <= target_temp: + # This case implies we started at or below the target. + # If current is also low, progress is 100%. If current is somehow high, progress is 0%. + progress = 1.0 if current_temp <= target_temp else 0.0 + elif current_temp <= target_temp: + progress = 1.0 # Fully cooled + else: + # We are actively cooling from initial_peak_temp towards target_temp + total_range_to_cool = initial_peak_temp - target_temp + amount_cooled = initial_peak_temp - current_temp + + if total_range_to_cool <= 0: # Defensive check, should be covered by the first if + progress = 1.0 if current_temp <= target_temp else 0.0 + else: + progress = amount_cooled / total_range_to_cool + + progress = max(0, min(1, progress)) # Clamp progress between 0 and 1 + + filled_length = int(bar_length * progress) + bar_chars = '█' * filled_length + '-' * (bar_length - filled_length) + + status_message = f"***** Cooling GPUs: Peak: {initial_peak_temp:.1f}°C" + progress_percentage = f"{progress*100:.1f}%" + + # \r carriage return to overwrite the line, end='' is implicitly handled by sys.stdout.write + # Adding spaces at the end to clear any previous longer line + full_line = f"\r{status_message} |{bar_chars}| {current_temp:.1f}°C / {target_temp:.1f}°C " + + sys.stdout.write(full_line) + sys.stdout.flush() # Ensure it's printed immediately + + def execute_cool_down(self, latent, waitSeconds, use_waitTemperature, waitTemperature): # Parameters match keys in INPUT_TYPES + if use_waitTemperature: + """ + Waits for GPU(s) to cool down below waitTemperature, displaying a progress bar. + """ + initial_peak_temp_this_hot_cycle = None + cooling_message_printed_this_cycle = False + + while True: + try: + gpus = GPUtil.getGPUs() + except Exception as e: + # Handle cases where GPUtil might fail (e.g., drivers not installed) + print(f"\nError accessing GPU information: {e}. Skipping cool down.", file=sys.stderr) + return (latent,) # Exit gracefully + + if not gpus: + # No GPUs detected by GPUtil + # Depending on expected behavior, you might print a warning or just proceed + # print("No GPUs detected by GPUtil. Skipping cool down.") # Optional: inform user + break # Exit the cool down loop + + highest_current_temp = 0.0 # Initialize with a float + any_gpu_too_hot = False + + for gpu in gpus: + # Ensure gpu.temperature is treated as float + current_gpu_temp = float(gpu.temperature) + if current_gpu_temp > highest_current_temp: + highest_current_temp = current_gpu_temp + if current_gpu_temp > waitTemperature: + any_gpu_too_hot = True + + if any_gpu_too_hot: + if initial_peak_temp_this_hot_cycle is None: + # This is the beginning of a new hot cycle. + # Record the temperature that triggered the cooldown. + initial_peak_temp_this_hot_cycle = highest_current_temp + + # If the temperature spikes even higher mid-cooldown, update the peak. + if highest_current_temp > initial_peak_temp_this_hot_cycle: + initial_peak_temp_this_hot_cycle = highest_current_temp + + if not cooling_message_printed_this_cycle: + # sys.stdout.write("\n") # Optional: ensure progress bar starts on a new line from any prior prints + print(f"***** GPU temperature ({highest_current_temp:.1f}°C) exceeds target ({waitTemperature}°C). Initiating cool down...") + cooling_message_printed_this_cycle = True + + # Display the progress bar. initial_peak_temp_this_hot_cycle won't be None here. + self._display_temperature_progress(highest_current_temp, float(waitTemperature), initial_peak_temp_this_hot_cycle) + + time.sleep(2) # Check every 2 seconds + + else: + if initial_peak_temp_this_hot_cycle is not None: + # We were cooling down, and now all GPUs are at or below the target. + # Display final 100% progress using the target temperature as the current. + self._display_temperature_progress(float(waitTemperature), float(waitTemperature), initial_peak_temp_this_hot_cycle) + sys.stdout.write("\n***** GPU cool down complete. Temperature is at or below target.\n") + sys.stdout.flush() + + # Reset for any future heat-up events within the same node execution (if possible) + initial_peak_temp_this_hot_cycle = None + cooling_message_printed_this_cycle = False + break # Exit the while loop, cool down is complete + + if waitSeconds > 0: + # Loop from the entered number of seconds down to 1 + for i in range(waitSeconds, 0, -1): + # Print the current second + # Use \r to return to the beginning of the line, allowing the next print to overwrite + # Use end='' to prevent a newline character after each number + # \r carriage return to overwrite the line, end='' is implicitly handled by sys.stdout.write + # Adding spaces at the end to clear any previous longer line + full_line = f"\r***** Waiting: {i} / {waitSeconds} seconds " + sys.stdout.write(full_line) + sys.stdout.flush() # Ensure it's printed immediately + + # Wait for 1 second + time.sleep(1) + + # Print the final message + print(f"\r***** Waited: {waitSeconds} seconds ") # Extra spaces to overwrite previous numbers + + # Print a timestamp and return the latent + now = time.time() + time_tuple = time.localtime(now) + formatted_time = time.strftime("%Y-%m-%d %H:%M:%S", time_tuple) + print(f"\r***** {formatted_time}") + return (latent,) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..18a5b79 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +gputil \ No newline at end of file