switch to internally managed uvicorn

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-08-06 13:24:48 +02:00
parent 0efd18aed5
commit 6e448de56c
4 changed files with 91 additions and 14 deletions
+12 -1
View File
@@ -1,7 +1,17 @@
\# Change Log for SD.Next
# Change Log for SD.Next
## Update for 2026-08-05
### Highlights for 2026-08-05
This release brings **Sefi-Image** and **Mage-Flow** models, plus a new **Nunchaku-Lite** inference engine
On the server side, there are quite a few *under-the-hood* improvements, including optimized startup, optimized webserver, end-to-end profiling, storage analyzer, etc.
There are also several new auxiliary models, such as **Lucida** for background removal
And video processing now supports scripts such as prompt enhance, nudenet, etc.
Plus several quality-of-life improvements and bug-fixes across the board
### Details for 2026-08-05
- **Models**
- [SeFi-Image](https://huggingface.co/SeFi-Image/SeFi-Image-5B-RL) in *Base*, *Turbo* (distilled) and *RL* (finetuned) variants
SeFi is an interesting model that separates generation into semantic and texture latent streams
@@ -31,6 +41,7 @@
- **API**
- add `/sdapi/v1/storage` endpoint to return storage usage info
- **Internal**
- switch internal server to explicit `uvicorn`
- update core requirements
- **Fixes**
- seedvr quality
+33 -11
View File
@@ -1,36 +1,59 @@
import threading
import logging
import time
import asyncio
import uvicorn
import fastapi
from modules.logger import log
class UvicornServer(uvicorn.Server):
def __init__(self, app: fastapi.FastAPI, listen = None, port = None, keyfile = None, certfile = None, loop = "auto", http = "auto"):
def __init__(self, app: fastapi.FastAPI, host = None, listen = None, port = None, keyfile = None, certfile = None, loop = "auto", http = "auto"):
self.app: fastapi.FastAPI = app
self.thread: threading.Thread = None
self.loop = None
self.wants_restart = False
self.should_exit = False
kwargs = {
'loop': loop, # auto, asyncio, uvloop
'http': http, # auto, h11, httptools
'interface': "auto", # auto, asgi3, asgi2, wsgi
'ws': "auto", # auto, websockets, wsproto, websockets-sansio
'timeout_keep_alive': 60, # default=5
'ws_max_size': 1024 * 1024 * 1024, # default 16MB
'ws_max_queue': 64, # default=32
'ws_ping_interval': 30, # default=20
'ws_ping_timeout': 60, # default=20
'timeout_graceful_shutdown': 5, # default=None
'access_log': False, # default=True
'server_header': False, # default=True
'date_header': False, # default=True
'backlog': 4096, # default=2048
'reload': False, # default=False
}
self.config = uvicorn.Config(
app=self.app,
host = "0.0.0.0" if listen else "127.0.0.1",
port = port or 7861,
loop = loop, # auto, asyncio, uvloop
http = http, # auto, h11, httptools
interface = "auto", # auto, asgi3, asgi2, wsgi
ws = "auto", # auto, websockets, wsproto
host = host or ("0.0.0.0" if listen else "127.0.0.1"),
port = port or 7860,
log_level = logging.WARNING,
backlog = 4096, # default=2048
timeout_keep_alive = 60, # default=5
ssl_keyfile = keyfile,
ssl_certfile = certfile,
ws_max_size = 1024 * 1024 * 1024, # default 16MB
**kwargs
)
super().__init__(config=self.config)
log.info(f'Server: uvicorn={kwargs}')
def start(self):
self.thread = threading.Thread(target=self.run, daemon=True)
self.wants_restart = False
self.thread.start()
start = time.time()
while not self.started:
time.sleep(1e-3)
if time.time() - start > 5:
raise RuntimeError("Server failed to start. Please check that the port is available.")
policy = asyncio.get_event_loop_policy()
self.loop = f"{type(policy).__module__}.{type(policy).__name__}"
def stop(self):
self.should_exit = True
@@ -71,7 +94,6 @@ class HypercornServer:
self.thread = threading.Thread(target=self.run, daemon=True)
self.thread.start()
elif self.loop == 'asyncio': # does not run in thread
import asyncio
from hypercorn.asyncio import serve
self.server = serve(self.app, self.config)
asyncio.run(self.server)
+3 -1
View File
@@ -17,11 +17,13 @@ voluptuous
fasteners
limits
orjson
websockets
ftfy
websockets
httptools
# versioned
fastapi==0.124.4
uvicorn==0.52.1
rich==15.0.0
safetensors==0.8.0
peft==0.20.0
+43 -1
View File
@@ -317,6 +317,41 @@ def mount_subpath(app):
log.info(f'Mounted: subpath="{shared.opts.subpath}"')
def start_server(
blocks,
server_name: str | None = None, # pylint: disable=redefined-outer-name
server_port: int | None = None,
ssl_keyfile: str | None = None,
ssl_certfile: str | None = None,
ssl_keyfile_password: str | None = None, # pylint: disable=unused-argument
app_kwargs: dict | None = None,
):
from gradio.routes import App
from modules.server import UvicornServer
if ssl_keyfile is not None and ssl_certfile is None:
raise ValueError("ssl_certfile must be provided if ssl_keyfile is provided.")
server_name = server_name or "127.0.0.1"
server_port = server_port or 7860
url_host_name = "localhost" if server_name == "0.0.0.0" else server_name
if server_name.startswith("[") and server_name.endswith("]"):
server_name = server_name[1:-1]
app = App.create_app(blocks, app_kwargs=app_kwargs)
server = UvicornServer(
app=app,
listen=server_name == '0.0.0.0',
host=server_name,
port=server_port,
keyfile=ssl_keyfile,
certfile=ssl_certfile,
)
server.start()
if ssl_keyfile is not None:
path_to_local_server = f"https://{url_host_name}:{server_port}/"
else:
path_to_local_server = f"http://{url_host_name}:{server_port}/"
return server_name, server_port, path_to_local_server, app, server
def start_ui():
log.debug('UI start sequence')
log.debug(f'UI image support: kanvas={version.get("kanvas", "unknown")}')
@@ -362,8 +397,10 @@ def start_ui():
allowed_paths.append(shared.cmd_opts.models_dir)
if shared.cmd_opts.allowed_paths is not None:
allowed_paths += [p for p in shared.cmd_opts.allowed_paths if os.path.isdir(p)]
log.debug(f'Root paths: {allowed_paths}')
log.info(f'Server: name={server_name} port={shared.cmd_opts.port} paths={allowed_paths} ssl={shared.cmd_opts.tls_keyfile}:{shared.cmd_opts.tls_certfile} auth={len(auth_pairs)}')
with contextlib.redirect_stdout(stdout):
import gradio.networking
gradio.networking.start_server = start_server
app, local_url, share_url = shared.demo.launch( # app is FastAPI(Starlette) instance
share=shared.cmd_opts.share,
server_name=server_name,
@@ -382,6 +419,11 @@ def start_ui():
app_kwargs=fastapi_args,
_frontend=True and shared.cmd_opts.share,
)
uc = shared.demo.server.config
get_name = lambda c: getattr(c, '__name__', c) # pylint: disable=unnecessary-lambda-assignment
log.debug(f'Server config: loop={shared.demo.server.loop} http={get_name(uc.http_protocol_class)} ws={get_name(uc.ws_protocol_class)} interface={uc.interface} workers={uc.workers} backlog={uc.backlog} timeout_keep_alive={uc.timeout_keep_alive} timeout_notify={uc.timeout_notify} ws_max_size={uc.ws_max_size} ws_max_queue={uc.ws_max_queue} ws_ping_interval={uc.ws_ping_interval} ws_ping_timeout={uc.ws_ping_timeout}')
if shared.cmd_opts.data_dir is not None:
modules.gr_tempdir.register_tmp_file(shared.demo, os.path.join(shared.cmd_opts.data_dir, 'x'))
log.info(f'Local URL: {local_url}')