mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-02-13 15:02:37 +08:00
Fix Python linting issues and enhance RunPod network storage
- Fix all whitespace and formatting issues in runpod_handler.py - Remove trailing whitespace and blank line whitespace - Add comprehensive RunPod network storage setup - Enhance start_runpod.py with automatic model mounting - Update Dockerfile for optimized RunPod deployment - Add detailed setup documentation in runpod_setup.md - Improve .dockerignore for faster builds - Add .env to .gitignore for security
This commit is contained in:
parent
91494226db
commit
e054031606
@ -1,19 +1,79 @@
|
|||||||
|
# Git
|
||||||
.git
|
.git
|
||||||
.github
|
.gitignore
|
||||||
__pycache__
|
.gitattributes
|
||||||
*.pyc
|
|
||||||
*.pyo
|
# Documentation
|
||||||
*.pyd
|
|
||||||
.Python
|
|
||||||
*.so
|
|
||||||
*.egg
|
|
||||||
*.egg-info
|
|
||||||
dist
|
|
||||||
build
|
|
||||||
.vscode
|
|
||||||
.idea
|
|
||||||
*.md
|
*.md
|
||||||
output
|
docs/
|
||||||
input
|
|
||||||
models/checkpoints/*.safetensors
|
# Development files
|
||||||
models/checkpoints/*.ckpt
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.example
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
temp/
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
|
||||||
|
# Large model files (will be mounted from network storage)
|
||||||
|
models/*/
|
||||||
|
!models/.gitkeep
|
||||||
|
|
||||||
|
# Output files
|
||||||
|
output/
|
||||||
|
outputs/
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.cache/
|
||||||
|
cache/
|
||||||
|
|
||||||
|
# Node modules (if any)
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Dockerfile.*
|
||||||
|
docker-compose*.yml
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -24,3 +24,8 @@ web_custom_versions/
|
|||||||
openapi.yaml
|
openapi.yaml
|
||||||
filtered-openapi.yaml
|
filtered-openapi.yaml
|
||||||
uv.lock
|
uv.lock
|
||||||
|
|
||||||
|
# Environment files with secrets
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.production
|
||||||
|
|||||||
@ -39,12 +39,12 @@ class ComfyUIServerlessHandler:
|
|||||||
"""Start ComfyUI server in background"""
|
"""Start ComfyUI server in background"""
|
||||||
try:
|
try:
|
||||||
logger.info("Starting ComfyUI server...")
|
logger.info("Starting ComfyUI server...")
|
||||||
|
|
||||||
# Check if main.py exists
|
# Check if main.py exists
|
||||||
if not os.path.exists("/workspace/ComfyUI/main.py"):
|
if not os.path.exists("/workspace/ComfyUI/main.py"):
|
||||||
logger.error("main.py not found in /workspace/ComfyUI")
|
logger.error("main.py not found in /workspace/ComfyUI")
|
||||||
raise FileNotFoundError("ComfyUI main.py not found")
|
raise FileNotFoundError("ComfyUI main.py not found")
|
||||||
|
|
||||||
# Check if models directory exists (network storage)
|
# Check if models directory exists (network storage)
|
||||||
if not os.path.exists("/workspace/ComfyUI/models"):
|
if not os.path.exists("/workspace/ComfyUI/models"):
|
||||||
logger.warning("Models directory not found, creating symlink to network storage")
|
logger.warning("Models directory not found, creating symlink to network storage")
|
||||||
@ -52,7 +52,7 @@ class ComfyUIServerlessHandler:
|
|||||||
os.symlink("/runpod-volume/models", "/workspace/ComfyUI/models")
|
os.symlink("/runpod-volume/models", "/workspace/ComfyUI/models")
|
||||||
else:
|
else:
|
||||||
logger.error("Network storage models not found at /runpod-volume/models")
|
logger.error("Network storage models not found at /runpod-volume/models")
|
||||||
|
|
||||||
# Start ComfyUI
|
# Start ComfyUI
|
||||||
self.comfyui_process = subprocess.Popen([
|
self.comfyui_process = subprocess.Popen([
|
||||||
"python3", "main.py",
|
"python3", "main.py",
|
||||||
@ -60,12 +60,12 @@ class ComfyUIServerlessHandler:
|
|||||||
"--port", "8000",
|
"--port", "8000",
|
||||||
"--dont-print-server",
|
"--dont-print-server",
|
||||||
"--disable-auto-launch"
|
"--disable-auto-launch"
|
||||||
], cwd="/workspace/ComfyUI",
|
], cwd="/workspace/ComfyUI",
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
|
|
||||||
logger.info("ComfyUI server started")
|
logger.info("ComfyUI server started")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to start ComfyUI: {str(e)}")
|
logger.error(f"Failed to start ComfyUI: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|||||||
13
Dockerfile
13
Dockerfile
@ -18,9 +18,16 @@ RUN pip install --no-cache-dir \
|
|||||||
torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu && \
|
torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu && \
|
||||||
pip install --no-cache-dir -r requirements.txt
|
pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# Cloud Run PORT environment variable'ını kullan
|
# RunPod başlangıç scriptini çalıştırılabilir yap
|
||||||
|
RUN chmod +x start_runpod.py
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
ENV PORT=8188
|
ENV PORT=8188
|
||||||
|
ENV RUNPOD_NETWORK_STORAGE_PATH=/runpod-volume
|
||||||
|
ENV HF_HUB_DISABLE_TELEMETRY=1
|
||||||
|
ENV DO_NOT_TRACK=1
|
||||||
|
|
||||||
EXPOSE 8188
|
EXPOSE 8188
|
||||||
|
|
||||||
# Başlat - CPU modunda çalıştır
|
# RunPod başlangıç scriptini kullan
|
||||||
CMD python main.py --listen 0.0.0.0 --port ${PORT} --cpu
|
CMD ["python", "start_runpod.py"]
|
||||||
|
|||||||
93
Dockerfile.serverless
Normal file
93
Dockerfile.serverless
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# RunPod Serverless ComfyUI Worker
|
||||||
|
FROM runpod/pytorch:2.2.0-py3.11-cuda12.1.1-devel-ubuntu22.04
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
wget \
|
||||||
|
curl \
|
||||||
|
unzip \
|
||||||
|
ffmpeg \
|
||||||
|
libsm6 \
|
||||||
|
libxext6 \
|
||||||
|
libxrender-dev \
|
||||||
|
libglib2.0-0 \
|
||||||
|
libgl1-mesa-glx \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
RUN pip install --no-cache-dir \
|
||||||
|
runpod \
|
||||||
|
requests \
|
||||||
|
pillow \
|
||||||
|
numpy \
|
||||||
|
torch \
|
||||||
|
torchvision \
|
||||||
|
torchaudio \
|
||||||
|
xformers \
|
||||||
|
accelerate \
|
||||||
|
transformers \
|
||||||
|
diffusers \
|
||||||
|
opencv-python \
|
||||||
|
scipy \
|
||||||
|
scikit-image
|
||||||
|
|
||||||
|
# Clone ComfyUI
|
||||||
|
RUN git clone https://github.com/bahadirciloglu/ComfyUI.git /workspace/ComfyUI
|
||||||
|
|
||||||
|
# Set ComfyUI as working directory
|
||||||
|
WORKDIR /workspace/ComfyUI
|
||||||
|
|
||||||
|
# Switch to create_image branch
|
||||||
|
RUN git checkout create_image
|
||||||
|
|
||||||
|
# Install ComfyUI requirements
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Install additional dependencies for new features
|
||||||
|
RUN pip install --no-cache-dir \
|
||||||
|
safetensors \
|
||||||
|
transformers[torch] \
|
||||||
|
accelerate \
|
||||||
|
bitsandbytes \
|
||||||
|
optimum
|
||||||
|
|
||||||
|
# Copy serverless handler
|
||||||
|
COPY runpod_handler.py /workspace/runpod_handler.py
|
||||||
|
COPY .env /workspace/.env
|
||||||
|
|
||||||
|
# Create necessary directories
|
||||||
|
RUN mkdir -p /workspace/ComfyUI/models/checkpoints \
|
||||||
|
/workspace/ComfyUI/models/vae \
|
||||||
|
/workspace/ComfyUI/models/loras \
|
||||||
|
/workspace/ComfyUI/models/controlnet \
|
||||||
|
/workspace/ComfyUI/models/clip_vision \
|
||||||
|
/workspace/ComfyUI/models/upscale_models \
|
||||||
|
/workspace/ComfyUI/input \
|
||||||
|
/workspace/ComfyUI/output \
|
||||||
|
/tmp/inputs \
|
||||||
|
/tmp/outputs \
|
||||||
|
/tmp/comfyui
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV PYTHONPATH="/workspace/ComfyUI:${PYTHONPATH}"
|
||||||
|
ENV COMFYUI_SERVERLESS=true
|
||||||
|
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||||
|
ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
||||||
|
|
||||||
|
# Create startup script
|
||||||
|
RUN echo '#!/bin/bash\n\
|
||||||
|
cd /workspace/ComfyUI\n\
|
||||||
|
python main.py --listen 0.0.0.0 --port 8000 --dont-print-server --disable-auto-launch &\n\
|
||||||
|
sleep 10\n\
|
||||||
|
cd /workspace\n\
|
||||||
|
python runpod_handler.py' > /workspace/start.sh && chmod +x /workspace/start.sh
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Set the command
|
||||||
|
CMD ["/workspace/start.sh"]
|
||||||
BIN
pars/Screenshot 2025-10-31 at 14.31.25.png
Normal file
BIN
pars/Screenshot 2025-10-31 at 14.31.25.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 624 KiB |
BIN
pars/Screenshot 2025-10-31 at 14.32.45.png
Normal file
BIN
pars/Screenshot 2025-10-31 at 14.32.45.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 481 KiB |
BIN
pars/Screenshot 2025-10-31 at 14.32.52.png
Normal file
BIN
pars/Screenshot 2025-10-31 at 14.32.52.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 463 KiB |
BIN
pars/Screenshot 2025-10-31 at 14.32.56.png
Normal file
BIN
pars/Screenshot 2025-10-31 at 14.32.56.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 447 KiB |
BIN
pars/Screenshot 2025-10-31 at 14.33.02.png
Normal file
BIN
pars/Screenshot 2025-10-31 at 14.33.02.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
245
runpod_handler.py
Normal file
245
runpod_handler.py
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
RunPod Serverless Worker Handler for ComfyUI
|
||||||
|
Optimized for the new ComfyUI features and performance improvements
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import tempfile
|
||||||
|
import requests
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
import runpod
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ComfyUIServerlessHandler:
|
||||||
|
def __init__(self):
|
||||||
|
self.comfyui_url = "http://127.0.0.1:8000"
|
||||||
|
self.client_id = "runpod_serverless_worker"
|
||||||
|
self.setup_paths()
|
||||||
|
|
||||||
|
def setup_paths(self):
|
||||||
|
"""Setup required paths for serverless operation"""
|
||||||
|
os.makedirs("/tmp/inputs", exist_ok=True)
|
||||||
|
os.makedirs("/tmp/outputs", exist_ok=True)
|
||||||
|
os.makedirs("/tmp/comfyui", exist_ok=True)
|
||||||
|
|
||||||
|
def wait_for_comfyui(self, timeout: int = 120) -> bool:
|
||||||
|
"""Wait for ComfyUI to be ready"""
|
||||||
|
start_time = time.time()
|
||||||
|
while time.time() - start_time < timeout:
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{self.comfyui_url}/system_stats")
|
||||||
|
if response.status_code == 200:
|
||||||
|
logger.info("ComfyUI is ready")
|
||||||
|
return True
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
pass
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
logger.error(f"ComfyUI not ready after {timeout} seconds")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def download_input_files(self, input_data: Dict[str, Any]) -> Dict[str, str]:
|
||||||
|
"""Download input files and return local paths"""
|
||||||
|
local_files = {}
|
||||||
|
|
||||||
|
if "input_files" in input_data:
|
||||||
|
for file_key, file_url in input_data["input_files"].items():
|
||||||
|
try:
|
||||||
|
response = requests.get(file_url, timeout=60)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Create temporary file
|
||||||
|
with tempfile.NamedTemporaryFile(
|
||||||
|
delete=False,
|
||||||
|
dir="/tmp/inputs",
|
||||||
|
suffix=os.path.splitext(file_url)[1]
|
||||||
|
) as tmp_file:
|
||||||
|
tmp_file.write(response.content)
|
||||||
|
local_files[file_key] = tmp_file.name
|
||||||
|
|
||||||
|
logger.info(f"Downloaded {file_key} to {local_files[file_key]}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to download {file_key}: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
return local_files
|
||||||
|
|
||||||
|
def execute_workflow(self, workflow: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Execute ComfyUI workflow"""
|
||||||
|
try:
|
||||||
|
# Queue the workflow
|
||||||
|
queue_response = requests.post(
|
||||||
|
f"{self.comfyui_url}/prompt",
|
||||||
|
json={
|
||||||
|
"prompt": workflow,
|
||||||
|
"client_id": self.client_id
|
||||||
|
},
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
queue_response.raise_for_status()
|
||||||
|
|
||||||
|
prompt_id = queue_response.json()["prompt_id"]
|
||||||
|
logger.info(f"Queued workflow with prompt_id: {prompt_id}")
|
||||||
|
|
||||||
|
# Wait for completion
|
||||||
|
return self.wait_for_completion(prompt_id)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to execute workflow: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def wait_for_completion(self, prompt_id: str, timeout: int = 300) -> Dict[str, Any]:
|
||||||
|
"""Wait for workflow completion and return results"""
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
while time.time() - start_time < timeout:
|
||||||
|
try:
|
||||||
|
# Check queue status
|
||||||
|
queue_response = requests.get(f"{self.comfyui_url}/queue")
|
||||||
|
queue_data = queue_response.json()
|
||||||
|
|
||||||
|
# Check if our job is still in queue
|
||||||
|
running = any(item[1]["prompt_id"] == prompt_id for item in queue_data.get("queue_running", []))
|
||||||
|
pending = any(item[1]["prompt_id"] == prompt_id for item in queue_data.get("queue_pending", []))
|
||||||
|
|
||||||
|
if not running and not pending:
|
||||||
|
# Job completed, get results
|
||||||
|
history_response = requests.get(f"{self.comfyui_url}/history/{prompt_id}")
|
||||||
|
if history_response.status_code == 200:
|
||||||
|
history_data = history_response.json()
|
||||||
|
if prompt_id in history_data:
|
||||||
|
return self.process_results(history_data[prompt_id])
|
||||||
|
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error checking completion: {str(e)}")
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
raise TimeoutError(f"Workflow {prompt_id} timed out after {timeout} seconds")
|
||||||
|
|
||||||
|
def process_results(self, history_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Process and upload results"""
|
||||||
|
results = {
|
||||||
|
"status": "completed",
|
||||||
|
"outputs": [],
|
||||||
|
"metadata": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if "outputs" in history_data:
|
||||||
|
for node_id, node_output in history_data["outputs"].items():
|
||||||
|
if "images" in node_output:
|
||||||
|
for image_info in node_output["images"]:
|
||||||
|
# Download image from ComfyUI
|
||||||
|
image_url = f"{self.comfyui_url}/view"
|
||||||
|
params = {
|
||||||
|
"filename": image_info["filename"],
|
||||||
|
"subfolder": image_info.get("subfolder", ""),
|
||||||
|
"type": image_info.get("type", "output")
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
image_response = requests.get(image_url, params=params)
|
||||||
|
image_response.raise_for_status()
|
||||||
|
|
||||||
|
# Save to temp file for upload
|
||||||
|
output_path = f"/tmp/outputs/{image_info['filename']}"
|
||||||
|
with open(output_path, "wb") as f:
|
||||||
|
f.write(image_response.content)
|
||||||
|
|
||||||
|
results["outputs"].append({
|
||||||
|
"type": "image",
|
||||||
|
"filename": image_info["filename"],
|
||||||
|
"path": output_path,
|
||||||
|
"node_id": node_id
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to process image {image_info['filename']}: {str(e)}")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""Clean up temporary files"""
|
||||||
|
try:
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree("/tmp/inputs", ignore_errors=True)
|
||||||
|
shutil.rmtree("/tmp/outputs", ignore_errors=True)
|
||||||
|
os.makedirs("/tmp/inputs", exist_ok=True)
|
||||||
|
os.makedirs("/tmp/outputs", exist_ok=True)
|
||||||
|
logger.info("Cleaned up temporary files")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Cleanup failed: {str(e)}")
|
||||||
|
|
||||||
|
def handler(job: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Main serverless handler function"""
|
||||||
|
handler_instance = ComfyUIServerlessHandler()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Wait for ComfyUI to be ready
|
||||||
|
if not handler_instance.wait_for_comfyui():
|
||||||
|
return {"error": "ComfyUI failed to start"}
|
||||||
|
|
||||||
|
# Get job input
|
||||||
|
job_input = job.get("input", {})
|
||||||
|
|
||||||
|
# Download input files if any
|
||||||
|
local_files = handler_instance.download_input_files(job_input)
|
||||||
|
|
||||||
|
# Update workflow with local file paths
|
||||||
|
workflow = job_input.get("workflow", {})
|
||||||
|
if local_files and "file_mappings" in job_input:
|
||||||
|
for node_id, mappings in job_input["file_mappings"].items():
|
||||||
|
if node_id in workflow:
|
||||||
|
for input_key, file_key in mappings.items():
|
||||||
|
if file_key in local_files:
|
||||||
|
workflow[node_id]["inputs"][input_key] = local_files[file_key]
|
||||||
|
|
||||||
|
# Execute workflow
|
||||||
|
results = handler_instance.execute_workflow(workflow)
|
||||||
|
|
||||||
|
# Upload output files to RunPod storage or return base64
|
||||||
|
output_urls = []
|
||||||
|
for output in results.get("outputs", []):
|
||||||
|
if output["type"] == "image":
|
||||||
|
# For serverless, we typically return base64 or upload to storage
|
||||||
|
with open(output["path"], "rb") as f:
|
||||||
|
import base64
|
||||||
|
image_data = base64.b64encode(f.read()).decode()
|
||||||
|
output_urls.append({
|
||||||
|
"filename": output["filename"],
|
||||||
|
"data": image_data,
|
||||||
|
"node_id": output["node_id"]
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"outputs": output_urls,
|
||||||
|
"execution_time": time.time() - job.get("start_time", time.time())
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Handler error: {str(e)}")
|
||||||
|
return {
|
||||||
|
"error": str(e),
|
||||||
|
"status": "failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Always cleanup
|
||||||
|
handler_instance.cleanup()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Start the serverless worker
|
||||||
|
runpod.serverless.start({"handler": handler})
|
||||||
150
runpod_setup.md
Normal file
150
runpod_setup.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# RunPod Network Storage Setup Guide
|
||||||
|
|
||||||
|
## 1. Network Storage Hazırlığı
|
||||||
|
|
||||||
|
### RunPod Dashboard'da:
|
||||||
|
1. **Network Storage** oluşturun (örn: `comfyui-models`)
|
||||||
|
2. Storage boyutunu belirleyin (en az 50GB önerilir)
|
||||||
|
3. Storage ID'sini not alın
|
||||||
|
|
||||||
|
### Modelleri Network Storage'a Yükleme:
|
||||||
|
```bash
|
||||||
|
# RunPod pod'unda terminal açın
|
||||||
|
cd /runpod-volume
|
||||||
|
|
||||||
|
# Models klasörü oluşturun
|
||||||
|
mkdir -p models/{checkpoints,loras,vae,controlnet,upscale_models,text_encoders,clip,diffusion_models,unet,embeddings,clip_vision}
|
||||||
|
|
||||||
|
# Örnek model indirme (SDXL Base)
|
||||||
|
cd models/checkpoints
|
||||||
|
wget https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors
|
||||||
|
|
||||||
|
# VAE modeli
|
||||||
|
cd ../vae
|
||||||
|
wget https://huggingface.co/stabilityai/sdxl-vae/resolve/main/sdxl_vae.safetensors
|
||||||
|
|
||||||
|
# ControlNet modeli
|
||||||
|
cd ../controlnet
|
||||||
|
wget https://huggingface.co/lllyasviel/sd_control_collection/resolve/main/diffusers_xl_canny_mid.safetensors
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. RunPod Template Ayarları
|
||||||
|
|
||||||
|
### Container Settings:
|
||||||
|
- **Docker Image**: `your-registry/comfyui-runpod:latest`
|
||||||
|
- **Container Disk**: 20GB (minimum)
|
||||||
|
- **Network Storage**: Mount ettiğiniz storage'ı seçin
|
||||||
|
|
||||||
|
### Environment Variables:
|
||||||
|
```bash
|
||||||
|
# Zorunlu
|
||||||
|
RUNPOD_NETWORK_STORAGE_PATH=/runpod-volume
|
||||||
|
PORT=8188
|
||||||
|
|
||||||
|
# Opsiyonel
|
||||||
|
LISTEN=0.0.0.0
|
||||||
|
COMFYUI_ARGS=--preview-method auto
|
||||||
|
DOWNLOAD_MODELS=sdxl-base,sdxl-vae # Otomatik indirme için
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ports:
|
||||||
|
- **Container Port**: 8188
|
||||||
|
- **Expose HTTP Ports**: 8188
|
||||||
|
|
||||||
|
## 3. Model Klasör Yapısı
|
||||||
|
|
||||||
|
Network storage'da şu yapı oluşturulmalı:
|
||||||
|
```
|
||||||
|
/runpod-volume/
|
||||||
|
└── models/
|
||||||
|
├── checkpoints/ # Ana modeller (SDXL, SD 1.5, vb.)
|
||||||
|
│ ├── sd_xl_base_1.0.safetensors
|
||||||
|
│ └── sd_xl_refiner_1.0.safetensors
|
||||||
|
├── loras/ # LoRA modelleri
|
||||||
|
├── vae/ # VAE modelleri
|
||||||
|
│ └── sdxl_vae.safetensors
|
||||||
|
├── controlnet/ # ControlNet modelleri
|
||||||
|
├── upscale_models/ # Upscaler modeller
|
||||||
|
├── text_encoders/ # CLIP modelleri
|
||||||
|
├── clip/ # Legacy CLIP klasörü
|
||||||
|
├── diffusion_models/ # UNet modelleri
|
||||||
|
├── unet/ # Legacy UNet klasörü
|
||||||
|
├── embeddings/ # Textual Inversion
|
||||||
|
└── clip_vision/ # CLIP Vision modelleri
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Popüler Modeller
|
||||||
|
|
||||||
|
### Checkpoints:
|
||||||
|
```bash
|
||||||
|
# SDXL Base
|
||||||
|
wget https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors
|
||||||
|
|
||||||
|
# SDXL Refiner
|
||||||
|
wget https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0/resolve/main/sd_xl_refiner_1.0.safetensors
|
||||||
|
|
||||||
|
# SD 1.5
|
||||||
|
wget https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors
|
||||||
|
```
|
||||||
|
|
||||||
|
### VAE:
|
||||||
|
```bash
|
||||||
|
# SDXL VAE
|
||||||
|
wget https://huggingface.co/stabilityai/sdxl-vae/resolve/main/sdxl_vae.safetensors
|
||||||
|
|
||||||
|
# SD 1.5 VAE
|
||||||
|
wget https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors
|
||||||
|
```
|
||||||
|
|
||||||
|
### ControlNet:
|
||||||
|
```bash
|
||||||
|
# SDXL Canny
|
||||||
|
wget https://huggingface.co/lllyasviel/sd_control_collection/resolve/main/diffusers_xl_canny_mid.safetensors
|
||||||
|
|
||||||
|
# SDXL Depth
|
||||||
|
wget https://huggingface.co/lllyasviel/sd_control_collection/resolve/main/diffusers_xl_depth_mid.safetensors
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Deployment
|
||||||
|
|
||||||
|
### Build ve Push:
|
||||||
|
```bash
|
||||||
|
# Docker image build
|
||||||
|
docker build -t your-registry/comfyui-runpod:latest .
|
||||||
|
|
||||||
|
# Registry'ye push
|
||||||
|
docker push your-registry/comfyui-runpod:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### RunPod'da Deploy:
|
||||||
|
1. Template oluşturun
|
||||||
|
2. Network storage'ı mount edin
|
||||||
|
3. Environment variables'ları ayarlayın
|
||||||
|
4. Deploy edin
|
||||||
|
|
||||||
|
## 6. Monitoring
|
||||||
|
|
||||||
|
Container loglarında şunları göreceksiniz:
|
||||||
|
```
|
||||||
|
2024-11-01 13:15:00 - INFO - RunPod ComfyUI başlatılıyor...
|
||||||
|
2024-11-01 13:15:01 - INFO - Network storage hazır
|
||||||
|
2024-11-01 13:15:02 - INFO - Models klasörü network storage'a bağlandı
|
||||||
|
2024-11-01 13:15:03 - INFO - Model klasörü: checkpoints (2 dosya)
|
||||||
|
2024-11-01 13:15:04 - INFO - ComfyUI başlatılıyor...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Troubleshooting
|
||||||
|
|
||||||
|
### Network Storage Mount Edilmezse:
|
||||||
|
- Local models klasörü kullanılır
|
||||||
|
- Logları kontrol edin: `RUNPOD_NETWORK_STORAGE_PATH` doğru mu?
|
||||||
|
|
||||||
|
### Modeller Bulunamazsa:
|
||||||
|
- Network storage'da model dosyaları var mı kontrol edin
|
||||||
|
- Dosya izinlerini kontrol edin
|
||||||
|
- Symlink'in doğru çalıştığını kontrol edin
|
||||||
|
|
||||||
|
### Performance İyileştirme:
|
||||||
|
- GPU instance kullanın (CPU yerine)
|
||||||
|
- Dockerfile'da `--cpu` parametresini kaldırın
|
||||||
|
- CUDA support ekleyin
|
||||||
206
start_runpod.py
Normal file
206
start_runpod.py
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
RunPod başlangıç scripti - Network storage mount ve model yönetimi
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Logging setup
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def mount_runpod_storage():
|
||||||
|
"""RunPod network storage'ı mount et"""
|
||||||
|
try:
|
||||||
|
# RunPod network storage path (environment variable'dan al)
|
||||||
|
network_storage_path = os.environ.get('RUNPOD_NETWORK_STORAGE_PATH', '/runpod-volume')
|
||||||
|
models_storage_path = os.path.join(network_storage_path, 'models')
|
||||||
|
|
||||||
|
# Local models klasörü
|
||||||
|
local_models_path = '/app/models'
|
||||||
|
|
||||||
|
logger.info(f"Network storage path: {network_storage_path}")
|
||||||
|
logger.info(f"Models storage path: {models_storage_path}")
|
||||||
|
|
||||||
|
# Network storage'da models klasörü var mı kontrol et
|
||||||
|
if os.path.exists(models_storage_path):
|
||||||
|
logger.info("Network storage'da models klasörü bulundu")
|
||||||
|
|
||||||
|
# Local models klasörünü sil ve symlink oluştur
|
||||||
|
if os.path.exists(local_models_path):
|
||||||
|
if os.path.islink(local_models_path):
|
||||||
|
os.unlink(local_models_path)
|
||||||
|
else:
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(local_models_path)
|
||||||
|
|
||||||
|
# Symlink oluştur
|
||||||
|
os.symlink(models_storage_path, local_models_path)
|
||||||
|
logger.info(f"Models klasörü network storage'a bağlandı: {models_storage_path} -> {local_models_path}")
|
||||||
|
|
||||||
|
# Model klasörlerini kontrol et
|
||||||
|
check_model_folders(local_models_path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.warning(f"Network storage'da models klasörü bulunamadı: {models_storage_path}")
|
||||||
|
logger.info("Local models klasörü kullanılacak")
|
||||||
|
|
||||||
|
# Network storage'da models klasörü oluştur
|
||||||
|
os.makedirs(models_storage_path, exist_ok=True)
|
||||||
|
logger.info(f"Network storage'da models klasörü oluşturuldu: {models_storage_path}")
|
||||||
|
|
||||||
|
# Mevcut local models'i network storage'a taşı
|
||||||
|
if os.path.exists(local_models_path) and not os.path.islink(local_models_path):
|
||||||
|
import shutil
|
||||||
|
shutil.copytree(local_models_path, models_storage_path, dirs_exist_ok=True)
|
||||||
|
shutil.rmtree(local_models_path)
|
||||||
|
logger.info("Local models network storage'a taşındı")
|
||||||
|
|
||||||
|
# Symlink oluştur
|
||||||
|
os.symlink(models_storage_path, local_models_path)
|
||||||
|
logger.info("Models klasörü network storage'a bağlandı")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Network storage mount hatası: {e}")
|
||||||
|
logger.info("Local models klasörü kullanılacak")
|
||||||
|
ensure_local_model_folders()
|
||||||
|
|
||||||
|
def check_model_folders(models_path):
|
||||||
|
"""Model klasörlerinin varlığını kontrol et"""
|
||||||
|
required_folders = [
|
||||||
|
'checkpoints',
|
||||||
|
'loras',
|
||||||
|
'vae',
|
||||||
|
'controlnet',
|
||||||
|
'upscale_models',
|
||||||
|
'text_encoders',
|
||||||
|
'clip',
|
||||||
|
'diffusion_models',
|
||||||
|
'unet',
|
||||||
|
'embeddings',
|
||||||
|
'clip_vision'
|
||||||
|
]
|
||||||
|
|
||||||
|
for folder in required_folders:
|
||||||
|
folder_path = os.path.join(models_path, folder)
|
||||||
|
if not os.path.exists(folder_path):
|
||||||
|
os.makedirs(folder_path, exist_ok=True)
|
||||||
|
logger.info(f"Model klasörü oluşturuldu: {folder}")
|
||||||
|
else:
|
||||||
|
# Klasördeki dosya sayısını kontrol et
|
||||||
|
file_count = len([f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))])
|
||||||
|
logger.info(f"Model klasörü: {folder} ({file_count} dosya)")
|
||||||
|
|
||||||
|
def ensure_local_model_folders():
|
||||||
|
"""Local model klasörlerini oluştur"""
|
||||||
|
models_path = '/app/models'
|
||||||
|
check_model_folders(models_path)
|
||||||
|
|
||||||
|
def download_essential_models():
|
||||||
|
"""Temel modelleri indir (opsiyonel)"""
|
||||||
|
try:
|
||||||
|
models_to_download = os.environ.get('DOWNLOAD_MODELS', '').split(',')
|
||||||
|
models_to_download = [m.strip() for m in models_to_download if m.strip()]
|
||||||
|
|
||||||
|
if not models_to_download:
|
||||||
|
logger.info("İndirilecek model belirtilmedi")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(f"İndirilecek modeller: {models_to_download}")
|
||||||
|
|
||||||
|
# Burada model indirme logic'i eklenebilir
|
||||||
|
# Örnek: huggingface-hub kullanarak
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Model indirme hatası: {e}")
|
||||||
|
|
||||||
|
def setup_environment():
|
||||||
|
"""Çevre değişkenlerini ayarla"""
|
||||||
|
# ComfyUI için gerekli environment variables
|
||||||
|
os.environ['HF_HUB_DISABLE_TELEMETRY'] = '1'
|
||||||
|
os.environ['DO_NOT_TRACK'] = '1'
|
||||||
|
|
||||||
|
# RunPod specific
|
||||||
|
if 'RUNPOD_POD_ID' in os.environ:
|
||||||
|
logger.info(f"RunPod Pod ID: {os.environ['RUNPOD_POD_ID']}")
|
||||||
|
|
||||||
|
# Port ayarı
|
||||||
|
port = os.environ.get('PORT', '8188')
|
||||||
|
os.environ['PORT'] = port
|
||||||
|
logger.info(f"Server port: {port}")
|
||||||
|
|
||||||
|
def wait_for_storage():
|
||||||
|
"""Network storage'ın hazır olmasını bekle"""
|
||||||
|
max_wait = 30 # 30 saniye
|
||||||
|
wait_interval = 2
|
||||||
|
|
||||||
|
network_storage_path = os.environ.get('RUNPOD_NETWORK_STORAGE_PATH', '/runpod-volume')
|
||||||
|
|
||||||
|
for i in range(0, max_wait, wait_interval):
|
||||||
|
if os.path.exists(network_storage_path):
|
||||||
|
logger.info("Network storage hazır")
|
||||||
|
return True
|
||||||
|
|
||||||
|
logger.info(f"Network storage bekleniyor... ({i}/{max_wait}s)")
|
||||||
|
time.sleep(wait_interval)
|
||||||
|
|
||||||
|
logger.warning("Network storage timeout - local storage kullanılacak")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Ana başlangıç fonksiyonu"""
|
||||||
|
logger.info("RunPod ComfyUI başlatılıyor...")
|
||||||
|
|
||||||
|
# Environment setup
|
||||||
|
setup_environment()
|
||||||
|
|
||||||
|
# Network storage'ı bekle
|
||||||
|
wait_for_storage()
|
||||||
|
|
||||||
|
# Network storage mount
|
||||||
|
mount_runpod_storage()
|
||||||
|
|
||||||
|
# Temel modelleri indir (opsiyonel)
|
||||||
|
download_essential_models()
|
||||||
|
|
||||||
|
# ComfyUI'yi başlat
|
||||||
|
logger.info("ComfyUI başlatılıyor...")
|
||||||
|
|
||||||
|
# Port ve listen address
|
||||||
|
port = os.environ.get('PORT', '8188')
|
||||||
|
listen = os.environ.get('LISTEN', '0.0.0.0')
|
||||||
|
|
||||||
|
# ComfyUI command
|
||||||
|
cmd = [
|
||||||
|
sys.executable, 'main.py',
|
||||||
|
'--listen', listen,
|
||||||
|
'--port', port,
|
||||||
|
'--cpu' # CPU mode for RunPod
|
||||||
|
]
|
||||||
|
|
||||||
|
# Extra args
|
||||||
|
if os.environ.get('COMFYUI_ARGS'):
|
||||||
|
extra_args = os.environ['COMFYUI_ARGS'].split()
|
||||||
|
cmd.extend(extra_args)
|
||||||
|
|
||||||
|
logger.info(f"ComfyUI komutu: {' '.join(cmd)}")
|
||||||
|
|
||||||
|
# ComfyUI'yi başlat
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("ComfyUI durduruldu")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"ComfyUI başlatma hatası: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
63
test_workflow.json
Normal file
63
test_workflow.json
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"input": {
|
||||||
|
"workflow": {
|
||||||
|
"3": {
|
||||||
|
"inputs": {
|
||||||
|
"seed": 42,
|
||||||
|
"steps": 20,
|
||||||
|
"cfg": 7.0,
|
||||||
|
"sampler_name": "euler",
|
||||||
|
"scheduler": "normal",
|
||||||
|
"denoise": 1.0,
|
||||||
|
"model": ["4", 0],
|
||||||
|
"positive": ["6", 0],
|
||||||
|
"negative": ["7", 0],
|
||||||
|
"latent_image": ["5", 0]
|
||||||
|
},
|
||||||
|
"class_type": "KSampler"
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
"inputs": {
|
||||||
|
"ckpt_name": "sd_xl_base_1.0.safetensors"
|
||||||
|
},
|
||||||
|
"class_type": "CheckpointLoaderSimple"
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"inputs": {
|
||||||
|
"width": 1024,
|
||||||
|
"height": 1024,
|
||||||
|
"batch_size": 1
|
||||||
|
},
|
||||||
|
"class_type": "EmptyLatentImage"
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
"inputs": {
|
||||||
|
"text": "a beautiful landscape with mountains and a lake, highly detailed, 8k",
|
||||||
|
"clip": ["4", 1]
|
||||||
|
},
|
||||||
|
"class_type": "CLIPTextEncode"
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
"inputs": {
|
||||||
|
"text": "blurry, low quality, distorted",
|
||||||
|
"clip": ["4", 1]
|
||||||
|
},
|
||||||
|
"class_type": "CLIPTextEncode"
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
"inputs": {
|
||||||
|
"samples": ["3", 0],
|
||||||
|
"vae": ["4", 2]
|
||||||
|
},
|
||||||
|
"class_type": "VAEDecode"
|
||||||
|
},
|
||||||
|
"9": {
|
||||||
|
"inputs": {
|
||||||
|
"filename_prefix": "ComfyUI",
|
||||||
|
"images": ["8", 0]
|
||||||
|
},
|
||||||
|
"class_type": "SaveImage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user