diff --git a/python/sglang/multimodal_gen/runtime/pipelines_core/schedule_batch.py b/python/sglang/multimodal_gen/runtime/pipelines_core/schedule_batch.py index 9118770a5..160c2b5e6 100644 --- a/python/sglang/multimodal_gen/runtime/pipelines_core/schedule_batch.py +++ b/python/sglang/multimodal_gen/runtime/pipelines_core/schedule_batch.py @@ -11,6 +11,7 @@ in a functional manner, reducing the need for explicit parameter passing. from __future__ import annotations +import logging import os import pprint from copy import deepcopy @@ -21,11 +22,11 @@ import PIL.Image import torch from sglang.multimodal_gen.configs.sample.sampling_params import SamplingParams -from sglang.multimodal_gen.runtime.server_args import ( - ServerArgs, +from sglang.multimodal_gen.runtime.server_args import ServerArgs +from sglang.multimodal_gen.runtime.utils.logging_utils import ( _sanitize_for_logging, + init_logger, ) -from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger from sglang.multimodal_gen.runtime.utils.perf_logger import RequestMetrics from sglang.multimodal_gen.utils import align_to @@ -288,20 +289,22 @@ class Req: else: target_width = -1 - # sanitize prompts for info-level logging - sanitized_prompt = _sanitize_for_logging(self.prompt, key_hint="prompt") - sanitized_neg_prompt = _sanitize_for_logging( - self.negative_prompt, key_hint="negative_prompt" - ) + if logger.isEnabledFor(logging.DEBUG): + display_prompt = self.prompt + display_neg_prompt = self.negative_prompt + else: + display_prompt = _sanitize_for_logging(self.prompt, key_hint="prompt") + display_neg_prompt = _sanitize_for_logging( + self.negative_prompt, key_hint="negative_prompt" + ) - # Log sampling parameters debug_str = f"""Sampling params: width: {target_width} height: {target_height} num_frames: {self.num_frames} fps: {self.fps} - prompt: {sanitized_prompt} - neg_prompt: {sanitized_neg_prompt} + prompt: {display_prompt} + neg_prompt: {display_neg_prompt} seed: {self.seed} infer_steps: {self.num_inference_steps} num_outputs_per_prompt: {self.num_outputs_per_prompt} @@ -313,7 +316,7 @@ class Req: save_output: {self.save_output} output_file_path: {self.output_file_path()} """ # type: ignore[attr-defined] - logger.debug(debug_str) + logger.info(debug_str) @dataclass diff --git a/python/sglang/multimodal_gen/runtime/server_args.py b/python/sglang/multimodal_gen/runtime/server_args.py index 246aa3ec3..e330fdb60 100644 --- a/python/sglang/multimodal_gen/runtime/server_args.py +++ b/python/sglang/multimodal_gen/runtime/server_args.py @@ -6,7 +6,6 @@ import argparse import dataclasses -import inspect import json import math import os @@ -37,6 +36,7 @@ from sglang.multimodal_gen.runtime.utils.common import ( is_valid_ipv6_address, ) from sglang.multimodal_gen.runtime.utils.logging_utils import ( + _sanitize_for_logging, configure_logger, init_logger, ) @@ -49,119 +49,6 @@ from sglang.multimodal_gen.utils import ( logger = init_logger(__name__) -def _is_torch_tensor(obj: Any) -> tuple[bool, Any]: - """Return (is_tensor, torch_module_or_None) without importing torch at module import time.""" - try: - import torch # type: ignore - - return isinstance(obj, torch.Tensor), torch - except Exception: - return False, None - - -def _sanitize_for_logging(obj: Any, key_hint: str | None = None) -> Any: - """Recursively convert objects to JSON-serializable forms for concise logging. - - Rules: - - Drop any field/dict key named 'param_names_mapping'. - - Render Enums using their value. - - Render torch.Tensor as a compact summary; if key name is 'scaling_factor', include stats. - - Dataclasses are expanded to dicts and sanitized recursively. - - Callables/functions are rendered as their qualified name. - - Redact sensitive fields like 'prompt' and 'negative_prompt' (only show length). - - Fallback to str(...) for unknown types. - """ - # Handle simple types quickly - if obj is None or isinstance(obj, (str, int, float, bool)): - # redact sensitive prompt fields - if key_hint in ("prompt", "negative_prompt"): - if isinstance(obj, str): - return f"" - return obj - - # Enum -> value for readability - if isinstance(obj, Enum): - return obj.value - - # torch.Tensor handling (lazy import) - is_tensor, torch_mod = _is_torch_tensor(obj) - if is_tensor: - try: - ten = obj.detach().cpu() - if key_hint == "scaling_factor": - # Provide a compact, single-line summary for scaling_factor - stats = { - "shape": list(ten.shape), - "dtype": str(ten.dtype), - } - # Stats might fail for some dtypes; guard individually - try: - stats["min"] = float(ten.min().item()) - except Exception: - pass - try: - stats["max"] = float(ten.max().item()) - except Exception: - pass - try: - stats["mean"] = float(ten.float().mean().item()) - except Exception: - pass - return {"tensor": "scaling_factor", **stats} - # Generic tensor summary - return {"tensor": True, "shape": list(ten.shape), "dtype": str(ten.dtype)} - except Exception: - return "" - - # Dataclasses -> dict - if dataclasses.is_dataclass(obj): - result: dict[str, Any] = {} - for f in dataclasses.fields(obj): - if not f.repr: - continue - name = f.name - if "names_mapping" in name: # drop noisy mappings - continue - try: - value = getattr(obj, name) - except Exception: - continue - result[name] = _sanitize_for_logging(value, key_hint=name) - return result - - # Dicts -> sanitize keys/values; drop 'param_names_mapping' - if isinstance(obj, dict): - result_dict: dict[str, Any] = {} - for k, v in obj.items(): - try: - key_str = str(k) - except Exception: - key_str = "" - if key_str == "param_names_mapping": - continue - result_dict[key_str] = _sanitize_for_logging(v, key_hint=key_str) - return result_dict - - # Sequences/Sets -> list - if isinstance(obj, (list, tuple, set)): - return [_sanitize_for_logging(x, key_hint=key_hint) for x in obj] - - # Functions / Callables -> qualified name - try: - if inspect.isroutine(obj) or inspect.isclass(obj): - module = getattr(obj, "__module__", "") - qn = getattr(obj, "__qualname__", getattr(obj, "__name__", "")) - return f"{module}.{qn}" if module else qn - except Exception: - pass - - # Fallback: string representation - try: - return str(obj) - except Exception: - return "" - - class Backend(str, Enum): """ Enumeration for different model backends. diff --git a/python/sglang/multimodal_gen/runtime/utils/logging_utils.py b/python/sglang/multimodal_gen/runtime/utils/logging_utils.py index 2b94bfe8b..6214bacd9 100644 --- a/python/sglang/multimodal_gen/runtime/utils/logging_utils.py +++ b/python/sglang/multimodal_gen/runtime/utils/logging_utils.py @@ -6,12 +6,15 @@ import argparse import contextlib +import dataclasses import datetime +import inspect import logging import os import sys import time from contextlib import contextmanager +from enum import Enum from functools import lru_cache, partial from logging import Logger from types import MethodType @@ -281,6 +284,107 @@ def init_logger(name: str) -> _SGLDiffusionLogger: logger = init_logger(__name__) +def _is_torch_tensor(obj: Any) -> tuple[bool, Any]: + """Return (is_tensor, torch_module_or_None) without importing torch at module import time.""" + try: + import torch # type: ignore + + return isinstance(obj, torch.Tensor), torch + except Exception: + return False, None + + +def _sanitize_for_logging(obj: Any, key_hint: str | None = None) -> Any: + """Recursively convert objects to JSON-serializable forms for concise logging. + + Rules: + - Drop any field/dict key named 'param_names_mapping'. + - Render Enums using their value. + - Render torch.Tensor as a compact summary; if key name is 'scaling_factor', include stats. + - Dataclasses are expanded to dicts and sanitized recursively. + - Callables/functions are rendered as their qualified name. + - Redact sensitive fields like 'prompt' and 'negative_prompt' (only show length). + - Fallback to str(...) for unknown types. + """ + if obj is None or isinstance(obj, (str, int, float, bool)): + if key_hint in ("prompt", "negative_prompt"): + if isinstance(obj, str): + return f"" + return obj + + if isinstance(obj, Enum): + return obj.value + + is_tensor, torch_mod = _is_torch_tensor(obj) + if is_tensor: + try: + ten = obj.detach().cpu() + if key_hint == "scaling_factor": + stats = { + "shape": list(ten.shape), + "dtype": str(ten.dtype), + } + try: + stats["min"] = float(ten.min().item()) + except Exception: + pass + try: + stats["max"] = float(ten.max().item()) + except Exception: + pass + try: + stats["mean"] = float(ten.float().mean().item()) + except Exception: + pass + return {"tensor": "scaling_factor", **stats} + return {"tensor": True, "shape": list(ten.shape), "dtype": str(ten.dtype)} + except Exception: + return "" + + if dataclasses.is_dataclass(obj): + result: dict[str, Any] = {} + for f in dataclasses.fields(obj): + if not f.repr: + continue + name = f.name + if "names_mapping" in name: + continue + try: + value = getattr(obj, name) + except Exception: + continue + result[name] = _sanitize_for_logging(value, key_hint=name) + return result + + if isinstance(obj, dict): + result_dict: dict[str, Any] = {} + for k, v in obj.items(): + try: + key_str = str(k) + except Exception: + key_str = "" + if key_str == "param_names_mapping": + continue + result_dict[key_str] = _sanitize_for_logging(v, key_hint=key_str) + return result_dict + + if isinstance(obj, (list, tuple, set)): + return [_sanitize_for_logging(x, key_hint=key_hint) for x in obj] + + try: + if inspect.isroutine(obj) or inspect.isclass(obj): + module = getattr(obj, "__module__", "") + qn = getattr(obj, "__qualname__", getattr(obj, "__name__", "")) + return f"{module}.{qn}" if module else qn + except Exception: + pass + + try: + return str(obj) + except Exception: + return "" + + def _trace_calls(log_path, root_dir, frame, event, arg=None): if event in ["call", "return"]: # Extract the filename, line number, function name, and the code object @@ -403,6 +507,8 @@ def globally_suppress_loggers(): "python_multipart.multipart", "filelock", "urllib3", + "httpx", + "httpcore", ] for name in target_names: @@ -457,7 +563,7 @@ def log_generation_timer( "Processing prompt %d/%d: %s", request_idx, total_requests, - prompt[:100], + _sanitize_for_logging(prompt, key_hint="prompt"), ) timer = GenerationTimer()