[diffusion] UX: suppress excessive logging from httpx and httpcore (#20452)

This commit is contained in:
Mick
2026-03-13 14:43:09 +08:00
committed by GitHub
parent 9c8777c80f
commit b638b25b22
3 changed files with 123 additions and 127 deletions
@@ -11,6 +11,7 @@ in a functional manner, reducing the need for explicit parameter passing.
from __future__ import annotations from __future__ import annotations
import logging
import os import os
import pprint import pprint
from copy import deepcopy from copy import deepcopy
@@ -21,11 +22,11 @@ import PIL.Image
import torch import torch
from sglang.multimodal_gen.configs.sample.sampling_params import SamplingParams from sglang.multimodal_gen.configs.sample.sampling_params import SamplingParams
from sglang.multimodal_gen.runtime.server_args import ( from sglang.multimodal_gen.runtime.server_args import ServerArgs
ServerArgs, from sglang.multimodal_gen.runtime.utils.logging_utils import (
_sanitize_for_logging, _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.runtime.utils.perf_logger import RequestMetrics
from sglang.multimodal_gen.utils import align_to from sglang.multimodal_gen.utils import align_to
@@ -288,20 +289,22 @@ class Req:
else: else:
target_width = -1 target_width = -1
# sanitize prompts for info-level logging if logger.isEnabledFor(logging.DEBUG):
sanitized_prompt = _sanitize_for_logging(self.prompt, key_hint="prompt") display_prompt = self.prompt
sanitized_neg_prompt = _sanitize_for_logging( display_neg_prompt = self.negative_prompt
self.negative_prompt, key_hint="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: debug_str = f"""Sampling params:
width: {target_width} width: {target_width}
height: {target_height} height: {target_height}
num_frames: {self.num_frames} num_frames: {self.num_frames}
fps: {self.fps} fps: {self.fps}
prompt: {sanitized_prompt} prompt: {display_prompt}
neg_prompt: {sanitized_neg_prompt} neg_prompt: {display_neg_prompt}
seed: {self.seed} seed: {self.seed}
infer_steps: {self.num_inference_steps} infer_steps: {self.num_inference_steps}
num_outputs_per_prompt: {self.num_outputs_per_prompt} num_outputs_per_prompt: {self.num_outputs_per_prompt}
@@ -313,7 +316,7 @@ class Req:
save_output: {self.save_output} save_output: {self.save_output}
output_file_path: {self.output_file_path()} output_file_path: {self.output_file_path()}
""" # type: ignore[attr-defined] """ # type: ignore[attr-defined]
logger.debug(debug_str) logger.info(debug_str)
@dataclass @dataclass
@@ -6,7 +6,6 @@
import argparse import argparse
import dataclasses import dataclasses
import inspect
import json import json
import math import math
import os import os
@@ -37,6 +36,7 @@ from sglang.multimodal_gen.runtime.utils.common import (
is_valid_ipv6_address, is_valid_ipv6_address,
) )
from sglang.multimodal_gen.runtime.utils.logging_utils import ( from sglang.multimodal_gen.runtime.utils.logging_utils import (
_sanitize_for_logging,
configure_logger, configure_logger,
init_logger, init_logger,
) )
@@ -49,119 +49,6 @@ from sglang.multimodal_gen.utils import (
logger = init_logger(__name__) 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"<redacted, len={len(obj)}>"
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 "<tensor>"
# 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 = "<key>"
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__", "<callable>"))
return f"{module}.{qn}" if module else qn
except Exception:
pass
# Fallback: string representation
try:
return str(obj)
except Exception:
return "<unserializable>"
class Backend(str, Enum): class Backend(str, Enum):
""" """
Enumeration for different model backends. Enumeration for different model backends.
@@ -6,12 +6,15 @@
import argparse import argparse
import contextlib import contextlib
import dataclasses
import datetime import datetime
import inspect
import logging import logging
import os import os
import sys import sys
import time import time
from contextlib import contextmanager from contextlib import contextmanager
from enum import Enum
from functools import lru_cache, partial from functools import lru_cache, partial
from logging import Logger from logging import Logger
from types import MethodType from types import MethodType
@@ -281,6 +284,107 @@ def init_logger(name: str) -> _SGLDiffusionLogger:
logger = init_logger(__name__) 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"<redacted, len={len(obj)}>"
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 "<tensor>"
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 = "<key>"
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__", "<callable>"))
return f"{module}.{qn}" if module else qn
except Exception:
pass
try:
return str(obj)
except Exception:
return "<unserializable>"
def _trace_calls(log_path, root_dir, frame, event, arg=None): def _trace_calls(log_path, root_dir, frame, event, arg=None):
if event in ["call", "return"]: if event in ["call", "return"]:
# Extract the filename, line number, function name, and the code object # Extract the filename, line number, function name, and the code object
@@ -403,6 +507,8 @@ def globally_suppress_loggers():
"python_multipart.multipart", "python_multipart.multipart",
"filelock", "filelock",
"urllib3", "urllib3",
"httpx",
"httpcore",
] ]
for name in target_names: for name in target_names:
@@ -457,7 +563,7 @@ def log_generation_timer(
"Processing prompt %d/%d: %s", "Processing prompt %d/%d: %s",
request_idx, request_idx,
total_requests, total_requests,
prompt[:100], _sanitize_for_logging(prompt, key_hint="prompt"),
) )
timer = GenerationTimer() timer = GenerationTimer()