[diffusion] CI: improve diffusion CI (#13562)
Co-authored-by: Adarsh Shirawalmath <114558126+adarshxs@users.noreply.github.com>
This commit is contained in:
co-authored by
Adarsh Shirawalmath
parent
af6bcadcf7
commit
127d59cd2c
@@ -244,8 +244,6 @@ jobs:
|
|||||||
|
|
||||||
echo "All benchmark tests completed!"
|
echo "All benchmark tests completed!"
|
||||||
|
|
||||||
# =============================================== multimodal_gen ====================================================
|
|
||||||
|
|
||||||
# Adding a single CUDA13 smoke test to verify that the kernel builds and runs
|
# Adding a single CUDA13 smoke test to verify that the kernel builds and runs
|
||||||
# TODO: Add back this test when it can pass on CI
|
# TODO: Add back this test when it can pass on CI
|
||||||
# cuda13-kernel-smoke-test:
|
# cuda13-kernel-smoke-test:
|
||||||
|
|||||||
@@ -15,26 +15,26 @@ from packaging import version
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
SGL_DIFFUSION_RINGBUFFER_WARNING_INTERVAL: int = 60
|
SGLANG_DIFFUSION_RINGBUFFER_WARNING_INTERVAL: int = 60
|
||||||
SGL_DIFFUSION_NCCL_SO_PATH: str | None = None
|
SGLANG_DIFFUSION_NCCL_SO_PATH: str | None = None
|
||||||
LD_LIBRARY_PATH: str | None = None
|
LD_LIBRARY_PATH: str | None = None
|
||||||
LOCAL_RANK: int = 0
|
LOCAL_RANK: int = 0
|
||||||
CUDA_VISIBLE_DEVICES: str | None = None
|
CUDA_VISIBLE_DEVICES: str | None = None
|
||||||
SGL_DIFFUSION_CACHE_ROOT: str = os.path.expanduser("~/.cache/sgl_diffusion")
|
SGLANG_DIFFUSION_CACHE_ROOT: str = os.path.expanduser("~/.cache/sgl_diffusion")
|
||||||
SGL_DIFFUSION_CONFIG_ROOT: str = os.path.expanduser("~/.config/sgl_diffusion")
|
SGLANG_DIFFUSION_CONFIG_ROOT: str = os.path.expanduser("~/.config/sgl_diffusion")
|
||||||
SGL_DIFFUSION_CONFIGURE_LOGGING: int = 1
|
SGLANG_DIFFUSION_CONFIGURE_LOGGING: int = 1
|
||||||
SGL_DIFFUSION_LOGGING_LEVEL: str = "INFO"
|
SGLANG_DIFFUSION_LOGGING_LEVEL: str = "INFO"
|
||||||
SGL_DIFFUSION_LOGGING_PREFIX: str = ""
|
SGLANG_DIFFUSION_LOGGING_PREFIX: str = ""
|
||||||
SGL_DIFFUSION_LOGGING_CONFIG_PATH: str | None = None
|
SGLANG_DIFFUSION_LOGGING_CONFIG_PATH: str | None = None
|
||||||
SGL_DIFFUSION_TRACE_FUNCTION: int = 0
|
SGLANG_DIFFUSION_TRACE_FUNCTION: int = 0
|
||||||
SGL_DIFFUSION_WORKER_MULTIPROC_METHOD: str = "fork"
|
SGLANG_DIFFUSION_WORKER_MULTIPROC_METHOD: str = "fork"
|
||||||
SGL_DIFFUSION_TARGET_DEVICE: str = "cuda"
|
SGLANG_DIFFUSION_TARGET_DEVICE: str = "cuda"
|
||||||
MAX_JOBS: str | None = None
|
MAX_JOBS: str | None = None
|
||||||
NVCC_THREADS: str | None = None
|
NVCC_THREADS: str | None = None
|
||||||
CMAKE_BUILD_TYPE: str | None = None
|
CMAKE_BUILD_TYPE: str | None = None
|
||||||
VERBOSE: bool = False
|
VERBOSE: bool = False
|
||||||
SGL_DIFFUSION_SERVER_DEV_MODE: bool = False
|
SGLANG_DIFFUSION_SERVER_DEV_MODE: bool = False
|
||||||
SGL_DIFFUSION_STAGE_LOGGING: bool = False
|
SGLANG_DIFFUSION_STAGE_LOGGING: bool = False
|
||||||
|
|
||||||
|
|
||||||
def _is_hip():
|
def _is_hip():
|
||||||
@@ -165,8 +165,8 @@ environment_variables: dict[str, Callable[[], Any]] = {
|
|||||||
# ================== Installation Time Env Vars ==================
|
# ================== Installation Time Env Vars ==================
|
||||||
# Target device of sgl-diffusion, supporting [cuda (by default),
|
# Target device of sgl-diffusion, supporting [cuda (by default),
|
||||||
# rocm, neuron, cpu, openvino]
|
# rocm, neuron, cpu, openvino]
|
||||||
"SGL_DIFFUSION_TARGET_DEVICE": lambda: os.getenv(
|
"SGLANG_DIFFUSION_TARGET_DEVICE": lambda: os.getenv(
|
||||||
"SGL_DIFFUSION_TARGET_DEVICE", "cuda"
|
"SGLANG_DIFFUSION_TARGET_DEVICE", "cuda"
|
||||||
),
|
),
|
||||||
# Maximum number of compilation jobs to run in parallel.
|
# Maximum number of compilation jobs to run in parallel.
|
||||||
# By default this is the number of CPUs
|
# By default this is the number of CPUs
|
||||||
@@ -176,10 +176,10 @@ environment_variables: dict[str, Callable[[], Any]] = {
|
|||||||
# If set, `MAX_JOBS` will be reduced to avoid oversubscribing the CPU.
|
# If set, `MAX_JOBS` will be reduced to avoid oversubscribing the CPU.
|
||||||
"NVCC_THREADS": lambda: os.getenv("NVCC_THREADS", None),
|
"NVCC_THREADS": lambda: os.getenv("NVCC_THREADS", None),
|
||||||
# If set, sgl_diffusion will use precompiled binaries (*.so)
|
# If set, sgl_diffusion will use precompiled binaries (*.so)
|
||||||
"SGL_DIFFUSION_USE_PRECOMPILED": lambda: bool(
|
"SGLANG_DIFFUSION_USE_PRECOMPILED": lambda: bool(
|
||||||
os.environ.get("SGL_DIFFUSION_USE_PRECOMPILED")
|
os.environ.get("SGLANG_DIFFUSION_USE_PRECOMPILED")
|
||||||
)
|
)
|
||||||
or bool(os.environ.get("SGL_DIFFUSION_PRECOMPILED_WHEEL_LOCATION")),
|
or bool(os.environ.get("SGLANG_DIFFUSION_PRECOMPILED_WHEEL_LOCATION")),
|
||||||
# CMake build type
|
# CMake build type
|
||||||
# If not set, defaults to "Debug" or "RelWithDebInfo"
|
# If not set, defaults to "Debug" or "RelWithDebInfo"
|
||||||
# Available options: "Debug", "Release", "RelWithDebInfo"
|
# Available options: "Debug", "Release", "RelWithDebInfo"
|
||||||
@@ -191,36 +191,36 @@ environment_variables: dict[str, Callable[[], Any]] = {
|
|||||||
# Note that this not only affects how sgl_diffusion finds its configuration files
|
# Note that this not only affects how sgl_diffusion finds its configuration files
|
||||||
# during runtime, but also affects how sgl_diffusion installs its configuration
|
# during runtime, but also affects how sgl_diffusion installs its configuration
|
||||||
# files during **installation**.
|
# files during **installation**.
|
||||||
"SGL_DIFFUSION_CONFIG_ROOT": lambda: os.path.expanduser(
|
"SGLANG_DIFFUSION_CONFIG_ROOT": lambda: os.path.expanduser(
|
||||||
os.getenv(
|
os.getenv(
|
||||||
"SGL_DIFFUSION_CONFIG_ROOT",
|
"SGLANG_DIFFUSION_CONFIG_ROOT",
|
||||||
os.path.join(get_default_config_root(), "sgl_diffusion"),
|
os.path.join(get_default_config_root(), "sgl_diffusion"),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
# ================== Runtime Env Vars ==================
|
# ================== Runtime Env Vars ==================
|
||||||
# Root directory for FASTVIDEO cache files
|
# Root directory for FASTVIDEO cache files
|
||||||
# Defaults to `~/.cache/sgl_diffusion` unless `XDG_CACHE_HOME` is set
|
# Defaults to `~/.cache/sgl_diffusion` unless `XDG_CACHE_HOME` is set
|
||||||
"SGL_DIFFUSION_CACHE_ROOT": lambda: os.path.expanduser(
|
"SGLANG_DIFFUSION_CACHE_ROOT": lambda: os.path.expanduser(
|
||||||
os.getenv(
|
os.getenv(
|
||||||
"SGL_DIFFUSION_CACHE_ROOT",
|
"SGLANG_DIFFUSION_CACHE_ROOT",
|
||||||
os.path.join(get_default_cache_root(), "sgl_diffusion"),
|
os.path.join(get_default_cache_root(), "sgl_diffusion"),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
# Interval in seconds to log a warning message when the ring buffer is full
|
# Interval in seconds to log a warning message when the ring buffer is full
|
||||||
"SGL_DIFFUSION_RINGBUFFER_WARNING_INTERVAL": lambda: int(
|
"SGLANG_DIFFUSION_RINGBUFFER_WARNING_INTERVAL": lambda: int(
|
||||||
os.environ.get("SGL_DIFFUSION_RINGBUFFER_WARNING_INTERVAL", "60")
|
os.environ.get("SGLANG_DIFFUSION_RINGBUFFER_WARNING_INTERVAL", "60")
|
||||||
),
|
),
|
||||||
# Path to the NCCL library file. It is needed because nccl>=2.19 brought
|
# Path to the NCCL library file. It is needed because nccl>=2.19 brought
|
||||||
# by PyTorch contains a bug: https://github.com/NVIDIA/nccl/issues/1234
|
# by PyTorch contains a bug: https://github.com/NVIDIA/nccl/issues/1234
|
||||||
"SGL_DIFFUSION_NCCL_SO_PATH": lambda: os.environ.get(
|
"SGLANG_DIFFUSION_NCCL_SO_PATH": lambda: os.environ.get(
|
||||||
"SGL_DIFFUSION_NCCL_SO_PATH", None
|
"SGLANG_DIFFUSION_NCCL_SO_PATH", None
|
||||||
),
|
),
|
||||||
# when `SGL_DIFFUSION_NCCL_SO_PATH` is not set, sgl_diffusion will try to find the nccl
|
# when `SGLANG_DIFFUSION_NCCL_SO_PATH` is not set, sgl_diffusion will try to find the nccl
|
||||||
# library file in the locations specified by `LD_LIBRARY_PATH`
|
# library file in the locations specified by `LD_LIBRARY_PATH`
|
||||||
"LD_LIBRARY_PATH": lambda: os.environ.get("LD_LIBRARY_PATH", None),
|
"LD_LIBRARY_PATH": lambda: os.environ.get("LD_LIBRARY_PATH", None),
|
||||||
# Internal flag to enable Dynamo fullgraph capture
|
# Internal flag to enable Dynamo fullgraph capture
|
||||||
"SGL_DIFFUSION_TEST_DYNAMO_FULLGRAPH_CAPTURE": lambda: bool(
|
"SGLANG_DIFFUSION_TEST_DYNAMO_FULLGRAPH_CAPTURE": lambda: bool(
|
||||||
os.environ.get("SGL_DIFFUSION_TEST_DYNAMO_FULLGRAPH_CAPTURE", "1") != "0"
|
os.environ.get("SGLANG_DIFFUSION_TEST_DYNAMO_FULLGRAPH_CAPTURE", "1") != "0"
|
||||||
),
|
),
|
||||||
# local rank of the process in the distributed setting, used to determine
|
# local rank of the process in the distributed setting, used to determine
|
||||||
# the GPU device id
|
# the GPU device id
|
||||||
@@ -228,62 +228,62 @@ environment_variables: dict[str, Callable[[], Any]] = {
|
|||||||
# used to control the visible devices in the distributed setting
|
# used to control the visible devices in the distributed setting
|
||||||
"CUDA_VISIBLE_DEVICES": lambda: os.environ.get("CUDA_VISIBLE_DEVICES", None),
|
"CUDA_VISIBLE_DEVICES": lambda: os.environ.get("CUDA_VISIBLE_DEVICES", None),
|
||||||
# timeout for each iteration in the engine
|
# timeout for each iteration in the engine
|
||||||
"SGL_DIFFUSION_ENGINE_ITERATION_TIMEOUT_S": lambda: int(
|
"SGLANG_DIFFUSION_ENGINE_ITERATION_TIMEOUT_S": lambda: int(
|
||||||
os.environ.get("SGL_DIFFUSION_ENGINE_ITERATION_TIMEOUT_S", "60")
|
os.environ.get("SGLANG_DIFFUSION_ENGINE_ITERATION_TIMEOUT_S", "60")
|
||||||
),
|
),
|
||||||
# Logging configuration
|
# Logging configuration
|
||||||
# If set to 0, sgl_diffusion will not configure logging
|
# If set to 0, sgl_diffusion will not configure logging
|
||||||
# If set to 1, sgl_diffusion will configure logging using the default configuration
|
# If set to 1, sgl_diffusion will configure logging using the default configuration
|
||||||
# or the configuration file specified by SGL_DIFFUSION_LOGGING_CONFIG_PATH
|
# or the configuration file specified by SGLANG_DIFFUSION_LOGGING_CONFIG_PATH
|
||||||
"SGL_DIFFUSION_CONFIGURE_LOGGING": lambda: int(
|
"SGLANG_DIFFUSION_CONFIGURE_LOGGING": lambda: int(
|
||||||
os.getenv("SGL_DIFFUSION_CONFIGURE_LOGGING", "1")
|
os.getenv("SGLANG_DIFFUSION_CONFIGURE_LOGGING", "1")
|
||||||
),
|
),
|
||||||
"SGL_DIFFUSION_LOGGING_CONFIG_PATH": lambda: os.getenv(
|
"SGLANG_DIFFUSION_LOGGING_CONFIG_PATH": lambda: os.getenv(
|
||||||
"SGL_DIFFUSION_LOGGING_CONFIG_PATH"
|
"SGLANG_DIFFUSION_LOGGING_CONFIG_PATH"
|
||||||
),
|
),
|
||||||
# this is used for configuring the default logging level
|
# this is used for configuring the default logging level
|
||||||
"SGL_DIFFUSION_LOGGING_LEVEL": lambda: os.getenv(
|
"SGLANG_DIFFUSION_LOGGING_LEVEL": lambda: os.getenv(
|
||||||
"SGL_DIFFUSION_LOGGING_LEVEL", "INFO"
|
"SGLANG_DIFFUSION_LOGGING_LEVEL", "INFO"
|
||||||
),
|
),
|
||||||
# if set, SGL_DIFFUSION_LOGGING_PREFIX will be prepended to all log messages
|
# if set, SGLANG_DIFFUSION_LOGGING_PREFIX will be prepended to all log messages
|
||||||
"SGL_DIFFUSION_LOGGING_PREFIX": lambda: os.getenv(
|
"SGLANG_DIFFUSION_LOGGING_PREFIX": lambda: os.getenv(
|
||||||
"SGL_DIFFUSION_LOGGING_PREFIX", ""
|
"SGLANG_DIFFUSION_LOGGING_PREFIX", ""
|
||||||
),
|
),
|
||||||
# Trace function calls
|
# Trace function calls
|
||||||
# If set to 1, sgl_diffusion will trace function calls
|
# If set to 1, sgl_diffusion will trace function calls
|
||||||
# Useful for debugging
|
# Useful for debugging
|
||||||
"SGL_DIFFUSION_TRACE_FUNCTION": lambda: int(
|
"SGLANG_DIFFUSION_TRACE_FUNCTION": lambda: int(
|
||||||
os.getenv("SGL_DIFFUSION_TRACE_FUNCTION", "0")
|
os.getenv("SGLANG_DIFFUSION_TRACE_FUNCTION", "0")
|
||||||
),
|
),
|
||||||
# Path to the attention configuration file. Only used for sliding tile
|
# Path to the attention configuration file. Only used for sliding tile
|
||||||
# attention for now.
|
# attention for now.
|
||||||
"SGL_DIFFUSION_ATTENTION_CONFIG": lambda: (
|
"SGLANG_DIFFUSION_ATTENTION_CONFIG": lambda: (
|
||||||
None
|
None
|
||||||
if os.getenv("SGL_DIFFUSION_ATTENTION_CONFIG", None) is None
|
if os.getenv("SGLANG_DIFFUSION_ATTENTION_CONFIG", None) is None
|
||||||
else os.path.expanduser(os.getenv("SGL_DIFFUSION_ATTENTION_CONFIG", "."))
|
else os.path.expanduser(os.getenv("SGLANG_DIFFUSION_ATTENTION_CONFIG", "."))
|
||||||
),
|
),
|
||||||
# Use dedicated multiprocess context for workers.
|
# Use dedicated multiprocess context for workers.
|
||||||
# Both spawn and fork work
|
# Both spawn and fork work
|
||||||
"SGL_DIFFUSION_WORKER_MULTIPROC_METHOD": lambda: os.getenv(
|
"SGLANG_DIFFUSION_WORKER_MULTIPROC_METHOD": lambda: os.getenv(
|
||||||
"SGL_DIFFUSION_WORKER_MULTIPROC_METHOD", "fork"
|
"SGLANG_DIFFUSION_WORKER_MULTIPROC_METHOD", "fork"
|
||||||
),
|
),
|
||||||
# Enables torch profiler if set. Path to the directory where torch profiler
|
# Enables torch profiler if set. Path to the directory where torch profiler
|
||||||
# traces are saved. Note that it must be an absolute path.
|
# traces are saved. Note that it must be an absolute path.
|
||||||
"SGL_DIFFUSION_TORCH_PROFILER_DIR": lambda: (
|
"SGLANG_DIFFUSION_TORCH_PROFILER_DIR": lambda: (
|
||||||
None
|
None
|
||||||
if os.getenv("SGL_DIFFUSION_TORCH_PROFILER_DIR", None) is None
|
if os.getenv("SGLANG_DIFFUSION_TORCH_PROFILER_DIR", None) is None
|
||||||
else os.path.expanduser(os.getenv("SGL_DIFFUSION_TORCH_PROFILER_DIR", "."))
|
else os.path.expanduser(os.getenv("SGLANG_DIFFUSION_TORCH_PROFILER_DIR", "."))
|
||||||
),
|
),
|
||||||
# If set, sgl_diffusion will run in development mode, which will enable
|
# If set, sgl_diffusion will run in development mode, which will enable
|
||||||
# some additional endpoints for developing and debugging,
|
# some additional endpoints for developing and debugging,
|
||||||
# e.g. `/reset_prefix_cache`
|
# e.g. `/reset_prefix_cache`
|
||||||
"SGL_DIFFUSION_SERVER_DEV_MODE": lambda: bool(
|
"SGLANG_DIFFUSION_SERVER_DEV_MODE": lambda: bool(
|
||||||
int(os.getenv("SGL_DIFFUSION_SERVER_DEV_MODE", "0"))
|
int(os.getenv("SGLANG_DIFFUSION_SERVER_DEV_MODE", "0"))
|
||||||
),
|
),
|
||||||
# If set, sgl_diffusion will enable stage logging, which will print the time
|
# If set, sgl_diffusion will enable stage logging, which will print the time
|
||||||
# taken for each stage
|
# taken for each stage
|
||||||
"SGL_DIFFUSION_STAGE_LOGGING": lambda: bool(
|
"SGLANG_DIFFUSION_STAGE_LOGGING": lambda: bool(
|
||||||
int(os.getenv("SGL_DIFFUSION_STAGE_LOGGING", "0"))
|
int(os.getenv("SGLANG_DIFFUSION_STAGE_LOGGING", "0"))
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -21,10 +21,10 @@
|
|||||||
# recompilation of the code every time we want to switch between different
|
# recompilation of the code every time we want to switch between different
|
||||||
# versions. This current implementation, with a **pure** Python wrapper, is
|
# versions. This current implementation, with a **pure** Python wrapper, is
|
||||||
# more flexible. We can easily switch between different versions of NCCL by
|
# more flexible. We can easily switch between different versions of NCCL by
|
||||||
# changing the environment variable `SGL_DIFFUSION_NCCL_SO_PATH`, or the `so_file`
|
# changing the environment variable `SGLANG_DIFFUSION_NCCL_SO_PATH`, or the `so_file`
|
||||||
# variable in the code.
|
# variable in the code.
|
||||||
|
|
||||||
# TODO(will): support SGL_DIFFUSION_NCCL_SO_PATH
|
# TODO(will): support SGLANG_DIFFUSION_NCCL_SO_PATH
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import platform
|
import platform
|
||||||
@@ -281,7 +281,7 @@ class NCCLLibrary:
|
|||||||
"Otherwise, the nccl library might not exist, be corrupted "
|
"Otherwise, the nccl library might not exist, be corrupted "
|
||||||
"or it does not support the current platform %s."
|
"or it does not support the current platform %s."
|
||||||
"If you already have the library, please set the "
|
"If you already have the library, please set the "
|
||||||
"environment variable SGL_DIFFUSION_NCCL_SO_PATH"
|
"environment variable SGLANG_DIFFUSION_NCCL_SO_PATH"
|
||||||
" to point to the correct nccl library path.",
|
" to point to the correct nccl library path.",
|
||||||
so_file,
|
so_file,
|
||||||
platform.platform(),
|
platform.platform(),
|
||||||
|
|||||||
@@ -119,9 +119,9 @@ class SlidingTileAttentionImpl(AttentionImpl):
|
|||||||
raise ValueError("st attn not supported")
|
raise ValueError("st attn not supported")
|
||||||
# TODO(will-refactor): for now this is the mask strategy, but maybe we should
|
# TODO(will-refactor): for now this is the mask strategy, but maybe we should
|
||||||
# have a more general config for STA?
|
# have a more general config for STA?
|
||||||
config_file = envs.SGL_DIFFUSION_ATTENTION_CONFIG
|
config_file = envs.SGLANG_DIFFUSION_ATTENTION_CONFIG
|
||||||
if config_file is None:
|
if config_file is None:
|
||||||
raise ValueError("SGL_DIFFUSION_ATTENTION_CONFIG is not set")
|
raise ValueError("SGLANG_DIFFUSION_ATTENTION_CONFIG is not set")
|
||||||
|
|
||||||
# TODO(kevin): get mask strategy for different STA modes
|
# TODO(kevin): get mask strategy for different STA modes
|
||||||
with open(config_file) as f:
|
with open(config_file) as f:
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ def _cached_get_attn_backend(
|
|||||||
# Check whether a particular choice of backend was
|
# Check whether a particular choice of backend was
|
||||||
# previously forced.
|
# previously forced.
|
||||||
#
|
#
|
||||||
# THIS SELECTION OVERRIDES THE SGL_DIFFUSION_ATTENTION_BACKEND
|
# THIS SELECTION OVERRIDES THE SGLANG_DIFFUSION_ATTENTION_BACKEND
|
||||||
# ENVIRONMENT VARIABLE.
|
# ENVIRONMENT VARIABLE.
|
||||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ if TYPE_CHECKING:
|
|||||||
logger = init_logger(__name__)
|
logger = init_logger(__name__)
|
||||||
|
|
||||||
# TODO(will): check if this is needed
|
# TODO(will): check if this is needed
|
||||||
# track_batchsize: bool = envs.SGL_DIFFUSION_LOG_BATCHSIZE_INTERVAL >= 0
|
# track_batchsize: bool = envs.SGLANG_DIFFUSION_LOG_BATCHSIZE_INTERVAL >= 0
|
||||||
track_batchsize: bool = False
|
track_batchsize: bool = False
|
||||||
last_logging_time: float = 0
|
last_logging_time: float = 0
|
||||||
forward_start_time: float = 0
|
forward_start_time: float = 0
|
||||||
# batchsize_logging_interval: float = envs.SGL_DIFFUSION_LOG_BATCHSIZE_INTERVAL
|
# batchsize_logging_interval: float = envs.SGLANG_DIFFUSION_LOG_BATCHSIZE_INTERVAL
|
||||||
batchsize_logging_interval: float = 1000
|
batchsize_logging_interval: float = 1000
|
||||||
batchsize_forward_time: defaultdict = defaultdict(list)
|
batchsize_forward_time: defaultdict = defaultdict(list)
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ def _discover_and_register_models() -> dict[str, tuple[str, str, str]]:
|
|||||||
return discovered_models
|
return discovered_models
|
||||||
|
|
||||||
|
|
||||||
_SGL_DIFFUSION_MODELS = _discover_and_register_models()
|
_SGLANG_DIFFUSION_MODELS = _discover_and_register_models()
|
||||||
|
|
||||||
_SUBPROCESS_COMMAND = [
|
_SUBPROCESS_COMMAND = [
|
||||||
sys.executable,
|
sys.executable,
|
||||||
@@ -361,6 +361,6 @@ ModelRegistry = _ModelRegistry(
|
|||||||
component_name,
|
component_name,
|
||||||
mod_relname,
|
mod_relname,
|
||||||
cls_name,
|
cls_name,
|
||||||
) in _SGL_DIFFUSION_MODELS.items()
|
) in _SGLANG_DIFFUSION_MODELS.items()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ class PipelineStage(ABC):
|
|||||||
# Execute the actual stage logic
|
# Execute the actual stage logic
|
||||||
logging_info = getattr(batch, "logging_info", None)
|
logging_info = getattr(batch, "logging_info", None)
|
||||||
|
|
||||||
if envs.SGL_DIFFUSION_STAGE_LOGGING:
|
if envs.SGLANG_DIFFUSION_STAGE_LOGGING:
|
||||||
logger.info("[%s] Starting execution", stage_name)
|
logger.info("[%s] Starting execution", stage_name)
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
|
|
||||||
|
|||||||
@@ -1284,9 +1284,9 @@ class DenoisingStage(PipelineStage):
|
|||||||
elif STA_mode == STA_Mode.STA_INFERENCE:
|
elif STA_mode == STA_Mode.STA_INFERENCE:
|
||||||
import sglang.multimodal_gen.envs as envs
|
import sglang.multimodal_gen.envs as envs
|
||||||
|
|
||||||
config_file = envs.SGL_DIFFUSION_ATTENTION_CONFIG
|
config_file = envs.SGLANG_DIFFUSION_ATTENTION_CONFIG
|
||||||
if config_file is None:
|
if config_file is None:
|
||||||
raise ValueError("SGL_DIFFUSION_ATTENTION_CONFIG is not set")
|
raise ValueError("SGLANG_DIFFUSION_ATTENTION_CONFIG is not set")
|
||||||
STA_param = configure_sta(
|
STA_param = configure_sta(
|
||||||
mode=STA_Mode.STA_INFERENCE,
|
mode=STA_Mode.STA_INFERENCE,
|
||||||
layer_num=layer_num,
|
layer_num=layer_num,
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ class RocmPlatform(Platform):
|
|||||||
dtype: torch.dtype,
|
dtype: torch.dtype,
|
||||||
) -> str:
|
) -> str:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Trying SGL_DIFFUSION_ATTENTION_BACKEND=%s",
|
"Trying SGLANG_DIFFUSION_ATTENTION_BACKEND=%s",
|
||||||
envs.SGL_DIFFUSION_ATTENTION_BACKEND,
|
envs.SGLANG_DIFFUSION_ATTENTION_BACKEND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if selected_backend == AttentionBackendEnum.TORCH_SDPA:
|
if selected_backend == AttentionBackendEnum.TORCH_SDPA:
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ from typing import Any, cast
|
|||||||
|
|
||||||
import sglang.multimodal_gen.envs as envs
|
import sglang.multimodal_gen.envs as envs
|
||||||
|
|
||||||
SGL_DIFFUSION_CONFIGURE_LOGGING = envs.SGL_DIFFUSION_CONFIGURE_LOGGING
|
SGLANG_DIFFUSION_CONFIGURE_LOGGING = envs.SGLANG_DIFFUSION_CONFIGURE_LOGGING
|
||||||
SGL_DIFFUSION_LOGGING_CONFIG_PATH = envs.SGL_DIFFUSION_LOGGING_CONFIG_PATH
|
SGLANG_DIFFUSION_LOGGING_CONFIG_PATH = envs.SGLANG_DIFFUSION_LOGGING_CONFIG_PATH
|
||||||
SGL_DIFFUSION_LOGGING_LEVEL = envs.SGL_DIFFUSION_LOGGING_LEVEL
|
SGLANG_DIFFUSION_LOGGING_LEVEL = envs.SGLANG_DIFFUSION_LOGGING_LEVEL
|
||||||
SGL_DIFFUSION_LOGGING_PREFIX = envs.SGL_DIFFUSION_LOGGING_PREFIX
|
SGLANG_DIFFUSION_LOGGING_PREFIX = envs.SGLANG_DIFFUSION_LOGGING_PREFIX
|
||||||
|
|
||||||
RED = "\033[91m"
|
RED = "\033[91m"
|
||||||
GREEN = "\033[92m"
|
GREEN = "\033[92m"
|
||||||
@@ -28,7 +28,7 @@ YELLOW = "\033[93m"
|
|||||||
RESET = "\033[0;0m"
|
RESET = "\033[0;0m"
|
||||||
|
|
||||||
_FORMAT = (
|
_FORMAT = (
|
||||||
f"{SGL_DIFFUSION_LOGGING_PREFIX}%(levelname)s %(asctime)s "
|
f"{SGLANG_DIFFUSION_LOGGING_PREFIX}%(levelname)s %(asctime)s "
|
||||||
"[%(filename)s: %(lineno)d] %(message)s"
|
"[%(filename)s: %(lineno)d] %(message)s"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ DEFAULT_LOGGING_CONFIG = {
|
|||||||
"sgl_diffusion": {
|
"sgl_diffusion": {
|
||||||
"class": "logging.StreamHandler",
|
"class": "logging.StreamHandler",
|
||||||
"formatter": "sgl_diffusion",
|
"formatter": "sgl_diffusion",
|
||||||
"level": SGL_DIFFUSION_LOGGING_LEVEL,
|
"level": SGLANG_DIFFUSION_LOGGING_LEVEL,
|
||||||
"stream": "ext://sys.stdout",
|
"stream": "ext://sys.stdout",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -340,7 +340,7 @@ def enable_trace_function_call(log_file_path: str, root_dir: str | None = None):
|
|||||||
will have the trace enabled. Other threads will not be affected.
|
will have the trace enabled. Other threads will not be affected.
|
||||||
"""
|
"""
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"SGL_DIFFUSION_TRACE_FUNCTION is enabled. It will record every"
|
"SGLANG_DIFFUSION_TRACE_FUNCTION is enabled. It will record every"
|
||||||
" function executed by Python. This will slow down the code. It "
|
" function executed by Python. This will slow down the code. It "
|
||||||
"is suggested to be used for debugging hang or crashes only."
|
"is suggested to be used for debugging hang or crashes only."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from dateutil.tz import UTC
|
from dateutil.tz import UTC
|
||||||
|
|
||||||
|
import sglang
|
||||||
|
|
||||||
|
|
||||||
def get_diffusion_perf_log_dir() -> str:
|
def get_diffusion_perf_log_dir() -> str:
|
||||||
"""
|
"""
|
||||||
@@ -26,7 +29,10 @@ def get_diffusion_perf_log_dir() -> str:
|
|||||||
return os.path.abspath(log_dir)
|
return os.path.abspath(log_dir)
|
||||||
if log_dir is None:
|
if log_dir is None:
|
||||||
# Not set, use default
|
# Not set, use default
|
||||||
return os.path.join(os.path.expanduser("~/.cache/sglang"), "logs")
|
sglang_path = Path(sglang.__file__).resolve()
|
||||||
|
# .gitignore
|
||||||
|
target_path = (sglang_path.parent / "../../.cache/logs").resolve()
|
||||||
|
return str(target_path)
|
||||||
# Is set, but is an empty string
|
# Is set, but is an empty string
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|||||||
@@ -1,155 +1,351 @@
|
|||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"model": "Diffusion Server",
|
"model": "Diffusion Server",
|
||||||
"hardware": "CI H100 80GB pool",
|
"hardware": "CI H100 80GB pool",
|
||||||
"description": "Reference numbers captured from the CI diffusion server baseline run"
|
"description": "Reference numbers captured from the CI diffusion server baseline run"
|
||||||
},
|
|
||||||
"tolerances": {
|
|
||||||
"e2e": 0.25,
|
|
||||||
"stage": 0.3,
|
|
||||||
"denoise_step": 0.2,
|
|
||||||
"denoise_agg": 0.1
|
|
||||||
},
|
|
||||||
"sampling": {
|
|
||||||
"step_fractions": [
|
|
||||||
0.0,
|
|
||||||
0.2,
|
|
||||||
0.4,
|
|
||||||
0.6,
|
|
||||||
0.8,
|
|
||||||
1.0
|
|
||||||
],
|
|
||||||
"warmup_requests": {
|
|
||||||
"text": 1,
|
|
||||||
"image_edit": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scenarios": {
|
|
||||||
"text_to_image": {
|
|
||||||
"notes": "Single-image generation using the default prompt",
|
|
||||||
"expected_e2e_ms": 74500.0,
|
|
||||||
"expected_avg_denoise_ms": 422.42,
|
|
||||||
"expected_median_denoise_ms": 410.62,
|
|
||||||
"stages_ms": {
|
|
||||||
"InputValidationStage": 0.1,
|
|
||||||
"TextEncodingStage": 834.2,
|
|
||||||
"ConditioningStage": 0.1,
|
|
||||||
"TimestepPreparationStage": 10.6,
|
|
||||||
"LatentPreparationStage": 9.0,
|
|
||||||
"DenoisingStage": 21202.6,
|
|
||||||
"DecodingStage": 476.12
|
|
||||||
},
|
|
||||||
"denoise_step_ms": {
|
|
||||||
"0": 1077.77, "1": 345.13, "2": 413.8, "3": 405.49, "4": 408.14, "5": 409.06,
|
|
||||||
"6": 408.85, "7": 410.53, "8": 407.51, "9": 409.44, "10": 408.65, "11": 410.14,
|
|
||||||
"12": 411.74, "13": 409.59, "14": 409.17, "15": 410.78, "16": 410.66, "17": 410.58,
|
|
||||||
"18": 411.27, "19": 410.51, "20": 409.03, "21": 410.16, "22": 409.42, "23": 411.03,
|
|
||||||
"24": 410.18, "25": 409.72, "26": 410.26, "27": 410.21, "28": 410.71, "29": 410.76,
|
|
||||||
"30": 411.06, "31": 410.1, "32": 410.55, "33": 410.77, "34": 410.74, "35": 411.75,
|
|
||||||
"36": 410.78, "37": 411.56, "38": 410.85, "39": 411.08, "40": 411.12, "41": 411.1,
|
|
||||||
"42": 411.09, "43": 410.87, "44": 411.37, "45": 411.68, "46": 411.0, "47": 410.09,
|
|
||||||
"48": 412.72, "49": 410.42
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"image_edit": {
|
"tolerances": {
|
||||||
"notes": "single uploaded reference image, Qwen/Qwen-Image-Edit",
|
"e2e": 0.05,
|
||||||
"expected_e2e_ms": 138500.0,
|
"stage": 0.05,
|
||||||
"expected_avg_denoise_ms": 720.0,
|
"denoise_step": 0.15,
|
||||||
"expected_median_denoise_ms": 718.0,
|
"denoise_agg": 0.08
|
||||||
"stages_ms": {
|
|
||||||
"InputValidationStage": 23,
|
|
||||||
"ImageEncodingStage": 1350.0,
|
|
||||||
"ImageVAEEncodingStage": 340.0,
|
|
||||||
"ConditioningStage": 0.13,
|
|
||||||
"TimestepPreparationStage": 13.78,
|
|
||||||
"LatentPreparationStage": 10.0,
|
|
||||||
"DenoisingStage": 36000.0,
|
|
||||||
"DecodingStage": 850.0
|
|
||||||
},
|
|
||||||
"denoise_step_ms": {
|
|
||||||
"0": 720.0, "1": 720.0, "2": 720.0, "3": 720.0, "4": 720.0, "5": 720.0,
|
|
||||||
"6": 720.0, "7": 720.0, "8": 720.0, "9": 720.0, "10": 720.0, "11": 720.0,
|
|
||||||
"12": 720.0, "13": 720.0, "14": 720.0, "15": 720.0, "16": 720.0, "17": 720.0,
|
|
||||||
"18": 720.0, "19": 720.0, "20": 720.0, "21": 720.0, "22": 720.0, "23": 720.0,
|
|
||||||
"24": 720.0, "25": 720.0, "26": 720.0, "27": 720.0, "28": 720.0, "29": 720.0,
|
|
||||||
"30": 720.0, "31": 720.0, "32": 720.0, "33": 720.0, "34": 720.0, "35": 720.0,
|
|
||||||
"36": 720.0, "37": 720.0, "38": 720.0, "39": 720.0, "40": 720.0, "41": 720.0,
|
|
||||||
"42": 720.0, "43": 720.0, "44": 720.0, "45": 720.0, "46": 720.0, "47": 720.0,
|
|
||||||
"48": 720.0, "49": 720.0
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"text_to_video": {
|
"improvement_reporting": {
|
||||||
"notes": "Single-video generation using the default prompt",
|
"threshold": 0.2
|
||||||
"expected_e2e_ms": 95616.59,
|
|
||||||
"expected_avg_denoise_ms": 1798.77,
|
|
||||||
"expected_median_denoise_ms": 1786.78,
|
|
||||||
|
|
||||||
"stages_ms": {
|
|
||||||
"InputValidationStage": 1.03,
|
|
||||||
"TextEncodingStage": 3450.0,
|
|
||||||
"ConditioningStage": 1.0,
|
|
||||||
"TimestepPreparationStage": 6.0,
|
|
||||||
"LatentPreparationStage": 15.0,
|
|
||||||
"DenoisingStage": 90100.0,
|
|
||||||
"DecodingStage": 3650.0
|
|
||||||
},
|
|
||||||
|
|
||||||
"denoise_step_ms": {
|
|
||||||
"0": 3500.0, "10": 1800.0, "20": 1800.0, "29": 1800.0, "39": 1800.0, "49": 1800.0
|
|
||||||
},
|
|
||||||
"frames_per_second": 0.51,
|
|
||||||
"total_frames": 49,
|
|
||||||
"avg_frame_time_ms": 1951.36
|
|
||||||
},
|
},
|
||||||
"image_to_video": {
|
"sampling": {
|
||||||
"notes": "Wan-AI/Wan2.2-I2V-A14B",
|
"step_fractions": [
|
||||||
"expected_e2e_ms": 282500.0,
|
0.0,
|
||||||
"expected_avg_denoise_ms": 7000.0,
|
0.2,
|
||||||
"expected_median_denoise_ms": 7000.19,
|
0.4,
|
||||||
"stages_ms": {
|
0.6,
|
||||||
"InputValidationStage": 20.0,
|
0.8,
|
||||||
"TextEncodingStage": 2100.0,
|
1.0
|
||||||
"ConditioningStage": 2.0,
|
],
|
||||||
"TimestepPreparationStage": 2.0,
|
"warmup_requests": {
|
||||||
"LatentPreparationStage": 10.0,
|
"text": 1,
|
||||||
"ImageVAEEncodingStage": 1800.0,
|
"image_edit": 0
|
||||||
"DenoisingStage": 278000.0,
|
|
||||||
"DecodingStage": 2700.0
|
|
||||||
},
|
|
||||||
"denoise_step_ms": {
|
|
||||||
"0": 24000.0,
|
|
||||||
"8": 7000.0,
|
|
||||||
"16": 7000.0,
|
|
||||||
"23": 7000.0,
|
|
||||||
"31": 7000.0,
|
|
||||||
"39": 7000.0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"text_image_to_video": {
|
"scenarios": {
|
||||||
"notes": "Text-and-Image-to-Video generation baseline for Wan2.2-TI2V-5B",
|
"qwen_image_t2i": {
|
||||||
"expected_e2e_ms": 178300.0,
|
"notes": "Single-image generation using the default prompt",
|
||||||
"expected_avg_denoise_ms": 3250.0,
|
"expected_e2e_ms": 74500.0,
|
||||||
"expected_median_denoise_ms": 3260.0,
|
"expected_avg_denoise_ms": 422.42,
|
||||||
"stages_ms": {
|
"expected_median_denoise_ms": 410.62,
|
||||||
"InputValidationStage": 80.0,
|
"stages_ms": {
|
||||||
"TextEncodingStage": 3000.0,
|
"InputValidationStage": 0.1,
|
||||||
"ConditioningStage": 1.0,
|
"TextEncodingStage": 834.2,
|
||||||
"TimestepPreparationStage": 6.0,
|
"ConditioningStage": 0.1,
|
||||||
"LatentPreparationStage": 30.0,
|
"TimestepPreparationStage": 10.6,
|
||||||
"DenoisingStage": 162900.0,
|
"LatentPreparationStage": 11.8,
|
||||||
"DecodingStage": 13500.0
|
"DenoisingStage": 21202.6,
|
||||||
},
|
"DecodingStage": 476.12
|
||||||
"denoise_step_ms": {
|
},
|
||||||
"0": 3700.0,
|
"denoise_step_ms": {
|
||||||
"10": 3300.0,
|
"0": 1077.77,
|
||||||
"20": 3300.0,
|
"1": 345.13,
|
||||||
"29": 3300.0,
|
"2": 413.8,
|
||||||
"39": 3300.0,
|
"3": 405.49,
|
||||||
"49": 3300.0
|
"4": 408.14,
|
||||||
},
|
"5": 409.06,
|
||||||
"frames_per_second": null,
|
"6": 408.85,
|
||||||
"total_frames": null,
|
"7": 410.53,
|
||||||
"avg_frame_time_ms": null
|
"8": 407.51,
|
||||||
|
"9": 409.44,
|
||||||
|
"10": 408.65,
|
||||||
|
"11": 410.14,
|
||||||
|
"12": 411.74,
|
||||||
|
"13": 409.59,
|
||||||
|
"14": 409.17,
|
||||||
|
"15": 410.78,
|
||||||
|
"16": 410.66,
|
||||||
|
"17": 410.58,
|
||||||
|
"18": 411.27,
|
||||||
|
"19": 410.51,
|
||||||
|
"20": 409.03,
|
||||||
|
"21": 410.16,
|
||||||
|
"22": 409.42,
|
||||||
|
"23": 411.03,
|
||||||
|
"24": 410.18,
|
||||||
|
"25": 409.72,
|
||||||
|
"26": 410.26,
|
||||||
|
"27": 410.21,
|
||||||
|
"28": 410.71,
|
||||||
|
"29": 410.76,
|
||||||
|
"30": 411.06,
|
||||||
|
"31": 410.1,
|
||||||
|
"32": 410.55,
|
||||||
|
"33": 410.77,
|
||||||
|
"34": 410.74,
|
||||||
|
"35": 411.75,
|
||||||
|
"36": 410.78,
|
||||||
|
"37": 411.56,
|
||||||
|
"38": 410.85,
|
||||||
|
"39": 411.08,
|
||||||
|
"40": 411.12,
|
||||||
|
"41": 411.1,
|
||||||
|
"42": 411.09,
|
||||||
|
"43": 410.87,
|
||||||
|
"44": 411.37,
|
||||||
|
"45": 411.68,
|
||||||
|
"46": 411.0,
|
||||||
|
"47": 410.09,
|
||||||
|
"48": 412.72,
|
||||||
|
"49": 410.42
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flux_image_t2i": {
|
||||||
|
"stages_ms": {
|
||||||
|
"InputValidationStage": 0.08,
|
||||||
|
"TextEncodingStage": 87.36,
|
||||||
|
"ConditioningStage": 0.024,
|
||||||
|
"TimestepPreparationStage": 2.53,
|
||||||
|
"LatentPreparationStage": 6.21,
|
||||||
|
"DenoisingStage": 8161.97,
|
||||||
|
"DecodingStage": 378.21
|
||||||
|
},
|
||||||
|
"denoise_step_ms": {
|
||||||
|
"0": 56.16,
|
||||||
|
"10": 163.54,
|
||||||
|
"20": 165.94,
|
||||||
|
"29": 165.26,
|
||||||
|
"39": 165.16,
|
||||||
|
"49": 165.36
|
||||||
|
},
|
||||||
|
"expected_e2e_ms": 8764.73,
|
||||||
|
"expected_avg_denoise_ms": 160.79,
|
||||||
|
"expected_median_denoise_ms": 165.14
|
||||||
|
},
|
||||||
|
"qwen_image_edit_ti2i": {
|
||||||
|
"notes": "single uploaded reference image, Qwen/Qwen-Image-Edit",
|
||||||
|
"expected_e2e_ms": 138500.0,
|
||||||
|
"expected_avg_denoise_ms": 720.0,
|
||||||
|
"expected_median_denoise_ms": 718.0,
|
||||||
|
"stages_ms": {
|
||||||
|
"InputValidationStage": 23,
|
||||||
|
"ImageEncodingStage": 1350.0,
|
||||||
|
"ImageVAEEncodingStage": 340.0,
|
||||||
|
"ConditioningStage": 0.13,
|
||||||
|
"TimestepPreparationStage": 13.78,
|
||||||
|
"LatentPreparationStage": 15.0,
|
||||||
|
"DenoisingStage": 36000.0,
|
||||||
|
"DecodingStage": 850.0
|
||||||
|
},
|
||||||
|
"denoise_step_ms": {
|
||||||
|
"0": 720.0,
|
||||||
|
"1": 720.0,
|
||||||
|
"2": 720.0,
|
||||||
|
"3": 720.0,
|
||||||
|
"4": 720.0,
|
||||||
|
"5": 720.0,
|
||||||
|
"6": 720.0,
|
||||||
|
"7": 720.0,
|
||||||
|
"8": 720.0,
|
||||||
|
"9": 720.0,
|
||||||
|
"10": 720.0,
|
||||||
|
"11": 720.0,
|
||||||
|
"12": 720.0,
|
||||||
|
"13": 720.0,
|
||||||
|
"14": 720.0,
|
||||||
|
"15": 720.0,
|
||||||
|
"16": 720.0,
|
||||||
|
"17": 720.0,
|
||||||
|
"18": 720.0,
|
||||||
|
"19": 720.0,
|
||||||
|
"20": 720.0,
|
||||||
|
"21": 720.0,
|
||||||
|
"22": 720.0,
|
||||||
|
"23": 720.0,
|
||||||
|
"24": 720.0,
|
||||||
|
"25": 720.0,
|
||||||
|
"26": 720.0,
|
||||||
|
"27": 720.0,
|
||||||
|
"28": 720.0,
|
||||||
|
"29": 720.0,
|
||||||
|
"30": 720.0,
|
||||||
|
"31": 720.0,
|
||||||
|
"32": 720.0,
|
||||||
|
"33": 720.0,
|
||||||
|
"34": 720.0,
|
||||||
|
"35": 720.0,
|
||||||
|
"36": 720.0,
|
||||||
|
"37": 720.0,
|
||||||
|
"38": 720.0,
|
||||||
|
"39": 720.0,
|
||||||
|
"40": 720.0,
|
||||||
|
"41": 720.0,
|
||||||
|
"42": 720.0,
|
||||||
|
"43": 720.0,
|
||||||
|
"44": 720.0,
|
||||||
|
"45": 720.0,
|
||||||
|
"46": 720.0,
|
||||||
|
"47": 720.0,
|
||||||
|
"48": 720.0,
|
||||||
|
"49": 720.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fastwan2_1_t2v": {
|
||||||
|
"notes": "Single-video generation using the default prompt",
|
||||||
|
"expected_e2e_ms": 95616.59,
|
||||||
|
"expected_avg_denoise_ms": 1798.77,
|
||||||
|
"expected_median_denoise_ms": 1786.78,
|
||||||
|
"stages_ms": {
|
||||||
|
"InputValidationStage": 1.03,
|
||||||
|
"TextEncodingStage": 3450.0,
|
||||||
|
"ConditioningStage": 1.0,
|
||||||
|
"TimestepPreparationStage": 6.0,
|
||||||
|
"LatentPreparationStage": 15.0,
|
||||||
|
"DenoisingStage": 90100.0,
|
||||||
|
"DecodingStage": 3650.0
|
||||||
|
},
|
||||||
|
"denoise_step_ms": {
|
||||||
|
"0": 3500.0,
|
||||||
|
"10": 1800.0,
|
||||||
|
"20": 1800.0,
|
||||||
|
"29": 1800.0,
|
||||||
|
"39": 1800.0,
|
||||||
|
"49": 1800.0
|
||||||
|
},
|
||||||
|
"frames_per_second": 0.51,
|
||||||
|
"total_frames": 49,
|
||||||
|
"avg_frame_time_ms": 1951.36
|
||||||
|
},
|
||||||
|
"wan2_2_i2v_a14b": {
|
||||||
|
"notes": "Wan-AI/Wan2.2-I2V-A14B",
|
||||||
|
"expected_e2e_ms": 282500.0,
|
||||||
|
"expected_avg_denoise_ms": 7000.0,
|
||||||
|
"expected_median_denoise_ms": 7000.19,
|
||||||
|
"stages_ms": {
|
||||||
|
"InputValidationStage": 26.26,
|
||||||
|
"TextEncodingStage": 2749.6,
|
||||||
|
"ConditioningStage": 2.0,
|
||||||
|
"TimestepPreparationStage": 2.0,
|
||||||
|
"LatentPreparationStage": 10.0,
|
||||||
|
"ImageVAEEncodingStage": 2031.0,
|
||||||
|
"DenoisingStage": 278000.0,
|
||||||
|
"DecodingStage": 2849.6
|
||||||
|
},
|
||||||
|
"denoise_step_ms": {
|
||||||
|
"0": 24000.0,
|
||||||
|
"8": 7000.0,
|
||||||
|
"16": 7000.0,
|
||||||
|
"23": 7000.0,
|
||||||
|
"31": 7000.0,
|
||||||
|
"39": 7000.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"wan2_1_i2v_14b_480P": {
|
||||||
|
"stages_ms": {
|
||||||
|
"per_frame_generation": null
|
||||||
|
},
|
||||||
|
"denoise_step_ms": {
|
||||||
|
"0": 5432.37,
|
||||||
|
"1": 4861.66,
|
||||||
|
"2": 4890.23,
|
||||||
|
"3": 4919.9,
|
||||||
|
"4": 4929.49,
|
||||||
|
"5": 4927.35,
|
||||||
|
"6": 4927.6,
|
||||||
|
"7": 4932.02,
|
||||||
|
"8": 4897.83,
|
||||||
|
"9": 4897.16,
|
||||||
|
"10": 4899.59,
|
||||||
|
"11": 4901.51,
|
||||||
|
"12": 4896.38,
|
||||||
|
"13": 4914.3,
|
||||||
|
"14": 4901.91,
|
||||||
|
"15": 4898.58,
|
||||||
|
"16": 4903.18,
|
||||||
|
"17": 4899.54,
|
||||||
|
"18": 4910.5,
|
||||||
|
"19": 4898.18,
|
||||||
|
"20": 4901.56,
|
||||||
|
"21": 4903.12,
|
||||||
|
"22": 4896.82,
|
||||||
|
"23": 4901.01,
|
||||||
|
"24": 4897.42,
|
||||||
|
"25": 4911.94,
|
||||||
|
"26": 4901.29,
|
||||||
|
"27": 4894.79,
|
||||||
|
"28": 4899.57,
|
||||||
|
"29": 4900.63,
|
||||||
|
"30": 4904.78,
|
||||||
|
"31": 4899.79,
|
||||||
|
"32": 4898.01,
|
||||||
|
"33": 4910.81,
|
||||||
|
"34": 4901.16,
|
||||||
|
"35": 4898.48,
|
||||||
|
"36": 4894.97,
|
||||||
|
"37": 4905.9,
|
||||||
|
"38": 4901.52,
|
||||||
|
"39": 4898.56,
|
||||||
|
"40": 4900.19,
|
||||||
|
"41": 4896.5,
|
||||||
|
"42": 4899.89,
|
||||||
|
"43": 4898.45,
|
||||||
|
"44": 4897.27,
|
||||||
|
"45": 4895.47,
|
||||||
|
"46": 4889.26,
|
||||||
|
"47": 4887.44,
|
||||||
|
"48": 4894.07,
|
||||||
|
"49": 4888.59
|
||||||
|
},
|
||||||
|
"expected_e2e_ms": 254552.63,
|
||||||
|
"expected_avg_denoise_ms": 4912.17,
|
||||||
|
"expected_median_denoise_ms": 4899.69
|
||||||
|
},
|
||||||
|
"wan2_2_i2v_14b_720P": {
|
||||||
|
"stages_ms": {
|
||||||
|
"InputValidationStage": 47.04,
|
||||||
|
"TextEncodingStage": 2321.27,
|
||||||
|
"ImageEncodingStage": 3244.34,
|
||||||
|
"ConditioningStage": 0.0234,
|
||||||
|
"TimestepPreparationStage": 2.88,
|
||||||
|
"LatentPreparationStage": 5.24,
|
||||||
|
"ImageVAEEncodingStage": 1887.64,
|
||||||
|
"DenoisingStage": 245826.78,
|
||||||
|
"DecodingStage": 2882.45,
|
||||||
|
"per_frame_generation": null
|
||||||
|
},
|
||||||
|
"denoise_step_ms": {
|
||||||
|
"0": 5429.38,
|
||||||
|
"10": 4901.39,
|
||||||
|
"20": 4912.89,
|
||||||
|
"29": 4900.75,
|
||||||
|
"39": 4906.23,
|
||||||
|
"49": 4892.55
|
||||||
|
},
|
||||||
|
"expected_e2e_ms": 254850.94,
|
||||||
|
"expected_avg_denoise_ms": 4914.73,
|
||||||
|
"expected_median_denoise_ms": 4903.27
|
||||||
|
},
|
||||||
|
"wan2_2_ti2v_5b": {
|
||||||
|
"notes": "Text-and-Image-to-Video generation baseline for Wan2.2-TI2V-5B",
|
||||||
|
"expected_e2e_ms": 178300.0,
|
||||||
|
"expected_avg_denoise_ms": 3250.0,
|
||||||
|
"expected_median_denoise_ms": 3260.0,
|
||||||
|
"stages_ms": {
|
||||||
|
"InputValidationStage": 150.0,
|
||||||
|
"TextEncodingStage": 3000.0,
|
||||||
|
"ConditioningStage": 1.0,
|
||||||
|
"TimestepPreparationStage": 6.0,
|
||||||
|
"LatentPreparationStage": 30.0,
|
||||||
|
"DenoisingStage": 162900.0,
|
||||||
|
"DecodingStage": 14767.0
|
||||||
|
},
|
||||||
|
"denoise_step_ms": {
|
||||||
|
"0": 3700.0,
|
||||||
|
"10": 3300.0,
|
||||||
|
"20": 3300.0,
|
||||||
|
"29": 3300.0,
|
||||||
|
"39": 3300.0,
|
||||||
|
"49": 3300.0
|
||||||
|
},
|
||||||
|
"frames_per_second": null,
|
||||||
|
"total_frames": null,
|
||||||
|
"avg_frame_time_ms": null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
Config-driven diffusion performance test with pytest parametrization.
|
Config-driven diffusion performance test with pytest parametrization.
|
||||||
Adding a new model/scenario = adding one DiffusionCase entry in diffusion_config.py.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -15,20 +14,20 @@ from openai import OpenAI
|
|||||||
|
|
||||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||||
from sglang.multimodal_gen.test.server.conftest import _GLOBAL_PERF_RESULTS
|
from sglang.multimodal_gen.test.server.conftest import _GLOBAL_PERF_RESULTS
|
||||||
from sglang.multimodal_gen.test.server.diffusion_config import (
|
from sglang.multimodal_gen.test.server.test_server_utils import (
|
||||||
BASELINE_CONFIG,
|
|
||||||
DIFFUSION_CASES,
|
|
||||||
DiffusionCase,
|
|
||||||
)
|
|
||||||
from sglang.multimodal_gen.test.server.diffusion_server import (
|
|
||||||
VALIDATOR_REGISTRY,
|
VALIDATOR_REGISTRY,
|
||||||
PerformanceValidator,
|
PerformanceValidator,
|
||||||
ServerContext,
|
ServerContext,
|
||||||
ServerManager,
|
ServerManager,
|
||||||
VideoPerformanceValidator,
|
|
||||||
WarmupRunner,
|
WarmupRunner,
|
||||||
download_image_from_url,
|
download_image_from_url,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.test.server.testcase_configs import (
|
||||||
|
BASELINE_CONFIG,
|
||||||
|
DIFFUSION_CASES,
|
||||||
|
DiffusionTestCase,
|
||||||
|
PerformanceSummary,
|
||||||
|
)
|
||||||
from sglang.multimodal_gen.test.test_utils import (
|
from sglang.multimodal_gen.test.test_utils import (
|
||||||
get_dynamic_server_port,
|
get_dynamic_server_port,
|
||||||
read_perf_records,
|
read_perf_records,
|
||||||
@@ -42,13 +41,13 @@ logger = init_logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=DIFFUSION_CASES, ids=lambda c: c.id)
|
@pytest.fixture(params=DIFFUSION_CASES, ids=lambda c: c.id)
|
||||||
def case(request) -> DiffusionCase:
|
def case(request) -> DiffusionTestCase:
|
||||||
"""Provide a DiffusionCase for each test."""
|
"""Provide a DiffusionTestCase for each test."""
|
||||||
return request.param
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def diffusion_server(case: DiffusionCase) -> ServerContext:
|
def diffusion_server(case: DiffusionTestCase) -> ServerContext:
|
||||||
"""Start a diffusion server for a single case and tear it down afterwards."""
|
"""Start a diffusion server for a single case and tear it down afterwards."""
|
||||||
default_port = get_dynamic_server_port()
|
default_port = get_dynamic_server_port()
|
||||||
port = int(os.environ.get("SGLANG_TEST_SERVER_PORT", default_port))
|
port = int(os.environ.get("SGLANG_TEST_SERVER_PORT", default_port))
|
||||||
@@ -79,17 +78,17 @@ def diffusion_server(case: DiffusionCase) -> ServerContext:
|
|||||||
)
|
)
|
||||||
warmup.run_text_warmups(case.warmup_text)
|
warmup.run_text_warmups(case.warmup_text)
|
||||||
|
|
||||||
if case.warmup_edit > 0 and case.image_edit_prompt and case.image_edit_path:
|
if case.warmup_edit > 0 and case.edit_prompt and case.image_path:
|
||||||
# Handle URL or local path
|
# Handle URL or local path
|
||||||
image_path = case.image_edit_path
|
image_path = case.image_path
|
||||||
if case.is_image_url():
|
if case.is_image_url():
|
||||||
image_path = download_image_from_url(str(case.image_edit_path))
|
image_path = download_image_from_url(str(case.image_path))
|
||||||
else:
|
else:
|
||||||
image_path = Path(case.image_edit_path)
|
image_path = Path(case.image_path)
|
||||||
|
|
||||||
warmup.run_edit_warmups(
|
warmup.run_edit_warmups(
|
||||||
count=case.warmup_edit,
|
count=case.warmup_edit,
|
||||||
edit_prompt=case.image_edit_prompt,
|
edit_prompt=case.edit_prompt,
|
||||||
image_path=image_path,
|
image_path=image_path,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -132,12 +131,13 @@ class TestDiffusionPerformance:
|
|||||||
def _run_and_collect(
|
def _run_and_collect(
|
||||||
self,
|
self,
|
||||||
ctx: ServerContext,
|
ctx: ServerContext,
|
||||||
case: DiffusionCase,
|
case: DiffusionTestCase,
|
||||||
generate_fn: Callable[[], None],
|
generate_fn: Callable[[], None],
|
||||||
) -> tuple[dict, dict]:
|
) -> tuple[dict, dict]:
|
||||||
"""Run generation and collect performance records."""
|
"""Run generation and collect performance records."""
|
||||||
log_path = ctx.perf_log_path
|
log_path = ctx.perf_log_path
|
||||||
prev_len = len(read_perf_records(log_path))
|
prev_len = len(read_perf_records(log_path))
|
||||||
|
log_wait_timeout = 1200
|
||||||
|
|
||||||
generate_fn()
|
generate_fn()
|
||||||
|
|
||||||
@@ -145,22 +145,25 @@ class TestDiffusionPerformance:
|
|||||||
"total_inference_time",
|
"total_inference_time",
|
||||||
prev_len,
|
prev_len,
|
||||||
log_path,
|
log_path,
|
||||||
|
timeout=log_wait_timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
scenario = BASELINE_CONFIG.scenarios[case.scenario_name]
|
stage_metrics = {}
|
||||||
stage_metrics, _ = wait_for_stage_metrics(
|
if perf_record:
|
||||||
perf_record.get("request_id", ""),
|
|
||||||
prev_len,
|
stage_metrics, _ = wait_for_stage_metrics(
|
||||||
len(scenario.stages_ms),
|
perf_record.get("request_id", ""),
|
||||||
log_path,
|
prev_len,
|
||||||
)
|
log_path,
|
||||||
|
timeout=log_wait_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
return perf_record, stage_metrics
|
return perf_record, stage_metrics
|
||||||
|
|
||||||
def _generate_for_case(
|
def _generate_for_case(
|
||||||
self,
|
self,
|
||||||
ctx: ServerContext,
|
ctx: ServerContext,
|
||||||
case: DiffusionCase,
|
case: DiffusionTestCase,
|
||||||
) -> Callable[[], None]:
|
) -> Callable[[], None]:
|
||||||
"""Return appropriate generation function for the case."""
|
"""Return appropriate generation function for the case."""
|
||||||
client = self._client(ctx)
|
client = self._client(ctx)
|
||||||
@@ -192,21 +195,35 @@ class TestDiffusionPerformance:
|
|||||||
job = client.videos.create(**create_kwargs) # type: ignore[attr-defined]
|
job = client.videos.create(**create_kwargs) # type: ignore[attr-defined]
|
||||||
video_id = job.id
|
video_id = job.id
|
||||||
|
|
||||||
deadline = time.time() + 600
|
job_completed = False
|
||||||
|
is_baseline_generation_mode = (
|
||||||
|
os.environ.get("SGLANG_GEN_BASELINE", "0") == "1"
|
||||||
|
)
|
||||||
|
timeout = 3600.0 if is_baseline_generation_mode else 600.0
|
||||||
|
deadline = time.time() + timeout
|
||||||
while True:
|
while True:
|
||||||
page = client.videos.list() # type: ignore[attr-defined]
|
page = client.videos.list() # type: ignore[attr-defined]
|
||||||
item = next((v for v in page.data if v.id == video_id), None)
|
item = next((v for v in page.data if v.id == video_id), None)
|
||||||
|
|
||||||
if item and getattr(item, "status", None) == "completed":
|
if item and getattr(item, "status", None) == "completed":
|
||||||
|
job_completed = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if time.time() > deadline:
|
if time.time() > deadline:
|
||||||
pytest.fail(
|
break
|
||||||
f"{case.id}: video job {video_id} did not complete in time"
|
|
||||||
)
|
|
||||||
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
|
if not job_completed:
|
||||||
|
if is_baseline_generation_mode:
|
||||||
|
logger.warning(
|
||||||
|
f"{case.id}: video job {video_id} timed out during baseline generation. "
|
||||||
|
"Attempting to collect performance data anyway."
|
||||||
|
)
|
||||||
|
return b""
|
||||||
|
|
||||||
|
pytest.fail(f"{case.id}: video job {video_id} did not complete in time")
|
||||||
|
|
||||||
# download video
|
# download video
|
||||||
resp = client.videos.download_content(video_id=video_id) # type: ignore[attr-defined]
|
resp = client.videos.download_content(video_id=video_id) # type: ignore[attr-defined]
|
||||||
content = resp.read()
|
content = resp.read()
|
||||||
@@ -235,14 +252,14 @@ class TestDiffusionPerformance:
|
|||||||
|
|
||||||
def generate_image_edit():
|
def generate_image_edit():
|
||||||
"""TI2I: Text + Image ? Image edit."""
|
"""TI2I: Text + Image ? Image edit."""
|
||||||
if not case.image_edit_prompt or not case.image_edit_path:
|
if not case.edit_prompt or not case.image_path:
|
||||||
pytest.skip(f"{case.id}: no edit config")
|
pytest.skip(f"{case.id}: no edit config")
|
||||||
|
|
||||||
# Handle URL or local path
|
# Handle URL or local path
|
||||||
if case.is_image_url():
|
if case.is_image_url():
|
||||||
image_path = download_image_from_url(str(case.image_edit_path))
|
image_path = download_image_from_url(str(case.image_path))
|
||||||
else:
|
else:
|
||||||
image_path = Path(case.image_edit_path)
|
image_path = Path(case.image_path)
|
||||||
if not image_path.exists():
|
if not image_path.exists():
|
||||||
pytest.skip(f"{case.id}: file missing: {image_path}")
|
pytest.skip(f"{case.id}: file missing: {image_path}")
|
||||||
|
|
||||||
@@ -250,7 +267,7 @@ class TestDiffusionPerformance:
|
|||||||
result = client.images.edit(
|
result = client.images.edit(
|
||||||
model=case.model_path,
|
model=case.model_path,
|
||||||
image=fh,
|
image=fh,
|
||||||
prompt=case.image_edit_prompt,
|
prompt=case.edit_prompt,
|
||||||
n=1,
|
n=1,
|
||||||
size=case.output_size,
|
size=case.output_size,
|
||||||
response_format="b64_json",
|
response_format="b64_json",
|
||||||
@@ -275,21 +292,21 @@ class TestDiffusionPerformance:
|
|||||||
|
|
||||||
def generate_image_to_video():
|
def generate_image_to_video():
|
||||||
"""I2V: Image ? Video (optional prompt)."""
|
"""I2V: Image ? Video (optional prompt)."""
|
||||||
if not case.image_edit_path:
|
if not case.image_path:
|
||||||
pytest.skip(f"{case.id}: no input image configured")
|
pytest.skip(f"{case.id}: no input image configured")
|
||||||
|
|
||||||
# Handle URL or local path
|
# Handle URL or local path
|
||||||
if case.is_image_url():
|
if case.is_image_url():
|
||||||
image_path = download_image_from_url(str(case.image_edit_path))
|
image_path = download_image_from_url(str(case.image_path))
|
||||||
else:
|
else:
|
||||||
image_path = Path(case.image_edit_path)
|
image_path = Path(case.image_path)
|
||||||
if not image_path.exists():
|
if not image_path.exists():
|
||||||
pytest.skip(f"{case.id}: file missing: {image_path}")
|
pytest.skip(f"{case.id}: file missing: {image_path}")
|
||||||
|
|
||||||
with image_path.open("rb") as fh:
|
with image_path.open("rb") as fh:
|
||||||
_create_and_download_video(
|
_create_and_download_video(
|
||||||
model=case.model_path,
|
model=case.model_path,
|
||||||
prompt=case.image_edit_prompt,
|
prompt=case.edit_prompt,
|
||||||
size=case.output_size,
|
size=case.output_size,
|
||||||
seconds=video_seconds,
|
seconds=video_seconds,
|
||||||
input_reference=fh,
|
input_reference=fh,
|
||||||
@@ -297,49 +314,55 @@ class TestDiffusionPerformance:
|
|||||||
|
|
||||||
def generate_text_image_to_video():
|
def generate_text_image_to_video():
|
||||||
"""TI2V: Text + Image ? Video."""
|
"""TI2V: Text + Image ? Video."""
|
||||||
if not case.image_edit_prompt or not case.image_edit_path:
|
if not case.edit_prompt or not case.image_path:
|
||||||
pytest.skip(f"{case.id}: no edit config")
|
pytest.skip(f"{case.id}: no edit config")
|
||||||
|
|
||||||
# Handle URL or local path
|
# Handle URL or local path
|
||||||
if case.is_image_url():
|
if case.is_image_url():
|
||||||
image_path = download_image_from_url(str(case.image_edit_path))
|
image_path = download_image_from_url(str(case.image_path))
|
||||||
else:
|
else:
|
||||||
image_path = Path(case.image_edit_path)
|
image_path = Path(case.image_path)
|
||||||
if not image_path.exists():
|
if not image_path.exists():
|
||||||
pytest.skip(f"{case.id}: file missing: {image_path}")
|
pytest.skip(f"{case.id}: file missing: {image_path}")
|
||||||
|
|
||||||
with image_path.open("rb") as fh:
|
with image_path.open("rb") as fh:
|
||||||
_create_and_download_video(
|
_create_and_download_video(
|
||||||
model=case.model_path,
|
model=case.model_path,
|
||||||
prompt=case.image_edit_prompt,
|
prompt=case.edit_prompt,
|
||||||
size=case.output_size,
|
size=case.output_size,
|
||||||
seconds=video_seconds,
|
seconds=video_seconds,
|
||||||
input_reference=fh,
|
input_reference=fh,
|
||||||
)
|
)
|
||||||
|
|
||||||
if case.modality == "video":
|
if case.modality == "video":
|
||||||
if case.image_edit_path and case.image_edit_prompt:
|
if case.image_path and case.edit_prompt:
|
||||||
return generate_text_image_to_video
|
return generate_text_image_to_video
|
||||||
elif case.image_edit_path:
|
elif case.image_path:
|
||||||
return generate_image_to_video
|
return generate_image_to_video
|
||||||
else:
|
else:
|
||||||
return generate_video
|
return generate_video
|
||||||
|
|
||||||
# Image modality
|
# Image modality
|
||||||
if case.image_edit_prompt and case.image_edit_path:
|
if case.edit_prompt and case.image_path:
|
||||||
return generate_image_edit
|
return generate_image_edit
|
||||||
|
|
||||||
return generate_image
|
return generate_image
|
||||||
|
|
||||||
def _validate_and_record(
|
def _validate_and_record(
|
||||||
self,
|
self,
|
||||||
case: DiffusionCase,
|
case: DiffusionTestCase,
|
||||||
perf_record: dict,
|
perf_record: dict,
|
||||||
stage_metrics: dict,
|
stage_metrics: dict,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Validate metrics and record results."""
|
"""Validate metrics and record results."""
|
||||||
scenario = BASELINE_CONFIG.scenarios[case.scenario_name]
|
is_baseline_generation_mode = os.environ.get("SGLANG_GEN_BASELINE", "0") == "1"
|
||||||
|
|
||||||
|
scenario = BASELINE_CONFIG.scenarios.get(case.id)
|
||||||
|
if scenario is None:
|
||||||
|
if is_baseline_generation_mode:
|
||||||
|
scenario = {} # Dummy scenario
|
||||||
|
else:
|
||||||
|
pytest.fail(f"Testcase '{case.id}' not in perf_baselines.json")
|
||||||
validator_name = case.custom_validator or "default"
|
validator_name = case.custom_validator or "default"
|
||||||
validator_class = VALIDATOR_REGISTRY.get(validator_name, PerformanceValidator)
|
validator_class = VALIDATOR_REGISTRY.get(validator_name, PerformanceValidator)
|
||||||
|
|
||||||
@@ -349,10 +372,17 @@ class TestDiffusionPerformance:
|
|||||||
step_fractions=BASELINE_CONFIG.step_fractions,
|
step_fractions=BASELINE_CONFIG.step_fractions,
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(validator, VideoPerformanceValidator):
|
summary = validator.collect_metrics(perf_record, stage_metrics)
|
||||||
summary = validator.validate(perf_record, stage_metrics, case.num_frames)
|
|
||||||
else:
|
if is_baseline_generation_mode:
|
||||||
summary = validator.validate(perf_record, stage_metrics)
|
self._dump_baseline_scenario(case, summary)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
validator.validate(perf_record, stage_metrics, case.num_frames)
|
||||||
|
except AssertionError:
|
||||||
|
self._dump_baseline_scenario(case, summary)
|
||||||
|
raise
|
||||||
|
|
||||||
if case.modality == "video" and summary.frames_per_second:
|
if case.modality == "video" and summary.frames_per_second:
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -428,9 +458,46 @@ class TestDiffusionPerformance:
|
|||||||
summary.avg_frame_time_ms,
|
summary.avg_frame_time_ms,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _dump_baseline_scenario(
|
||||||
|
self, case: DiffusionTestCase, summary: "PerformanceSummary"
|
||||||
|
) -> None:
|
||||||
|
"""Dump performance metrics as a JSON scenario for baselines."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
denoise_steps_formatted = {
|
||||||
|
str(k): round(v, 2) for k, v in summary.sampled_steps.items()
|
||||||
|
}
|
||||||
|
stages_formatted = {k: round(v, 2) for k, v in summary.stage_metrics.items()}
|
||||||
|
|
||||||
|
baseline = {
|
||||||
|
"stages_ms": stages_formatted,
|
||||||
|
"denoise_step_ms": denoise_steps_formatted,
|
||||||
|
"expected_e2e_ms": round(summary.e2e_ms, 2),
|
||||||
|
"expected_avg_denoise_ms": round(summary.avg_denoise_ms, 2),
|
||||||
|
"expected_median_denoise_ms": round(summary.median_denoise_ms, 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Video-specific metrics
|
||||||
|
if case.modality == "video":
|
||||||
|
if "per_frame_generation" not in baseline["stages_ms"]:
|
||||||
|
baseline["stages_ms"]["per_frame_generation"] = (
|
||||||
|
round(summary.avg_frame_time_ms, 2)
|
||||||
|
if summary.avg_frame_time_ms
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
output = f"""
|
||||||
|
To add this baseline, copy the following JSON snippet into
|
||||||
|
the "scenarios" section of perf_baselines.json:
|
||||||
|
|
||||||
|
"{case.id}": {json.dumps(baseline, indent=4)}
|
||||||
|
|
||||||
|
"""
|
||||||
|
print(output)
|
||||||
|
|
||||||
def test_diffusion_perf(
|
def test_diffusion_perf(
|
||||||
self,
|
self,
|
||||||
case: DiffusionCase,
|
case: DiffusionTestCase,
|
||||||
diffusion_server: ServerContext,
|
diffusion_server: ServerContext,
|
||||||
):
|
):
|
||||||
"""Single parametrized test that runs for all cases.
|
"""Single parametrized test that runs for all cases.
|
||||||
|
|||||||
+133
-85
@@ -7,7 +7,9 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
import statistics
|
import statistics
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -18,7 +20,7 @@ from openai import OpenAI
|
|||||||
|
|
||||||
from sglang.multimodal_gen.runtime.utils.common import kill_process_tree
|
from sglang.multimodal_gen.runtime.utils.common import kill_process_tree
|
||||||
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||||
from sglang.multimodal_gen.test.server.diffusion_config import (
|
from sglang.multimodal_gen.test.server.testcase_configs import (
|
||||||
PerformanceSummary,
|
PerformanceSummary,
|
||||||
ScenarioConfig,
|
ScenarioConfig,
|
||||||
ToleranceConfig,
|
ToleranceConfig,
|
||||||
@@ -74,6 +76,7 @@ class ServerContext:
|
|||||||
perf_log_path: Path
|
perf_log_path: Path
|
||||||
log_dir: Path
|
log_dir: Path
|
||||||
_stdout_fh: Any = field(repr=False)
|
_stdout_fh: Any = field(repr=False)
|
||||||
|
_log_thread: threading.Thread | None = field(default=None, repr=False)
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
"""Clean up server resources."""
|
"""Clean up server resources."""
|
||||||
@@ -127,19 +130,42 @@ class ServerManager:
|
|||||||
command.extend(self.extra_args.strip().split())
|
command.extend(self.extra_args.strip().split())
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env["SGL_DIFFUSION_STAGE_LOGGING"] = "1"
|
env["SGLANG_DIFFUSION_STAGE_LOGGING"] = "1"
|
||||||
env["SGLANG_PERF_LOG_DIR"] = log_dir.as_posix()
|
env["SGLANG_PERF_LOG_DIR"] = log_dir.as_posix()
|
||||||
|
|
||||||
stdout_fh = stdout_path.open("w", encoding="utf-8", buffering=1)
|
stdout_fh = stdout_path.open("w", encoding="utf-8", buffering=1)
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
command,
|
command,
|
||||||
stdout=stdout_fh,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
text=True,
|
text=True,
|
||||||
bufsize=1,
|
bufsize=1,
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
log_thread = None
|
||||||
|
if process.stdout:
|
||||||
|
|
||||||
|
def _log_pipe(pipe: Any, file: Any) -> None:
|
||||||
|
"""Read from pipe and write to file and stdout."""
|
||||||
|
try:
|
||||||
|
with pipe:
|
||||||
|
for line in iter(pipe.readline, ""):
|
||||||
|
sys.stdout.write(line)
|
||||||
|
file.write(line)
|
||||||
|
file.flush()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Log pipe thread error: %s", e)
|
||||||
|
finally:
|
||||||
|
file.close()
|
||||||
|
logger.debug("Log pipe thread finished.")
|
||||||
|
|
||||||
|
log_thread = threading.Thread(
|
||||||
|
target=_log_pipe, args=(process.stdout, stdout_fh)
|
||||||
|
)
|
||||||
|
log_thread.daemon = True
|
||||||
|
log_thread.start()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"[server-test] Starting server pid=%s, model=%s, log=%s",
|
"[server-test] Starting server pid=%s, model=%s, log=%s",
|
||||||
process.pid,
|
process.pid,
|
||||||
@@ -157,6 +183,7 @@ class ServerManager:
|
|||||||
perf_log_path=perf_log_path,
|
perf_log_path=perf_log_path,
|
||||||
log_dir=log_dir,
|
log_dir=log_dir,
|
||||||
_stdout_fh=stdout_fh,
|
_stdout_fh=stdout_fh,
|
||||||
|
_log_thread=log_thread,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _wait_for_ready(self, process: subprocess.Popen, stdout_path: Path) -> None:
|
def _wait_for_ready(self, process: subprocess.Popen, stdout_path: Path) -> None:
|
||||||
@@ -264,6 +291,8 @@ class WarmupRunner:
|
|||||||
class PerformanceValidator:
|
class PerformanceValidator:
|
||||||
"""Validates performance metrics against expectations."""
|
"""Validates performance metrics against expectations."""
|
||||||
|
|
||||||
|
is_video_gen: bool = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
scenario: ScenarioConfig,
|
scenario: ScenarioConfig,
|
||||||
@@ -273,104 +302,134 @@ class PerformanceValidator:
|
|||||||
self.scenario = scenario
|
self.scenario = scenario
|
||||||
self.tolerances = tolerances
|
self.tolerances = tolerances
|
||||||
self.step_fractions = step_fractions
|
self.step_fractions = step_fractions
|
||||||
|
self.is_baseline_generation_mode = (
|
||||||
|
os.environ.get("SGLANG_GEN_BASELINE", "0") == "1"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _assert_le(self, name: str, actual: float, expected: float, tolerance: float):
|
||||||
|
"""Assert that actual is less than or equal to expected within a tolerance."""
|
||||||
|
upper_bound = expected * (1 + tolerance)
|
||||||
|
assert actual <= upper_bound, (
|
||||||
|
f"Validation failed for '{name}'.\n"
|
||||||
|
f" - Actual: {actual:.4f}ms\n"
|
||||||
|
f" - Expected: {expected:.4f}ms\n"
|
||||||
|
f" - Limit: {upper_bound:.4f}ms (tolerance: {tolerance:.1%})"
|
||||||
|
)
|
||||||
|
|
||||||
def validate(
|
def validate(
|
||||||
|
self, perf_record: dict, stage_metrics: dict, *args, **kwargs
|
||||||
|
) -> PerformanceSummary:
|
||||||
|
"""Validate all performance metrics and return summary."""
|
||||||
|
summary = self.collect_metrics(perf_record, stage_metrics)
|
||||||
|
if self.is_baseline_generation_mode:
|
||||||
|
return summary
|
||||||
|
|
||||||
|
self._validate_e2e(summary)
|
||||||
|
self._validate_denoise_agg(summary)
|
||||||
|
self._validate_denoise_steps(summary)
|
||||||
|
self._validate_stages(summary)
|
||||||
|
|
||||||
|
return summary
|
||||||
|
|
||||||
|
def collect_metrics(
|
||||||
self,
|
self,
|
||||||
perf_record: dict,
|
perf_record: dict,
|
||||||
stage_metrics: dict,
|
stage_metrics: dict,
|
||||||
) -> PerformanceSummary:
|
) -> PerformanceSummary:
|
||||||
"""Validate all performance metrics and return summary."""
|
"""Collect all performance metrics into a summary without validation."""
|
||||||
self._validate_e2e(perf_record)
|
|
||||||
avg_denoise, median_denoise = self._validate_denoise_agg(perf_record)
|
|
||||||
sampled_steps = self._validate_denoise_steps(perf_record)
|
|
||||||
self._validate_stages(stage_metrics)
|
|
||||||
|
|
||||||
return PerformanceSummary(
|
|
||||||
e2e_ms=float(perf_record["total_duration_ms"]),
|
|
||||||
avg_denoise_ms=avg_denoise,
|
|
||||||
median_denoise_ms=median_denoise,
|
|
||||||
stage_metrics=stage_metrics,
|
|
||||||
sampled_steps=sampled_steps,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_e2e(self, perf_record: dict) -> None:
|
|
||||||
"""Validate end-to-end performance."""
|
|
||||||
e2e_ms = float(perf_record.get("total_duration_ms", 0.0))
|
e2e_ms = float(perf_record.get("total_duration_ms", 0.0))
|
||||||
assert e2e_ms > 0, "E2E duration missing"
|
|
||||||
|
|
||||||
upper = self.scenario.expected_e2e_ms * (1 + self.tolerances.e2e)
|
|
||||||
assert e2e_ms <= upper, f"E2E {e2e_ms:.2f}ms exceeds {upper:.2f}ms"
|
|
||||||
|
|
||||||
def _validate_denoise_agg(self, perf_record: dict) -> tuple[float, float]:
|
|
||||||
"""Validate aggregate denoising metrics."""
|
|
||||||
steps = [
|
steps = [
|
||||||
s
|
s
|
||||||
for s in perf_record.get("steps", []) or []
|
for s in perf_record.get("steps", []) or []
|
||||||
if s.get("name") == "denoising_step_guided" and "duration_ms" in s
|
if s.get("name") == "denoising_step_guided" and "duration_ms" in s
|
||||||
]
|
]
|
||||||
assert steps, "Denoising step timings missing"
|
|
||||||
|
|
||||||
durations = [float(s["duration_ms"]) for s in steps]
|
avg_denoise = 0.0
|
||||||
avg = sum(durations) / len(durations)
|
median_denoise = 0.0
|
||||||
median = statistics.median(durations)
|
if steps:
|
||||||
|
durations = [float(s["duration_ms"]) for s in steps]
|
||||||
avg_upper = self.scenario.expected_avg_denoise_ms * (
|
avg_denoise = sum(durations) / len(durations)
|
||||||
1 + self.tolerances.denoise_agg
|
median_denoise = statistics.median(durations)
|
||||||
)
|
|
||||||
med_upper = self.scenario.expected_median_denoise_ms * (
|
|
||||||
1 + self.tolerances.denoise_agg
|
|
||||||
)
|
|
||||||
|
|
||||||
assert avg <= avg_upper, f"Avg denoise {avg:.2f}ms exceeds {avg_upper:.2f}ms"
|
|
||||||
assert (
|
|
||||||
median <= med_upper
|
|
||||||
), f"Median denoise {median:.2f}ms exceeds {med_upper:.2f}ms"
|
|
||||||
|
|
||||||
return avg, median
|
|
||||||
|
|
||||||
def _validate_denoise_steps(self, perf_record: dict) -> dict[int, float]:
|
|
||||||
"""Validate individual denoising steps."""
|
|
||||||
steps = [
|
|
||||||
s
|
|
||||||
for s in perf_record.get("steps", []) or []
|
|
||||||
if s.get("name") == "denoising_step_guided" and "duration_ms" in s
|
|
||||||
]
|
|
||||||
|
|
||||||
per_step = {
|
per_step = {
|
||||||
int(s["index"]): float(s["duration_ms"])
|
int(s["index"]): float(s["duration_ms"])
|
||||||
for s in steps
|
for s in steps
|
||||||
if s.get("index") is not None
|
if s.get("index") is not None
|
||||||
}
|
}
|
||||||
|
|
||||||
sample_indices = sample_step_indices(per_step, self.step_fractions)
|
sample_indices = sample_step_indices(per_step, self.step_fractions)
|
||||||
sampled = {idx: per_step[idx] for idx in sample_indices}
|
sampled_steps = {idx: per_step[idx] for idx in sample_indices}
|
||||||
|
|
||||||
for idx in sample_indices:
|
return PerformanceSummary(
|
||||||
|
e2e_ms=e2e_ms,
|
||||||
|
avg_denoise_ms=avg_denoise,
|
||||||
|
median_denoise_ms=median_denoise,
|
||||||
|
stage_metrics=stage_metrics,
|
||||||
|
sampled_steps=sampled_steps,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_e2e(self, summary: PerformanceSummary) -> None:
|
||||||
|
"""Validate end-to-end performance."""
|
||||||
|
assert summary.e2e_ms > 0, "E2E duration missing"
|
||||||
|
self._assert_le(
|
||||||
|
"E2E Latency",
|
||||||
|
summary.e2e_ms,
|
||||||
|
self.scenario.expected_e2e_ms,
|
||||||
|
self.tolerances.e2e,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_denoise_agg(self, summary: PerformanceSummary) -> None:
|
||||||
|
"""Validate aggregate denoising metrics."""
|
||||||
|
assert summary.avg_denoise_ms > 0, "Denoising step timings missing"
|
||||||
|
|
||||||
|
self._assert_le(
|
||||||
|
"Average Denoise Step",
|
||||||
|
summary.avg_denoise_ms,
|
||||||
|
self.scenario.expected_avg_denoise_ms,
|
||||||
|
self.tolerances.denoise_agg,
|
||||||
|
)
|
||||||
|
self._assert_le(
|
||||||
|
"Median Denoise Step",
|
||||||
|
summary.median_denoise_ms,
|
||||||
|
self.scenario.expected_median_denoise_ms,
|
||||||
|
self.tolerances.denoise_agg,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_denoise_steps(self, summary: PerformanceSummary) -> None:
|
||||||
|
"""Validate individual denoising steps."""
|
||||||
|
for idx, actual in summary.sampled_steps.items():
|
||||||
expected = self.scenario.denoise_step_ms.get(idx)
|
expected = self.scenario.denoise_step_ms.get(idx)
|
||||||
if expected is None:
|
if expected is None:
|
||||||
continue
|
continue
|
||||||
|
self._assert_le(
|
||||||
|
f"Denoise Step {idx}",
|
||||||
|
actual,
|
||||||
|
expected,
|
||||||
|
self.tolerances.denoise_step,
|
||||||
|
)
|
||||||
|
|
||||||
actual = per_step[idx]
|
def _validate_stages(self, summary: PerformanceSummary) -> None:
|
||||||
upper = expected * (1 + self.tolerances.denoise_step)
|
|
||||||
assert actual <= upper, f"Step {idx}: {actual:.2f}ms > {upper:.2f}ms"
|
|
||||||
|
|
||||||
return sampled
|
|
||||||
|
|
||||||
def _validate_stages(self, stage_metrics: dict) -> None:
|
|
||||||
"""Validate stage-level metrics."""
|
"""Validate stage-level metrics."""
|
||||||
assert stage_metrics, "Stage metrics missing"
|
assert summary.stage_metrics, "Stage metrics missing"
|
||||||
|
|
||||||
for stage, expected in self.scenario.stages_ms.items():
|
for stage, expected in self.scenario.stages_ms.items():
|
||||||
actual = stage_metrics.get(stage)
|
if stage == "per_frame_generation" and self.is_video_gen:
|
||||||
|
continue
|
||||||
|
actual = summary.stage_metrics.get(stage)
|
||||||
assert actual is not None, f"Stage {stage} timing missing"
|
assert actual is not None, f"Stage {stage} timing missing"
|
||||||
|
|
||||||
upper = expected * (1 + self.tolerances.stage)
|
self._assert_le(
|
||||||
assert actual <= upper, f"Stage {stage}: {actual:.2f}ms > {upper:.2f}ms"
|
f"Stage '{stage}'",
|
||||||
|
actual,
|
||||||
|
expected,
|
||||||
|
self.tolerances.stage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VideoPerformanceValidator(PerformanceValidator):
|
class VideoPerformanceValidator(PerformanceValidator):
|
||||||
"""Extended validator for video diffusion with frame-level metrics."""
|
"""Extended validator for video diffusion with frame-level metrics."""
|
||||||
|
|
||||||
|
is_video_gen = True
|
||||||
|
|
||||||
def validate(
|
def validate(
|
||||||
self,
|
self,
|
||||||
perf_record: dict,
|
perf_record: dict,
|
||||||
@@ -385,7 +444,8 @@ class VideoPerformanceValidator(PerformanceValidator):
|
|||||||
summary.avg_frame_time_ms = summary.e2e_ms / num_frames
|
summary.avg_frame_time_ms = summary.e2e_ms / num_frames
|
||||||
summary.frames_per_second = 1000.0 / summary.avg_frame_time_ms
|
summary.frames_per_second = 1000.0 / summary.avg_frame_time_ms
|
||||||
|
|
||||||
self._validate_frame_rate(summary)
|
if not self.is_baseline_generation_mode:
|
||||||
|
self._validate_frame_rate(summary)
|
||||||
|
|
||||||
return summary
|
return summary
|
||||||
|
|
||||||
@@ -393,24 +453,12 @@ class VideoPerformanceValidator(PerformanceValidator):
|
|||||||
"""Validate frame generation performance."""
|
"""Validate frame generation performance."""
|
||||||
expected_frame_time = self.scenario.stages_ms.get("per_frame_generation")
|
expected_frame_time = self.scenario.stages_ms.get("per_frame_generation")
|
||||||
if expected_frame_time and summary.avg_frame_time_ms:
|
if expected_frame_time and summary.avg_frame_time_ms:
|
||||||
upper = expected_frame_time * (1 + self.tolerances.stage)
|
self._assert_le(
|
||||||
assert (
|
"Average Frame Time",
|
||||||
summary.avg_frame_time_ms <= upper
|
summary.avg_frame_time_ms,
|
||||||
), f"Avg frame time {summary.avg_frame_time_ms:.2f}ms exceeds {upper:.2f}ms"
|
expected_frame_time,
|
||||||
|
self.tolerances.stage,
|
||||||
def _validate_stages(self, stage_metrics: dict) -> None:
|
)
|
||||||
"""Validate video-specific stages."""
|
|
||||||
assert stage_metrics, "Stage metrics missing"
|
|
||||||
|
|
||||||
for stage, expected in self.scenario.stages_ms.items():
|
|
||||||
if stage == "per_frame_generation":
|
|
||||||
continue
|
|
||||||
|
|
||||||
actual = stage_metrics.get(stage)
|
|
||||||
assert actual is not None, f"Stage {stage} timing missing"
|
|
||||||
|
|
||||||
upper = expected * (1 + self.tolerances.stage)
|
|
||||||
assert actual <= upper, f"Stage {stage}: {actual:.2f}ms > {upper:.2f}ms"
|
|
||||||
|
|
||||||
|
|
||||||
# Registry of validators by name
|
# Registry of validators by name
|
||||||
+75
-37
@@ -1,5 +1,19 @@
|
|||||||
"""
|
"""
|
||||||
Configuration and data structures for diffusion performance tests.
|
Configuration and data structures for diffusion performance tests.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
pytest python/sglang/multimodal_gen/test/server/test_server_performance.py
|
||||||
|
# for a single testcase, look for the name of the testcases in DIFFUSION_CASES
|
||||||
|
pytest python/sglang/multimodal_gen/test/server/test_server_performance.py -k qwen_image_t2i
|
||||||
|
|
||||||
|
|
||||||
|
To add a new testcase:
|
||||||
|
1. add your testcase with case-id: `my_new_test_case_id` to DIFFUSION_CASES
|
||||||
|
2. run `SGLANG_GEN_BASELINE=1 pytest -s python/sglang/multimodal_gen/test/server/test_server_performance.py -k my_new_test_case_id`
|
||||||
|
3. insert or override the corresponding scenario in `scenarios` section of perf_baselines.json with the output baseline of step-2
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -78,34 +92,36 @@ class BaselineConfig:
|
|||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class DiffusionCase:
|
class DiffusionTestCase:
|
||||||
"""Configuration for a single model/scenario test case."""
|
"""Configuration for a single model/scenario test case."""
|
||||||
|
|
||||||
id: str # pytest test id
|
id: str # pytest test id and scenario name
|
||||||
model_path: str # HF repo or local path
|
model_path: str # HF repo or local path
|
||||||
scenario_name: str # key into BASELINE_CONFIG.scenarios
|
|
||||||
modality: str = "image" # "image" or "video" or "3d"
|
modality: str = "image" # "image" or "video" or "3d"
|
||||||
prompt: str | None = None # text prompt for generation
|
|
||||||
output_size: str = "1024x1024" # output image dimensions (or video resolution)
|
output_size: str = "1024x1024" # output image dimensions (or video resolution)
|
||||||
|
|
||||||
|
# inputs and conditioning
|
||||||
|
prompt: str | None = None # text prompt for generation
|
||||||
|
edit_prompt: str | None = None # prompt for editing
|
||||||
|
image_path: Path | str | None = None # input image/video for editing (Path or URL)
|
||||||
|
|
||||||
|
# duration
|
||||||
|
seconds: int = 4 # for video: duration in seconds
|
||||||
num_frames: int | None = None # for video: number of frames
|
num_frames: int | None = None # for video: number of frames
|
||||||
fps: int | None = None # for video: frames per second
|
fps: int | None = None # for video: frames per second
|
||||||
|
|
||||||
warmup_text: int = 1 # number of text-to-image/video warmups
|
warmup_text: int = 1 # number of text-to-image/video warmups
|
||||||
warmup_edit: int = 0 # number of image/video-edit warmups
|
warmup_edit: int = 0 # number of image/video-edit warmups
|
||||||
image_edit_prompt: str | None = None # prompt for editing
|
|
||||||
image_edit_path: Path | str | None = (
|
|
||||||
None # input image/video for editing (Path or URL)
|
|
||||||
)
|
|
||||||
startup_grace_seconds: float = 0.0 # wait time after server starts
|
startup_grace_seconds: float = 0.0 # wait time after server starts
|
||||||
custom_validator: str | None = None # optional custom validator name
|
custom_validator: str | None = None # optional custom validator name
|
||||||
seconds: int = 4 # for video: duration in seconds
|
|
||||||
|
|
||||||
def is_image_url(self) -> bool:
|
def is_image_url(self) -> bool:
|
||||||
"""Check if image_edit_path is a URL."""
|
"""Check if image_edit_path is a URL."""
|
||||||
if self.image_edit_path is None:
|
if self.image_path is None:
|
||||||
return False
|
return False
|
||||||
return isinstance(self.image_edit_path, str) and (
|
return isinstance(self.image_path, str) and (
|
||||||
self.image_edit_path.startswith("http://")
|
self.image_path.startswith("http://")
|
||||||
or self.image_edit_path.startswith("https://")
|
or self.image_path.startswith("https://")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -128,12 +144,11 @@ IMAGE_INPUT_FILE = Path(__file__).resolve().parents[1] / "test_files" / "girl.jp
|
|||||||
|
|
||||||
# All test cases with clean default values
|
# All test cases with clean default values
|
||||||
# To test different models, simply add more DiffusionCase entries
|
# To test different models, simply add more DiffusionCase entries
|
||||||
DIFFUSION_CASES: list[DiffusionCase] = [
|
DIFFUSION_CASES: list[DiffusionTestCase] = [
|
||||||
# === Text to Image (T2I) ===
|
# === Text to Image (T2I) ===
|
||||||
DiffusionCase(
|
DiffusionTestCase(
|
||||||
id="qwen_image_t2i",
|
id="qwen_image_t2i",
|
||||||
model_path="Qwen/Qwen-Image",
|
model_path="Qwen/Qwen-Image",
|
||||||
scenario_name="text_to_image",
|
|
||||||
modality="image",
|
modality="image",
|
||||||
prompt="A futuristic cityscape at sunset with flying cars",
|
prompt="A futuristic cityscape at sunset with flying cars",
|
||||||
output_size="1024x1024",
|
output_size="1024x1024",
|
||||||
@@ -141,10 +156,9 @@ DIFFUSION_CASES: list[DiffusionCase] = [
|
|||||||
warmup_edit=0,
|
warmup_edit=0,
|
||||||
startup_grace_seconds=30.0,
|
startup_grace_seconds=30.0,
|
||||||
),
|
),
|
||||||
DiffusionCase(
|
DiffusionTestCase(
|
||||||
id="flux_image_t2i",
|
id="flux_image_t2i",
|
||||||
model_path="black-forest-labs/FLUX.1-dev",
|
model_path="black-forest-labs/FLUX.1-dev",
|
||||||
scenario_name="text_to_image",
|
|
||||||
modality="image",
|
modality="image",
|
||||||
prompt="A futuristic cityscape at sunset with flying cars",
|
prompt="A futuristic cityscape at sunset with flying cars",
|
||||||
output_size="1024x1024",
|
output_size="1024x1024",
|
||||||
@@ -153,24 +167,23 @@ DIFFUSION_CASES: list[DiffusionCase] = [
|
|||||||
startup_grace_seconds=30.0,
|
startup_grace_seconds=30.0,
|
||||||
),
|
),
|
||||||
# === Text and Image to Image (TI2I) ===
|
# === Text and Image to Image (TI2I) ===
|
||||||
DiffusionCase(
|
DiffusionTestCase(
|
||||||
id="qwen_image_edit_ti2i",
|
id="qwen_image_edit_ti2i",
|
||||||
model_path="Qwen/Qwen-Image-Edit",
|
model_path="Qwen/Qwen-Image-Edit",
|
||||||
scenario_name="image_edit",
|
|
||||||
modality="image",
|
modality="image",
|
||||||
prompt=None, # not used for editing
|
prompt=None, # not used for editing
|
||||||
output_size="1024x1536",
|
output_size="1024x1536",
|
||||||
warmup_text=0,
|
warmup_text=0,
|
||||||
warmup_edit=1,
|
warmup_edit=1,
|
||||||
image_edit_prompt="Convert 2D style to 3D style",
|
edit_prompt="Convert 2D style to 3D style",
|
||||||
image_edit_path="https://github.com/lm-sys/lm-sys.github.io/releases/download/test/TI2I_Qwen_Image_Edit_Input.jpg",
|
image_path="https://github.com/lm-sys/lm-sys.github.io/releases/download/test/TI2I_Qwen_Image_Edit_Input.jpg",
|
||||||
startup_grace_seconds=30.0,
|
startup_grace_seconds=30.0,
|
||||||
),
|
),
|
||||||
# === Text to Video (T2V) ===
|
# === Text to Video (T2V) ===
|
||||||
DiffusionCase(
|
# TODO: FastWan2.1, FastWan2.2
|
||||||
|
DiffusionTestCase(
|
||||||
id="fastwan2_1_t2v",
|
id="fastwan2_1_t2v",
|
||||||
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||||
scenario_name="text_to_video",
|
|
||||||
modality="video",
|
modality="video",
|
||||||
prompt="A curious raccoon",
|
prompt="A curious raccoon",
|
||||||
output_size="848x480",
|
output_size="848x480",
|
||||||
@@ -181,39 +194,64 @@ DIFFUSION_CASES: list[DiffusionCase] = [
|
|||||||
custom_validator="video",
|
custom_validator="video",
|
||||||
),
|
),
|
||||||
# === Image to Video (I2V) ===
|
# === Image to Video (I2V) ===
|
||||||
DiffusionCase(
|
DiffusionTestCase(
|
||||||
id="wan2_2_i2v",
|
id="wan2_2_i2v_a14b",
|
||||||
model_path="Wan-AI/Wan2.2-I2V-A14B-Diffusers",
|
model_path="Wan-AI/Wan2.2-I2V-A14B-Diffusers",
|
||||||
scenario_name="image_to_video",
|
|
||||||
modality="video",
|
modality="video",
|
||||||
prompt="generate", # passing in something since failing if no prompt is passed
|
prompt="generate", # passing in something since failing if no prompt is passed
|
||||||
warmup_text=0, # warmups only for image gen models
|
warmup_text=0, # warmups only for image gen models
|
||||||
warmup_edit=0,
|
warmup_edit=0,
|
||||||
output_size="832x1104",
|
output_size="832x1104",
|
||||||
image_edit_prompt="generate",
|
edit_prompt="generate",
|
||||||
image_edit_path="https://github.com/Wan-Video/Wan2.2/blob/990af50de458c19590c245151197326e208d7191/examples/i2v_input.JPG?raw=true",
|
image_path="https://github.com/Wan-Video/Wan2.2/blob/990af50de458c19590c245151197326e208d7191/examples/i2v_input.JPG?raw=true",
|
||||||
startup_grace_seconds=30.0,
|
startup_grace_seconds=30.0,
|
||||||
custom_validator="video",
|
custom_validator="video",
|
||||||
seconds=1,
|
seconds=1,
|
||||||
),
|
),
|
||||||
# === Text and Image to Video (TI2V) ===
|
# === Text and Image to Video (TI2V) ===
|
||||||
DiffusionCase(
|
DiffusionTestCase(
|
||||||
id="wan2_2_ti2v_5b",
|
id="wan2_1_i2v_14b_480P",
|
||||||
model_path="Wan-AI/Wan2.2-TI2V-5B-Diffusers",
|
model_path="Wan-AI/Wan2.1-I2V-14B-480P-Diffusers",
|
||||||
scenario_name="text_image_to_video",
|
output_size="832x1104",
|
||||||
modality="video",
|
modality="video",
|
||||||
prompt="Animate this image",
|
prompt="Animate this image",
|
||||||
|
edit_prompt="Add dynamic motion to the scene",
|
||||||
|
image_path="https://github.com/lm-sys/lm-sys.github.io/releases/download/test/TI2I_Qwen_Image_Edit_Input.jpg",
|
||||||
|
warmup_text=0, # warmups only for image gen models
|
||||||
|
warmup_edit=0,
|
||||||
|
startup_grace_seconds=30.0,
|
||||||
|
custom_validator="video",
|
||||||
|
seconds=1,
|
||||||
|
),
|
||||||
|
DiffusionTestCase(
|
||||||
|
id="wan2_2_i2v_14b_720P",
|
||||||
|
model_path="Wan-AI/Wan2.1-I2V-14B-720P-Diffusers",
|
||||||
|
modality="video",
|
||||||
|
prompt="Animate this image",
|
||||||
|
edit_prompt="Add dynamic motion to the scene",
|
||||||
|
image_path="https://github.com/lm-sys/lm-sys.github.io/releases/download/test/TI2I_Qwen_Image_Edit_Input.jpg",
|
||||||
output_size="832x1104",
|
output_size="832x1104",
|
||||||
warmup_text=0, # warmups only for image gen models
|
warmup_text=0, # warmups only for image gen models
|
||||||
warmup_edit=0,
|
warmup_edit=0,
|
||||||
image_edit_prompt="Add dynamic motion to the scene",
|
|
||||||
image_edit_path="https://github.com/lm-sys/lm-sys.github.io/releases/download/test/TI2I_Qwen_Image_Edit_Input.jpg",
|
|
||||||
startup_grace_seconds=30.0,
|
startup_grace_seconds=30.0,
|
||||||
custom_validator="video",
|
custom_validator="video",
|
||||||
seconds=4,
|
seconds=1,
|
||||||
|
),
|
||||||
|
DiffusionTestCase(
|
||||||
|
id="wan2_2_ti2v_5b",
|
||||||
|
model_path="Wan-AI/Wan2.2-TI2V-5B-Diffusers",
|
||||||
|
modality="video",
|
||||||
|
output_size="832x1104",
|
||||||
|
prompt="Animate this image",
|
||||||
|
edit_prompt="Add dynamic motion to the scene",
|
||||||
|
image_path="https://github.com/lm-sys/lm-sys.github.io/releases/download/test/TI2I_Qwen_Image_Edit_Input.jpg",
|
||||||
|
warmup_text=0, # warmups only for image gen models
|
||||||
|
warmup_edit=0,
|
||||||
|
startup_grace_seconds=30.0,
|
||||||
|
custom_validator="video",
|
||||||
|
seconds=1,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Load global configuration
|
# Load global configuration
|
||||||
BASELINE_CONFIG = BaselineConfig.load(Path(__file__).with_name("perf_baselines.json"))
|
BASELINE_CONFIG = BaselineConfig.load(Path(__file__).with_name("perf_baselines.json"))
|
||||||
@@ -172,6 +172,11 @@ def wait_for_perf_record(
|
|||||||
if rec.get("tag") == tag:
|
if rec.get("tag") == tag:
|
||||||
return rec, len(records)
|
return rec, len(records)
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
if os.environ.get("SGLANG_GEN_BASELINE", "0") == "1":
|
||||||
|
records = read_perf_records(log_path)
|
||||||
|
return {}, len(records)
|
||||||
|
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
f"Timeout waiting for perf log entry '{tag}' (start_len={prev_len})"
|
f"Timeout waiting for perf log entry '{tag}' (start_len={prev_len})"
|
||||||
)
|
)
|
||||||
@@ -180,15 +185,21 @@ def wait_for_perf_record(
|
|||||||
def wait_for_stage_metrics(
|
def wait_for_stage_metrics(
|
||||||
request_id: str,
|
request_id: str,
|
||||||
prev_len: int,
|
prev_len: int,
|
||||||
expected_count: int,
|
|
||||||
log_path: Path,
|
log_path: Path,
|
||||||
timeout: float = 120.0,
|
timeout: float = 300.0,
|
||||||
) -> tuple[dict[str, float], int]:
|
) -> tuple[dict[str, float], int]:
|
||||||
deadline = time.time() + timeout
|
deadline = time.time() + timeout
|
||||||
metrics: dict[str, float] = {}
|
metrics: dict[str, float] = {}
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
records = read_perf_records(log_path)
|
records = read_perf_records(log_path)
|
||||||
for rec in records[prev_len:]:
|
for rec in records[prev_len:]:
|
||||||
|
# Check if the request is completed
|
||||||
|
if (
|
||||||
|
rec.get("tag") == "total_inference_time"
|
||||||
|
and rec.get("request_id") == request_id
|
||||||
|
):
|
||||||
|
return metrics, len(records)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
rec.get("tag") == "pipeline_stage_metric"
|
rec.get("tag") == "pipeline_stage_metric"
|
||||||
and rec.get("request_id") == request_id
|
and rec.get("request_id") == request_id
|
||||||
@@ -197,13 +208,12 @@ def wait_for_stage_metrics(
|
|||||||
duration = rec.get("duration_ms")
|
duration = rec.get("duration_ms")
|
||||||
if stage is not None and duration is not None:
|
if stage is not None and duration is not None:
|
||||||
metrics[str(stage)] = float(duration)
|
metrics[str(stage)] = float(duration)
|
||||||
if len(metrics) >= expected_count:
|
|
||||||
return metrics, len(records)
|
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
raise AssertionError(
|
|
||||||
f"Timeout waiting for stage metrics for request {request_id} "
|
if os.environ.get("SGLANG_GEN_BASELINE", "0") == "1":
|
||||||
f"(collected={len(metrics)} expected={expected_count})"
|
records = read_perf_records(log_path)
|
||||||
)
|
return {}, len(records)
|
||||||
|
raise AssertionError(f"Timeout waiting for stage metrics for request {request_id} ")
|
||||||
|
|
||||||
|
|
||||||
def sample_step_indices(
|
def sample_step_indices(
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ PRECISION_TO_TYPE = {
|
|||||||
"bf16": torch.bfloat16,
|
"bf16": torch.bfloat16,
|
||||||
}
|
}
|
||||||
|
|
||||||
STR_BACKEND_ENV_VAR: str = "SGL_DIFFUSION_ATTENTION_BACKEND"
|
STR_BACKEND_ENV_VAR: str = "SGLANG_DIFFUSION_ATTENTION_BACKEND"
|
||||||
STR_ATTN_CONFIG_ENV_VAR: str = "SGL_DIFFUSION_ATTENTION_CONFIG"
|
STR_ATTN_CONFIG_ENV_VAR: str = "SGLANG_DIFFUSION_ATTENTION_CONFIG"
|
||||||
|
|
||||||
|
|
||||||
def find_nccl_library() -> str:
|
def find_nccl_library() -> str:
|
||||||
@@ -59,12 +59,12 @@ def find_nccl_library() -> str:
|
|||||||
After importing `torch`, `libnccl.so.2` or `librccl.so.1` can be
|
After importing `torch`, `libnccl.so.2` or `librccl.so.1` can be
|
||||||
found by `ctypes` automatically.
|
found by `ctypes` automatically.
|
||||||
"""
|
"""
|
||||||
so_file = envs.SGL_DIFFUSION_NCCL_SO_PATH
|
so_file = envs.SGLANG_DIFFUSION_NCCL_SO_PATH
|
||||||
|
|
||||||
# manually load the nccl library
|
# manually load the nccl library
|
||||||
if so_file:
|
if so_file:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Found nccl from environment variable SGL_DIFFUSION_NCCL_SO_PATH=%s",
|
"Found nccl from environment variable SGLANG_DIFFUSION_NCCL_SO_PATH=%s",
|
||||||
so_file,
|
so_file,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -66,11 +66,16 @@ RUNNER_LABEL_MODEL_MAP: Dict[str, List[str]] = {
|
|||||||
"Qwen/Qwen3-Embedding-8B",
|
"Qwen/Qwen3-Embedding-8B",
|
||||||
"Qwen/QwQ-32B-AWQ",
|
"Qwen/QwQ-32B-AWQ",
|
||||||
"Qwen/Qwen3-30B-A3B",
|
"Qwen/Qwen3-30B-A3B",
|
||||||
|
"Skywork/Skywork-Reward-Llama-3.1-8B-v0.2",
|
||||||
|
# diffusion
|
||||||
"Qwen/Qwen-Image",
|
"Qwen/Qwen-Image",
|
||||||
"Qwen/Qwen-Image-Edit",
|
"Qwen/Qwen-Image-Edit",
|
||||||
"Skywork/Skywork-Reward-Llama-3.1-8B-v0.2",
|
"black-forest-labs/FLUX.1-dev",
|
||||||
"Wan-AI/Wan2.2-I2V-A14B-Diffusers",
|
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||||
|
"Wan-AI/Wan2.1-I2V-14B-480P-Diffusers",
|
||||||
|
"Wan-AI/Wan2.1-I2V-14B-720P-Diffusers",
|
||||||
"Wan-AI/Wan2.2-TI2V-5B-Diffusers",
|
"Wan-AI/Wan2.2-TI2V-5B-Diffusers",
|
||||||
|
"Wan-AI/Wan2.2-I2V-A14B-Diffusers",
|
||||||
],
|
],
|
||||||
"2-gpu-runner": [
|
"2-gpu-runner": [
|
||||||
"mistralai/Mixtral-8x7B-Instruct-v0.1",
|
"mistralai/Mixtral-8x7B-Instruct-v0.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user