Refactor of http and engine entrypoints to allow custom override (#14869)
This commit is contained in:
@@ -27,15 +27,14 @@ import random
|
|||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import AsyncIterator, Dict, Iterator, List, Optional, Tuple, Union
|
from typing import AsyncIterator, Callable, Dict, Iterator, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import zmq
|
|
||||||
|
|
||||||
# Fix a bug of Python threading
|
# Fix a bug of Python threading
|
||||||
setattr(threading, "_register_atexit", lambda *args, **kwargs: None)
|
setattr(threading, "_register_atexit", lambda *args, **kwargs: None)
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import uvloop
|
import uvloop
|
||||||
|
import zmq
|
||||||
|
|
||||||
from sglang.srt.entrypoints.EngineBase import EngineBase
|
from sglang.srt.entrypoints.EngineBase import EngineBase
|
||||||
from sglang.srt.managers.data_parallel_controller import (
|
from sglang.srt.managers.data_parallel_controller import (
|
||||||
@@ -90,6 +89,102 @@ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
|||||||
_is_cuda = is_cuda()
|
_is_cuda = is_cuda()
|
||||||
|
|
||||||
|
|
||||||
|
def _launch_subprocesses(
|
||||||
|
server_args: ServerArgs, port_args: Optional[PortArgs] = None
|
||||||
|
) -> Tuple[TokenizerManager, TemplateManager, Dict, PortArgs]:
|
||||||
|
"""
|
||||||
|
Launch the TokenizerManager in the main process, the Scheduler in a subprocess, and the DetokenizerManager in another subprocess.
|
||||||
|
"""
|
||||||
|
# Configure global environment
|
||||||
|
configure_logger(server_args)
|
||||||
|
_set_envs_and_config(server_args)
|
||||||
|
server_args.check_server_args()
|
||||||
|
|
||||||
|
# Allocate ports for inter-process communications
|
||||||
|
if port_args is None:
|
||||||
|
port_args = PortArgs.init_new(server_args)
|
||||||
|
logger.info(f"{server_args=}")
|
||||||
|
|
||||||
|
# If using model from www.modelscope.cn, first download the model
|
||||||
|
server_args.model_path, server_args.tokenizer_path = prepare_model_and_tokenizer(
|
||||||
|
server_args.model_path, server_args.tokenizer_path
|
||||||
|
)
|
||||||
|
|
||||||
|
# Launch scheduler processes
|
||||||
|
scheduler_procs, scheduler_pipe_readers = _launch_scheduler_processes(
|
||||||
|
server_args=server_args,
|
||||||
|
port_args=port_args,
|
||||||
|
)
|
||||||
|
|
||||||
|
if server_args.node_rank >= 1:
|
||||||
|
# In multi-node cases, non-zero rank nodes do not need to run tokenizer or detokenizer,
|
||||||
|
# so they can just wait here.
|
||||||
|
|
||||||
|
for reader in scheduler_pipe_readers:
|
||||||
|
data = reader.recv()
|
||||||
|
assert data["status"] == "ready"
|
||||||
|
|
||||||
|
if os.getenv("SGLANG_BLOCK_NONZERO_RANK_CHILDREN") == "0":
|
||||||
|
# When using `Engine` as a Python API, we don't want to block here.
|
||||||
|
return None, None, None, port_args
|
||||||
|
|
||||||
|
launch_dummy_health_check_server(
|
||||||
|
server_args.host, server_args.port, server_args.enable_metrics
|
||||||
|
)
|
||||||
|
|
||||||
|
for proc in scheduler_procs:
|
||||||
|
proc.join()
|
||||||
|
logger.error(
|
||||||
|
f"Scheduler or DataParallelController {proc.pid} terminated with {proc.exitcode}"
|
||||||
|
)
|
||||||
|
return None, None, None, port_args
|
||||||
|
|
||||||
|
# Launch detokenizer process
|
||||||
|
detoken_proc = mp.Process(
|
||||||
|
target=run_detokenizer_process,
|
||||||
|
args=(
|
||||||
|
server_args,
|
||||||
|
port_args,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
detoken_proc.start()
|
||||||
|
|
||||||
|
# Init tokenizer manager first, as the bootstrap server is initialized here
|
||||||
|
if server_args.tokenizer_worker_num == 1:
|
||||||
|
tokenizer_manager, template_manager = _init_tokenizer_manager(
|
||||||
|
server_args, port_args
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Launch multi-tokenizer router
|
||||||
|
tokenizer_manager = MultiTokenizerRouter(server_args, port_args)
|
||||||
|
template_manager = None
|
||||||
|
|
||||||
|
# Wait for the model to finish loading
|
||||||
|
scheduler_infos = []
|
||||||
|
for i in range(len(scheduler_pipe_readers)):
|
||||||
|
try:
|
||||||
|
data = scheduler_pipe_readers[i].recv()
|
||||||
|
except EOFError:
|
||||||
|
logger.error(
|
||||||
|
f"Rank {i} scheduler is dead. Please check if there are relevant logs."
|
||||||
|
)
|
||||||
|
scheduler_procs[i].join()
|
||||||
|
logger.error(f"Exit code: {scheduler_procs[i].exitcode}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
if data["status"] != "ready":
|
||||||
|
raise RuntimeError(
|
||||||
|
"Initialization failed. Please see the error messages above."
|
||||||
|
)
|
||||||
|
scheduler_infos.append(data)
|
||||||
|
|
||||||
|
# Get back some info from scheduler to tokenizer_manager
|
||||||
|
scheduler_info = scheduler_infos[0]
|
||||||
|
tokenizer_manager.max_req_input_len = scheduler_info["max_req_input_len"]
|
||||||
|
|
||||||
|
return tokenizer_manager, template_manager, scheduler_info, port_args
|
||||||
|
|
||||||
|
|
||||||
class Engine(EngineBase):
|
class Engine(EngineBase):
|
||||||
"""
|
"""
|
||||||
The entry point to the inference engine.
|
The entry point to the inference engine.
|
||||||
@@ -104,6 +199,11 @@ class Engine(EngineBase):
|
|||||||
2. Inter-process communication is done through IPC (each process uses a different port) via the ZMQ library.
|
2. Inter-process communication is done through IPC (each process uses a different port) via the ZMQ library.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Some fields to allow people to override the server args
|
||||||
|
# and launch processes for their private forks.
|
||||||
|
launch_subprocesses_func: Callable = staticmethod(_launch_subprocesses)
|
||||||
|
server_args_class: ServerArgs = ServerArgs
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
The arguments of this function is the same as `sglang/srt/server_args.py::ServerArgs`.
|
The arguments of this function is the same as `sglang/srt/server_args.py::ServerArgs`.
|
||||||
@@ -119,7 +219,7 @@ class Engine(EngineBase):
|
|||||||
if "log_level" not in kwargs:
|
if "log_level" not in kwargs:
|
||||||
# Do not print logs by default
|
# Do not print logs by default
|
||||||
kwargs["log_level"] = "error"
|
kwargs["log_level"] = "error"
|
||||||
server_args = ServerArgs(**kwargs)
|
server_args = self.server_args_class(**kwargs)
|
||||||
self.server_args = server_args
|
self.server_args = server_args
|
||||||
logger.info(f"{server_args=}")
|
logger.info(f"{server_args=}")
|
||||||
|
|
||||||
@@ -128,7 +228,7 @@ class Engine(EngineBase):
|
|||||||
|
|
||||||
# Launch subprocesses
|
# Launch subprocesses
|
||||||
tokenizer_manager, template_manager, scheduler_info, port_args = (
|
tokenizer_manager, template_manager, scheduler_info, port_args = (
|
||||||
_launch_subprocesses(server_args=server_args)
|
self.launch_subprocesses_func(server_args=server_args)
|
||||||
)
|
)
|
||||||
self.tokenizer_manager = tokenizer_manager
|
self.tokenizer_manager = tokenizer_manager
|
||||||
self.template_manager = template_manager
|
self.template_manager = template_manager
|
||||||
@@ -780,28 +880,14 @@ def _init_tokenizer_manager(
|
|||||||
return tokenizer_manager, template_manager
|
return tokenizer_manager, template_manager
|
||||||
|
|
||||||
|
|
||||||
def _launch_subprocesses(
|
def _launch_scheduler_processes(
|
||||||
server_args: ServerArgs, port_args: Optional[PortArgs] = None
|
server_args: ServerArgs,
|
||||||
) -> Tuple[TokenizerManager, TemplateManager, Dict, PortArgs]:
|
port_args: PortArgs,
|
||||||
"""
|
run_scheduler_process_func: Callable = run_scheduler_process,
|
||||||
Launch the TokenizerManager in the main process, the Scheduler in a subprocess, and the DetokenizerManager in another subprocess.
|
run_data_parallel_controller_process_func: Callable = run_data_parallel_controller_process,
|
||||||
"""
|
):
|
||||||
# Configure global environment
|
|
||||||
configure_logger(server_args)
|
|
||||||
_set_envs_and_config(server_args)
|
|
||||||
server_args.check_server_args()
|
|
||||||
|
|
||||||
# Allocate ports for inter-process communications
|
|
||||||
if port_args is None:
|
|
||||||
port_args = PortArgs.init_new(server_args)
|
|
||||||
logger.info(f"{server_args=}")
|
|
||||||
|
|
||||||
# If using model from www.modelscope.cn, first download the model.
|
|
||||||
server_args.model_path, server_args.tokenizer_path = prepare_model_and_tokenizer(
|
|
||||||
server_args.model_path, server_args.tokenizer_path
|
|
||||||
)
|
|
||||||
|
|
||||||
scheduler_procs = []
|
scheduler_procs = []
|
||||||
|
|
||||||
if server_args.dp_size == 1:
|
if server_args.dp_size == 1:
|
||||||
# Launch tensor parallel scheduler processes
|
# Launch tensor parallel scheduler processes
|
||||||
memory_saver_adapter = TorchMemorySaverAdapter.create(
|
memory_saver_adapter = TorchMemorySaverAdapter.create(
|
||||||
@@ -835,7 +921,7 @@ def _launch_subprocesses(
|
|||||||
|
|
||||||
with maybe_reindex_device_id(gpu_id) as gpu_id:
|
with maybe_reindex_device_id(gpu_id) as gpu_id:
|
||||||
proc = mp.Process(
|
proc = mp.Process(
|
||||||
target=run_scheduler_process,
|
target=run_scheduler_process_func,
|
||||||
args=(
|
args=(
|
||||||
server_args,
|
server_args,
|
||||||
port_args,
|
port_args,
|
||||||
@@ -859,76 +945,10 @@ def _launch_subprocesses(
|
|||||||
reader, writer = mp.Pipe(duplex=False)
|
reader, writer = mp.Pipe(duplex=False)
|
||||||
scheduler_pipe_readers = [reader]
|
scheduler_pipe_readers = [reader]
|
||||||
proc = mp.Process(
|
proc = mp.Process(
|
||||||
target=run_data_parallel_controller_process,
|
target=run_data_parallel_controller_process_func,
|
||||||
args=(server_args, port_args, writer),
|
args=(server_args, port_args, writer),
|
||||||
)
|
)
|
||||||
proc.start()
|
proc.start()
|
||||||
scheduler_procs.append(proc)
|
scheduler_procs.append(proc)
|
||||||
|
|
||||||
if server_args.node_rank >= 1:
|
return scheduler_procs, scheduler_pipe_readers
|
||||||
# In multi-node cases, non-zero rank nodes do not need to run tokenizer or detokenizer,
|
|
||||||
# so they can just wait here.
|
|
||||||
|
|
||||||
for reader in scheduler_pipe_readers:
|
|
||||||
data = reader.recv()
|
|
||||||
assert data["status"] == "ready"
|
|
||||||
|
|
||||||
if os.getenv("SGLANG_BLOCK_NONZERO_RANK_CHILDREN") == "0":
|
|
||||||
# When using `Engine` as a Python API, we don't want to block here.
|
|
||||||
return None, None, None, port_args
|
|
||||||
|
|
||||||
launch_dummy_health_check_server(
|
|
||||||
server_args.host, server_args.port, server_args.enable_metrics
|
|
||||||
)
|
|
||||||
|
|
||||||
for proc in scheduler_procs:
|
|
||||||
proc.join()
|
|
||||||
logger.error(
|
|
||||||
f"Scheduler or DataParallelController {proc.pid} terminated with {proc.exitcode}"
|
|
||||||
)
|
|
||||||
return None, None, None, port_args
|
|
||||||
|
|
||||||
# Launch detokenizer process
|
|
||||||
detoken_proc = mp.Process(
|
|
||||||
target=run_detokenizer_process,
|
|
||||||
args=(
|
|
||||||
server_args,
|
|
||||||
port_args,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
detoken_proc.start()
|
|
||||||
|
|
||||||
# Init tokenizer manager first, as the bootstrap server is initialized here
|
|
||||||
if server_args.tokenizer_worker_num == 1:
|
|
||||||
tokenizer_manager, template_manager = _init_tokenizer_manager(
|
|
||||||
server_args, port_args
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Launch multi-tokenizer router
|
|
||||||
tokenizer_manager = MultiTokenizerRouter(server_args, port_args)
|
|
||||||
template_manager = None
|
|
||||||
|
|
||||||
# Wait for the model to finish loading
|
|
||||||
scheduler_infos = []
|
|
||||||
for i in range(len(scheduler_pipe_readers)):
|
|
||||||
try:
|
|
||||||
data = scheduler_pipe_readers[i].recv()
|
|
||||||
except EOFError:
|
|
||||||
logger.error(
|
|
||||||
f"Rank {i} scheduler is dead. Please check if there are relevant logs."
|
|
||||||
)
|
|
||||||
scheduler_procs[i].join()
|
|
||||||
logger.error(f"Exit code: {scheduler_procs[i].exitcode}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
if data["status"] != "ready":
|
|
||||||
raise RuntimeError(
|
|
||||||
"Initialization failed. Please see the error messages above."
|
|
||||||
)
|
|
||||||
scheduler_infos.append(data)
|
|
||||||
|
|
||||||
# Assume all schedulers have the same scheduler_info
|
|
||||||
scheduler_info = scheduler_infos[0]
|
|
||||||
tokenizer_manager.max_req_input_len = scheduler_info["max_req_input_len"]
|
|
||||||
|
|
||||||
return tokenizer_manager, template_manager, scheduler_info, port_args
|
|
||||||
|
|||||||
@@ -25,16 +25,22 @@ import os
|
|||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any, AsyncIterator, Callable, Dict, List, Optional, Union
|
from typing import (
|
||||||
|
Any,
|
||||||
from sglang.srt.tracing.trace import process_tracing_init, trace_set_thread_info
|
AsyncGenerator,
|
||||||
|
AsyncIterator,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
# Fix a bug of Python threading
|
# Fix a bug of Python threading
|
||||||
setattr(threading, "_register_atexit", lambda *args, **kwargs: None)
|
setattr(threading, "_register_atexit", lambda *args, **kwargs: None)
|
||||||
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
from typing import AsyncGenerator
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import orjson
|
import orjson
|
||||||
@@ -119,6 +125,7 @@ from sglang.srt.managers.tokenizer_manager import ServerStatus, TokenizerManager
|
|||||||
from sglang.srt.metrics.func_timer import enable_func_timer
|
from sglang.srt.metrics.func_timer import enable_func_timer
|
||||||
from sglang.srt.parser.reasoning_parser import ReasoningParser
|
from sglang.srt.parser.reasoning_parser import ReasoningParser
|
||||||
from sglang.srt.server_args import PortArgs, ServerArgs
|
from sglang.srt.server_args import PortArgs, ServerArgs
|
||||||
|
from sglang.srt.tracing.trace import process_tracing_init, trace_set_thread_info
|
||||||
from sglang.srt.utils import (
|
from sglang.srt.utils import (
|
||||||
add_api_key_middleware,
|
add_api_key_middleware,
|
||||||
add_prometheus_middleware,
|
add_prometheus_middleware,
|
||||||
@@ -134,6 +141,7 @@ from sglang.version import __version__
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
|
|
||||||
|
# Global constants
|
||||||
HEALTH_CHECK_TIMEOUT = int(os.getenv("SGLANG_HEALTH_CHECK_TIMEOUT", 20))
|
HEALTH_CHECK_TIMEOUT = int(os.getenv("SGLANG_HEALTH_CHECK_TIMEOUT", 20))
|
||||||
WAIT_WEIGHTS_READY_TIMEOUT = int(os.getenv("SGLANG_WAIT_WEIGHTS_READY_TIMEOUT", 120))
|
WAIT_WEIGHTS_READY_TIMEOUT = int(os.getenv("SGLANG_WAIT_WEIGHTS_READY_TIMEOUT", 120))
|
||||||
|
|
||||||
@@ -154,8 +162,15 @@ def set_global_state(global_state: _GlobalState):
|
|||||||
_global_state = global_state
|
_global_state = global_state
|
||||||
|
|
||||||
|
|
||||||
|
def get_global_state() -> _GlobalState:
|
||||||
|
return _global_state
|
||||||
|
|
||||||
|
|
||||||
async def init_multi_tokenizer() -> ServerArgs:
|
async def init_multi_tokenizer() -> ServerArgs:
|
||||||
"""Read args information from shm and init tokenizer manager for current process"""
|
"""
|
||||||
|
Initialization function for multi-process tokenizer mode.
|
||||||
|
It read args information from shm and inits tokenizer manager for current process.
|
||||||
|
"""
|
||||||
|
|
||||||
# Read configuration from shared memory
|
# Read configuration from shared memory
|
||||||
main_pid = get_main_process_id()
|
main_pid = get_main_process_id()
|
||||||
@@ -206,16 +221,12 @@ async def init_multi_tokenizer() -> ServerArgs:
|
|||||||
async def lifespan(fast_api_app: FastAPI):
|
async def lifespan(fast_api_app: FastAPI):
|
||||||
if getattr(fast_api_app, "is_single_tokenizer_mode", False):
|
if getattr(fast_api_app, "is_single_tokenizer_mode", False):
|
||||||
server_args = fast_api_app.server_args
|
server_args = fast_api_app.server_args
|
||||||
warmup_thread_args = fast_api_app.warmup_thread_args
|
warmup_thread_kwargs = fast_api_app.warmup_thread_kwargs
|
||||||
thread_label = "Tokenizer"
|
thread_label = "Tokenizer"
|
||||||
else:
|
else:
|
||||||
# Initialize multi-tokenizer support for worker processes
|
# Initialize multi-tokenizer support for worker processes
|
||||||
server_args = await init_multi_tokenizer()
|
server_args = await init_multi_tokenizer()
|
||||||
warmup_thread_args = (
|
warmup_thread_kwargs = dict(server_args=server_args)
|
||||||
server_args,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
thread_label = f"MultiTokenizer-{_global_state.tokenizer_manager.worker_id}"
|
thread_label = f"MultiTokenizer-{_global_state.tokenizer_manager.worker_id}"
|
||||||
|
|
||||||
# Add prometheus middleware
|
# Add prometheus middleware
|
||||||
@@ -298,7 +309,7 @@ async def lifespan(fast_api_app: FastAPI):
|
|||||||
# Execute the general warmup
|
# Execute the general warmup
|
||||||
warmup_thread = threading.Thread(
|
warmup_thread = threading.Thread(
|
||||||
target=_wait_and_warmup,
|
target=_wait_and_warmup,
|
||||||
args=warmup_thread_args,
|
kwargs=warmup_thread_kwargs,
|
||||||
)
|
)
|
||||||
warmup_thread.start()
|
warmup_thread.start()
|
||||||
|
|
||||||
@@ -434,8 +445,9 @@ async def health_generate(request: Request) -> Response:
|
|||||||
rid = f"HEALTH_CHECK_{time.time()}"
|
rid = f"HEALTH_CHECK_{time.time()}"
|
||||||
|
|
||||||
if _global_state.tokenizer_manager.is_image_gen:
|
if _global_state.tokenizer_manager.is_image_gen:
|
||||||
# Keep this branch for some internal use cases.
|
gri = _global_state.tokenizer_manager.get_image_gen_health_check_request(
|
||||||
raise NotImplementedError("Image generation is not supported yet.")
|
rid, sampling_params
|
||||||
|
)
|
||||||
elif _global_state.tokenizer_manager.is_generation:
|
elif _global_state.tokenizer_manager.is_generation:
|
||||||
gri = GenerateReqInput(
|
gri = GenerateReqInput(
|
||||||
rid=rid,
|
rid=rid,
|
||||||
@@ -1366,106 +1378,6 @@ def _create_error_response(e):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def launch_server(
|
|
||||||
server_args: ServerArgs,
|
|
||||||
pipe_finish_writer: Optional[multiprocessing.connection.Connection] = None,
|
|
||||||
launch_callback: Optional[Callable[[], None]] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Launch SRT (SGLang Runtime) Server.
|
|
||||||
|
|
||||||
The SRT server consists of an HTTP server and an SRT engine.
|
|
||||||
|
|
||||||
- HTTP server: A FastAPI server that routes requests to the engine.
|
|
||||||
- The engine consists of three components:
|
|
||||||
1. TokenizerManager: Tokenizes the requests and sends them to the scheduler.
|
|
||||||
2. Scheduler (subprocess): Receives requests from the Tokenizer Manager, schedules batches, forwards them, and sends the output tokens to the Detokenizer Manager.
|
|
||||||
3. DetokenizerManager (subprocess): Detokenizes the output tokens and sends the result back to the Tokenizer Manager.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
1. The HTTP server, Engine, and TokenizerManager all run in the main process.
|
|
||||||
2. Inter-process communication is done through IPC (each process uses a different port) via the ZMQ library.
|
|
||||||
"""
|
|
||||||
tokenizer_manager, template_manager, scheduler_info, port_args = (
|
|
||||||
_launch_subprocesses(server_args=server_args)
|
|
||||||
)
|
|
||||||
|
|
||||||
set_global_state(
|
|
||||||
_GlobalState(
|
|
||||||
tokenizer_manager=tokenizer_manager,
|
|
||||||
template_manager=template_manager,
|
|
||||||
scheduler_info=scheduler_info,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if server_args.enable_metrics:
|
|
||||||
add_prometheus_track_response_middleware(app)
|
|
||||||
|
|
||||||
# Pass additional arguments to the lifespan function.
|
|
||||||
# They will be used for additional initialization setups.
|
|
||||||
if server_args.tokenizer_worker_num == 1:
|
|
||||||
# If it is single tokenizer mode, we can pass the arguments by attributes of the app object.
|
|
||||||
app.is_single_tokenizer_mode = True
|
|
||||||
app.server_args = server_args
|
|
||||||
app.warmup_thread_args = (
|
|
||||||
server_args,
|
|
||||||
pipe_finish_writer,
|
|
||||||
launch_callback,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add api key authorization
|
|
||||||
# This is only supported in single tokenizer mode.
|
|
||||||
if server_args.api_key:
|
|
||||||
add_api_key_middleware(app, server_args.api_key)
|
|
||||||
else:
|
|
||||||
# If it is multi-tokenizer mode, we need to write the arguments to shared memory
|
|
||||||
# for other worker processes to read.
|
|
||||||
app.is_single_tokenizer_mode = False
|
|
||||||
multi_tokenizer_args_shm = write_data_for_multi_tokenizer(
|
|
||||||
port_args, server_args, scheduler_info
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Update logging configs
|
|
||||||
set_uvicorn_logging_configs()
|
|
||||||
|
|
||||||
# Listen for HTTP requests
|
|
||||||
if server_args.tokenizer_worker_num == 1:
|
|
||||||
uvicorn.run(
|
|
||||||
app,
|
|
||||||
host=server_args.host,
|
|
||||||
port=server_args.port,
|
|
||||||
root_path=server_args.fastapi_root_path,
|
|
||||||
log_level=server_args.log_level_http or server_args.log_level,
|
|
||||||
timeout_keep_alive=5,
|
|
||||||
loop="uvloop",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
from uvicorn.config import LOGGING_CONFIG
|
|
||||||
|
|
||||||
LOGGING_CONFIG["loggers"]["sglang.srt.entrypoints.http_server"] = {
|
|
||||||
"handlers": ["default"],
|
|
||||||
"level": "INFO",
|
|
||||||
"propagate": False,
|
|
||||||
}
|
|
||||||
monkey_patch_uvicorn_multiprocessing()
|
|
||||||
|
|
||||||
uvicorn.run(
|
|
||||||
"sglang.srt.entrypoints.http_server:app",
|
|
||||||
host=server_args.host,
|
|
||||||
port=server_args.port,
|
|
||||||
root_path=server_args.fastapi_root_path,
|
|
||||||
log_level=server_args.log_level_http or server_args.log_level,
|
|
||||||
timeout_keep_alive=5,
|
|
||||||
loop="uvloop",
|
|
||||||
workers=server_args.tokenizer_worker_num,
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
if server_args.tokenizer_worker_num > 1:
|
|
||||||
multi_tokenizer_args_shm.unlink()
|
|
||||||
_global_state.tokenizer_manager.socket_mapping.clear_all_sockets()
|
|
||||||
|
|
||||||
|
|
||||||
# Minimal 32x32 black PNG (base64, GLM4v requires at least 32x32 sized image)
|
# Minimal 32x32 black PNG (base64, GLM4v requires at least 32x32 sized image)
|
||||||
MINIMUM_PNG_PICTURE_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAbUlEQVRYhe3VsQ2AMAxE0Y/lIgNQULD/OqyCMgCihCKSG4yRuKuiNH6JLsoEbMACOGBcua9HOR7Y6w6swBwMy0qLTpkeI77qdEBpBFAHBBDAGH8WrwJKI4AAegUCfAKgEgpQDvh3CR3oQCuav58qlAw73kKCSgAAAABJRU5ErkJggg=="
|
MINIMUM_PNG_PICTURE_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAbUlEQVRYhe3VsQ2AMAxE0Y/lIgNQULD/OqyCMgCihCKSG4yRuKuiNH6JLsoEbMACOGBcua9HOR7Y6w6swBwMy0qLTpkeI77qdEBpBFAHBBDAGH8WrwJKI4AAegUCfAKgEgpQDvh3CR3oQCuav58qlAw73kKCSgAAAABJRU5ErkJggg=="
|
||||||
|
|
||||||
@@ -1501,9 +1413,8 @@ def _execute_server_warmup(
|
|||||||
|
|
||||||
model_info = res.json()
|
model_info = res.json()
|
||||||
|
|
||||||
|
# Construct a warmup request
|
||||||
is_vlm = bool(model_info.get("has_image_understanding", False))
|
is_vlm = bool(model_info.get("has_image_understanding", False))
|
||||||
|
|
||||||
# Send a warmup request
|
|
||||||
if model_info["is_generation"]:
|
if model_info["is_generation"]:
|
||||||
if is_vlm and not server_args.skip_tokenizer_init:
|
if is_vlm and not server_args.skip_tokenizer_init:
|
||||||
request_name = "/v1/chat/completions"
|
request_name = "/v1/chat/completions"
|
||||||
@@ -1554,7 +1465,7 @@ def _execute_server_warmup(
|
|||||||
if server_args.dp_size == 1:
|
if server_args.dp_size == 1:
|
||||||
json_data["text"] = json_data["text"][0]
|
json_data["text"] = json_data["text"][0]
|
||||||
|
|
||||||
# Debug dumping
|
# Config debug dumping
|
||||||
if server_args.debug_tensor_dump_input_file:
|
if server_args.debug_tensor_dump_input_file:
|
||||||
json_data.pop("text", None)
|
json_data.pop("text", None)
|
||||||
json_data["input_ids"] = np.load(
|
json_data["input_ids"] = np.load(
|
||||||
@@ -1562,8 +1473,9 @@ def _execute_server_warmup(
|
|||||||
).tolist()
|
).tolist()
|
||||||
json_data["sampling_params"]["max_new_tokens"] = 0
|
json_data["sampling_params"]["max_new_tokens"] = 0
|
||||||
|
|
||||||
|
# Send a warmup request
|
||||||
|
warmup_timeout = envs.SGLANG_WARMUP_TIMEOUT.get()
|
||||||
try:
|
try:
|
||||||
warmup_timeout = envs.SGLANG_WARMUP_TIMEOUT.get()
|
|
||||||
if server_args.disaggregation_mode == "null":
|
if server_args.disaggregation_mode == "null":
|
||||||
res = requests.post(
|
res = requests.post(
|
||||||
url + request_name,
|
url + request_name,
|
||||||
@@ -1627,13 +1539,16 @@ def _execute_server_warmup(
|
|||||||
|
|
||||||
def _wait_and_warmup(
|
def _wait_and_warmup(
|
||||||
server_args: ServerArgs,
|
server_args: ServerArgs,
|
||||||
pipe_finish_writer: Optional[multiprocessing.connection.Connection],
|
pipe_finish_writer: Optional[multiprocessing.connection.Connection] = None,
|
||||||
launch_callback: Optional[Callable[[], None]] = None,
|
launch_callback: Optional[Callable[[], None]] = None,
|
||||||
|
execute_warmup_func: Callable = _execute_server_warmup,
|
||||||
):
|
):
|
||||||
if server_args.checkpoint_engine_wait_weights_before_ready:
|
if server_args.checkpoint_engine_wait_weights_before_ready:
|
||||||
_wait_weights_ready()
|
_wait_weights_ready()
|
||||||
|
|
||||||
|
# Send a warmup request
|
||||||
if not server_args.skip_server_warmup:
|
if not server_args.skip_server_warmup:
|
||||||
if not _execute_server_warmup(
|
if not execute_warmup_func(
|
||||||
server_args,
|
server_args,
|
||||||
pipe_finish_writer,
|
pipe_finish_writer,
|
||||||
):
|
):
|
||||||
@@ -1641,6 +1556,7 @@ def _wait_and_warmup(
|
|||||||
else:
|
else:
|
||||||
_global_state.tokenizer_manager.server_status = ServerStatus.Up
|
_global_state.tokenizer_manager.server_status = ServerStatus.Up
|
||||||
|
|
||||||
|
# The server is ready for requests
|
||||||
logger.info("The server is fired up and ready to roll!")
|
logger.info("The server is fired up and ready to roll!")
|
||||||
|
|
||||||
if pipe_finish_writer is not None:
|
if pipe_finish_writer is not None:
|
||||||
@@ -1675,3 +1591,106 @@ def _wait_weights_ready():
|
|||||||
f"Consider increasing SGLANG_WAIT_WEIGHTS_READY_TIMEOUT environment variable. "
|
f"Consider increasing SGLANG_WAIT_WEIGHTS_READY_TIMEOUT environment variable. "
|
||||||
f"Current status: initial_weights_loaded={_global_state.tokenizer_manager.initial_weights_loaded}"
|
f"Current status: initial_weights_loaded={_global_state.tokenizer_manager.initial_weights_loaded}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def launch_server(
|
||||||
|
server_args: ServerArgs,
|
||||||
|
pipe_finish_writer: Optional[multiprocessing.connection.Connection] = None,
|
||||||
|
launch_subprocesses_func: Callable = _launch_subprocesses,
|
||||||
|
execute_warmup_func: Callable = _execute_server_warmup,
|
||||||
|
launch_callback: Optional[Callable[[], None]] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Launch SRT (SGLang Runtime) Server.
|
||||||
|
|
||||||
|
The SRT server consists of an HTTP server and an SRT engine.
|
||||||
|
|
||||||
|
- HTTP server: A FastAPI server that routes requests to the engine.
|
||||||
|
- The engine consists of three components:
|
||||||
|
1. TokenizerManager: Tokenizes the requests and sends them to the scheduler.
|
||||||
|
2. Scheduler (subprocess): Receives requests from the Tokenizer Manager, schedules batches, forwards them, and sends the output tokens to the Detokenizer Manager.
|
||||||
|
3. DetokenizerManager (subprocess): Detokenizes the output tokens and sends the result back to the Tokenizer Manager.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
1. The HTTP server, Engine, and TokenizerManager all run in the main process.
|
||||||
|
2. Inter-process communication is done through IPC (each process uses a different port) via the ZMQ library.
|
||||||
|
"""
|
||||||
|
tokenizer_manager, template_manager, scheduler_info, port_args = (
|
||||||
|
launch_subprocesses_func(server_args=server_args)
|
||||||
|
)
|
||||||
|
|
||||||
|
set_global_state(
|
||||||
|
_GlobalState(
|
||||||
|
tokenizer_manager=tokenizer_manager,
|
||||||
|
template_manager=template_manager,
|
||||||
|
scheduler_info=scheduler_info,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if server_args.enable_metrics:
|
||||||
|
add_prometheus_track_response_middleware(app)
|
||||||
|
|
||||||
|
# Pass additional arguments to the lifespan function.
|
||||||
|
# They will be used for additional initialization setups.
|
||||||
|
if server_args.tokenizer_worker_num == 1:
|
||||||
|
# If it is single tokenizer mode, we can pass the arguments by attributes of the app object.
|
||||||
|
app.is_single_tokenizer_mode = True
|
||||||
|
app.server_args = server_args
|
||||||
|
app.warmup_thread_kwargs = dict(
|
||||||
|
server_args=server_args,
|
||||||
|
pipe_finish_writer=pipe_finish_writer,
|
||||||
|
launch_callback=launch_callback,
|
||||||
|
execute_warmup_func=execute_warmup_func,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add api key authorization
|
||||||
|
# This is only supported in single tokenizer mode.
|
||||||
|
if server_args.api_key:
|
||||||
|
add_api_key_middleware(app, server_args.api_key)
|
||||||
|
else:
|
||||||
|
# If it is multi-tokenizer mode, we need to write the arguments to shared memory
|
||||||
|
# for other worker processes to read.
|
||||||
|
app.is_single_tokenizer_mode = False
|
||||||
|
multi_tokenizer_args_shm = write_data_for_multi_tokenizer(
|
||||||
|
port_args, server_args, scheduler_info
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Update logging configs
|
||||||
|
set_uvicorn_logging_configs()
|
||||||
|
|
||||||
|
# Listen for HTTP requests
|
||||||
|
if server_args.tokenizer_worker_num == 1:
|
||||||
|
uvicorn.run(
|
||||||
|
app,
|
||||||
|
host=server_args.host,
|
||||||
|
port=server_args.port,
|
||||||
|
root_path=server_args.fastapi_root_path,
|
||||||
|
log_level=server_args.log_level_http or server_args.log_level,
|
||||||
|
timeout_keep_alive=5,
|
||||||
|
loop="uvloop",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
from uvicorn.config import LOGGING_CONFIG
|
||||||
|
|
||||||
|
LOGGING_CONFIG["loggers"]["sglang.srt.entrypoints.http_server"] = {
|
||||||
|
"handlers": ["default"],
|
||||||
|
"level": "INFO",
|
||||||
|
"propagate": False,
|
||||||
|
}
|
||||||
|
monkey_patch_uvicorn_multiprocessing()
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
"sglang.srt.entrypoints.http_server:app",
|
||||||
|
host=server_args.host,
|
||||||
|
port=server_args.port,
|
||||||
|
root_path=server_args.fastapi_root_path,
|
||||||
|
log_level=server_args.log_level_http or server_args.log_level,
|
||||||
|
timeout_keep_alive=5,
|
||||||
|
loop="uvloop",
|
||||||
|
workers=server_args.tokenizer_worker_num,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
if server_args.tokenizer_worker_num > 1:
|
||||||
|
multi_tokenizer_args_shm.unlink()
|
||||||
|
_global_state.tokenizer_manager.socket_mapping.clear_all_sockets()
|
||||||
|
|||||||
Reference in New Issue
Block a user