mirror of
https://github.com/vladmandic/automatic
synced 2026-09-13 10:08:43 +02:00
update docker and progress monitoring
Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
+28
-10
@@ -1,5 +1,10 @@
|
||||
# SD.Next Dockerfile
|
||||
# docs: <https://github.com/vladmandic/automatic/wiki/Docker>
|
||||
|
||||
# base image
|
||||
FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime
|
||||
|
||||
# metadata
|
||||
LABEL org.opencontainers.image.vendor="SD.Next"
|
||||
LABEL org.opencontainers.image.authors="vladmandic"
|
||||
LABEL org.opencontainers.image.url="https://github.com/vladmandic/automatic/"
|
||||
@@ -9,31 +14,44 @@ LABEL org.opencontainers.image.licenses="AGPL-3.0"
|
||||
LABEL org.opencontainers.image.title="SD.Next"
|
||||
LABEL org.opencontainers.image.description="SD.Next: Advanced Implementation of Stable Diffusion and other Diffusion-based generative image models"
|
||||
LABEL org.opencontainers.image.base.name="https://hub.docker.com/pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime"
|
||||
WORKDIR /
|
||||
COPY . .
|
||||
LABEL org.opencontainers.image.version="latest"
|
||||
|
||||
# minimum install
|
||||
RUN ["apt-get", "-y", "update"]
|
||||
RUN ["apt-get", "-y", "install", "git", "build-essential", "google-perftools", "curl"]
|
||||
# optional if full cuda-dev is required by some downstream library
|
||||
# RUN ["apt-get", "-y", "nvidia-cuda-toolkit"]
|
||||
RUN ["/usr/sbin/ldconfig"]
|
||||
|
||||
# copy sdnext
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
|
||||
# stop pip and uv from caching
|
||||
ENV PIP_NO_CACHE_DIR=true
|
||||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
ENV UV_NO_CACHE=true
|
||||
ENV SD_INSTALL_DEBUG=true
|
||||
# disable model hashing for faster startup
|
||||
ENV SD_NOHASHING=true
|
||||
# set data directories
|
||||
ENV SD_DATADIR="/mnt/data"
|
||||
ENV SD_MODELSDIR="/mnt/models"
|
||||
# minimum install
|
||||
RUN ["apt-get", "-y", "update"]
|
||||
RUN ["apt-get", "-y", "install", "git", "build-essential", "google-perftools"]
|
||||
RUN ["ldconfig"]
|
||||
ENV SD_DOCKER=true
|
||||
|
||||
# tcmalloc is not required but it is highly recommended
|
||||
ENV LD_PRELOAD=libtcmalloc.so.4
|
||||
# sdnext will run all necessary pip install ops and then exit
|
||||
RUN ["python", "launch.py", "--debug", "--uv", "--use-cuda", "--log", "sdnext.log", "--test", "--optional"]
|
||||
RUN ["python", "/app/launch.py", "--debug", "--uv", "--use-cuda", "--log", "sdnext.log", "--test", "--optional"]
|
||||
# preinstall additional packages to avoid installation during runtime
|
||||
|
||||
# actually run sdnext
|
||||
CMD ["python", "launch.py", "--debug", "--skip-all", "--listen", "--quick", "--api-log", "--log", "sdnext.log"]
|
||||
|
||||
# expose port
|
||||
EXPOSE 7860
|
||||
# TBD add healthcheck function
|
||||
HEALTHCHECK NONE
|
||||
|
||||
# healthcheck function
|
||||
# HEALTHCHECK --interval=60s --timeout=10s --start-period=60s --retries=3 CMD curl --fail http://localhost:7860/sdapi/v1/status || exit 1
|
||||
|
||||
# stop signal
|
||||
STOPSIGNAL SIGINT
|
||||
|
||||
+3
-3
@@ -412,14 +412,14 @@ def get_platform():
|
||||
else:
|
||||
release = platform.release()
|
||||
return {
|
||||
# 'host': platform.node(),
|
||||
'arch': platform.machine(),
|
||||
'cpu': platform.processor(),
|
||||
'system': platform.system(),
|
||||
'release': release,
|
||||
# 'platform': platform.platform(aliased = True, terse = False),
|
||||
# 'version': platform.version(),
|
||||
'python': platform.python_version(),
|
||||
'docker': os.environ.get('SD_INSTALL_DEBUG', None) is not None,
|
||||
# 'host': platform.node(),
|
||||
# 'version': platform.version(),
|
||||
}
|
||||
except Exception as e:
|
||||
return { 'error': e }
|
||||
|
||||
@@ -258,9 +258,7 @@ def main():
|
||||
alive = False
|
||||
requests = 0
|
||||
if round(time.time()) % 120 == 0:
|
||||
state = f'job="{instance.state.job}" {instance.state.job_no}/{instance.state.job_count}' if instance.state.job != '' or instance.state.job_no != 0 or instance.state.job_count != 0 else 'idle'
|
||||
uptime = round(time.time() - instance.state.server_start)
|
||||
installer.log.debug(f'Server: alive={alive} jobs={instance.state.total_jobs} requests={requests} uptime={uptime} memory={get_memory_stats()} backend={instance.backend} state={state}')
|
||||
installer.log.debug(f'Server: alive={alive} requests={requests} memory={get_memory_stats()} {instance.state.status()}')
|
||||
if not alive:
|
||||
if uv is not None and uv.wants_restart:
|
||||
installer.log.info('Server restarting...')
|
||||
|
||||
@@ -38,6 +38,7 @@ class Api:
|
||||
self.add_api_route("/sdapi/v1/log", server.get_log_buffer, methods=["GET"], response_model=List[str])
|
||||
self.add_api_route("/sdapi/v1/start", self.get_session_start, methods=["GET"])
|
||||
self.add_api_route("/sdapi/v1/version", server.get_version, methods=["GET"])
|
||||
self.add_api_route("/sdapi/v1/status", server.get_status, methods=["GET"], response_model=models.ResStatus)
|
||||
self.add_api_route("/sdapi/v1/platform", server.get_platform, methods=["GET"])
|
||||
self.add_api_route("/sdapi/v1/progress", server.get_progress, methods=["GET"], response_model=models.ResProgress)
|
||||
self.add_api_route("/sdapi/v1/interrupt", server.post_interrupt, methods=["POST"])
|
||||
|
||||
@@ -300,6 +300,23 @@ class ResProgress(BaseModel):
|
||||
current_image: str = Field(default=None, title="Current image", description="The current image in base64 format. opts.show_progress_every_n_steps is required for this to work.")
|
||||
textinfo: str = Field(default=None, title="Info text", description="Info text used by WebUI.")
|
||||
|
||||
class ResStatus(BaseModel):
|
||||
status: str = Field(title="Status", description="Current status")
|
||||
task: str = Field(title="Task", description="Current task")
|
||||
timestamp: Optional[str] = Field(title="Timestamp", description="Timestamp of the current job")
|
||||
id: str = Field(title="ID", description="ID of the current task")
|
||||
job: int = Field(title="Job", description="Current job")
|
||||
jobs: int = Field(title="Jobs", description="Total jobs")
|
||||
total: int = Field(title="Total Jobs", description="Total jobs")
|
||||
step: int = Field(title="Step", description="Current step")
|
||||
steps: int = Field(title="Steps", description="Total steps")
|
||||
queued: int = Field(title="Queued", description="Number of queued tasks")
|
||||
uptime: int = Field(title="Uptime", description="Uptime of the server")
|
||||
elapsed: Optional[float] = Field(title="Elapsed time")
|
||||
eta: Optional[float] = Field(title="ETA in secs")
|
||||
progress: Optional[float] = Field(title="Progress", description="The progress with a range of 0 to 1")
|
||||
|
||||
|
||||
class ReqInterrogate(BaseModel):
|
||||
image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.")
|
||||
clip_model: str = Field(default="", title="CLiP Model", description="The interrogate model used.")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import time
|
||||
from typing import Any, Dict
|
||||
from fastapi import Depends
|
||||
from modules import shared
|
||||
@@ -66,7 +67,6 @@ def get_cmd_flags():
|
||||
return vars(shared.cmd_opts)
|
||||
|
||||
def get_progress(req: models.ReqProgress = Depends()):
|
||||
import time
|
||||
if shared.state.job_count == 0:
|
||||
return models.ResProgress(progress=0, eta_relative=0, state=shared.state.dict(), textinfo=shared.state.textinfo)
|
||||
shared.state.do_set_current_image()
|
||||
@@ -85,6 +85,9 @@ def get_progress(req: models.ReqProgress = Depends()):
|
||||
res = models.ResProgress(progress=progress, eta_relative=eta_relative, state=shared.state.dict(), current_image=current_image, textinfo=shared.state.textinfo)
|
||||
return res
|
||||
|
||||
def get_status():
|
||||
return shared.state.status()
|
||||
|
||||
def post_interrupt():
|
||||
shared.state.interrupt()
|
||||
return {}
|
||||
|
||||
@@ -73,7 +73,6 @@ def progressapi(req: ProgressRequest):
|
||||
elapsed = time.time() - shared.state.time_start if shared.state.time_start is not None else 0
|
||||
predicted = elapsed / progress if progress > 0 else None
|
||||
eta = predicted - elapsed if predicted is not None else None
|
||||
# shared.log.debug(f'Progress: step={step_x}:{step_y} batch={batch_x}:{batch_y} current={current} total={total} progress={progress} elapsed={elapsed} eta={eta}')
|
||||
id_live_preview = req.id_live_preview
|
||||
live_preview = None
|
||||
shared.state.set_current_image()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from functools import lru_cache
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
@@ -1271,6 +1272,7 @@ def html(filename):
|
||||
return ""
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_version():
|
||||
version = None
|
||||
if version is None:
|
||||
|
||||
@@ -62,6 +62,38 @@ class State:
|
||||
}
|
||||
return obj
|
||||
|
||||
def status(self):
|
||||
from modules import progress
|
||||
from modules.api import models
|
||||
res = models.ResStatus(
|
||||
task=self.job,
|
||||
id=progress.current_task or '',
|
||||
job=max(self.job_no, 0),
|
||||
jobs=max(self.frame_count, self.job_count, self.job_no),
|
||||
total=self.total_jobs,
|
||||
timestamp=self.job_timestamp if self.job != '' else None,
|
||||
step=self.sampling_step,
|
||||
steps=self.sampling_steps,
|
||||
queued=len(progress.pending_tasks),
|
||||
status='unknown',
|
||||
uptime = round(time.time() - self.server_start)
|
||||
)
|
||||
res.step = res.steps * res.job + res.step
|
||||
res.steps = res.steps * res.jobs
|
||||
res.progress = round(min(1, abs(res.step / res.steps) if res.steps > 0 else 0), 2)
|
||||
res.elapsed = round(time.time() - self.time_start, 2) if self.time_start is not None else None
|
||||
predicted = round(res.elapsed / res.progress, 2) if res.progress > 0 and res.elapsed is not None else None
|
||||
res.eta = round(predicted - res.elapsed, 2) if predicted is not None else None
|
||||
if self.paused:
|
||||
res.status = 'paused'
|
||||
elif self.interrupted:
|
||||
res.status = 'interrupted'
|
||||
elif self.skipped:
|
||||
res.status = 'skipped'
|
||||
else:
|
||||
res.status = 'running' if self.job != '' else 'idle'
|
||||
return res
|
||||
|
||||
def begin(self, title="", api=None):
|
||||
import modules.devices
|
||||
self.total_jobs += 1
|
||||
|
||||
Reference in New Issue
Block a user