[diffusion] log: unify generation performance logging (#14117)

This commit is contained in:
Mick
2025-11-29 12:21:59 +08:00
committed by GitHub
parent 143b57b805
commit 0a362d653f
3 changed files with 128 additions and 73 deletions
@@ -34,6 +34,8 @@ from sglang.multimodal_gen.runtime.server_args import PortArgs, ServerArgs
from sglang.multimodal_gen.runtime.sync_scheduler_client import sync_scheduler_client from sglang.multimodal_gen.runtime.sync_scheduler_client import sync_scheduler_client
from sglang.multimodal_gen.runtime.utils.logging_utils import ( from sglang.multimodal_gen.runtime.utils.logging_utils import (
init_logger, init_logger,
log_batch_completion,
log_generation_timer,
suppress_loggers, suppress_loggers,
suppress_other_loggers, suppress_other_loggers,
) )
@@ -287,73 +289,54 @@ class DiffGenerator:
# 2. send requests to scheduler, one at a time # 2. send requests to scheduler, one at a time
# TODO: send batch when supported # TODO: send batch when supported
for request_idx, req in enumerate(requests): for request_idx, req in enumerate(requests):
logger.info(
"Processing prompt %d/%d: %s",
request_idx + 1,
len(requests),
req.prompt[:100],
)
try: try:
start_time = time.perf_counter() with log_generation_timer(
output_batch = self._send_to_scheduler_and_wait_for_response([req]) logger, req.prompt, request_idx + 1, len(requests)
gen_time = time.perf_counter() - start_time ) as timer:
if output_batch.error: output_batch = self._send_to_scheduler_and_wait_for_response([req])
raise Exception(f"{output_batch.error}") if output_batch.error:
raise Exception(f"{output_batch.error}")
# FIXME: in generate mode, an internal assertion error won't raise an error if output_batch.output is None:
logger.info( logger.error(
"Pixel data generated successfully in %.2f seconds", "Received empty output from scheduler for prompt %d",
gen_time, request_idx + 1,
) )
continue
for output_idx, sample in enumerate(output_batch.output):
num_outputs = len(output_batch.output)
frames = self.post_process_sample(
sample,
fps=req.fps,
save_output=req.save_output,
save_file_path=req.output_file_path(
num_outputs, output_idx
),
data_type=req.data_type,
)
if output_batch.output is None: result_item: dict[str, Any] = {
logger.error( "samples": sample,
"Received empty output from scheduler for prompt %d", "frames": frames,
request_idx + 1, "prompts": req.prompt,
) "size": (req.height, req.width, req.num_frames),
continue "generation_time": timer.duration,
for output_idx, sample in enumerate(output_batch.output): "timings": (
num_outputs = len(output_batch.output) output_batch.timings.to_dict()
frames = self.post_process_sample( if output_batch.timings
sample, else {}
fps=req.fps, ),
save_output=req.save_output, "trajectory": output_batch.trajectory_latents,
save_file_path=req.output_file_path(num_outputs, output_idx), "trajectory_timesteps": output_batch.trajectory_timesteps,
data_type=req.data_type, "trajectory_decoded": output_batch.trajectory_decoded,
) "prompt_index": output_idx,
}
result_item: dict[str, Any] = { results.append(result_item)
"samples": sample, except Exception:
"frames": frames,
"prompts": req.prompt,
"size": (req.height, req.width, req.num_frames),
"generation_time": gen_time,
"timings": (
output_batch.timings.to_dict()
if output_batch.timings
else {}
),
"trajectory": output_batch.trajectory_latents,
"trajectory_timesteps": output_batch.trajectory_timesteps,
"trajectory_decoded": output_batch.trajectory_decoded,
"prompt_index": output_idx,
}
results.append(result_item)
except Exception as e:
logger.error(
"Failed to generate output for prompt %d: %s",
request_idx + 1,
e,
exc_info=True,
)
continue continue
total_gen_time = time.perf_counter() - total_start_time total_gen_time = time.perf_counter() - total_start_time
logger.info( log_batch_completion(logger, len(results), total_gen_time)
"Completed batch processing. Generated %d outputs in %.2f seconds.",
len(results),
total_gen_time,
)
if len(results) == 0: if len(results) == 0:
return None return None
@@ -1,6 +1,7 @@
# Copied and adapted from: https://github.com/hao-ai-lab/FastVideo # Copied and adapted from: https://github.com/hao-ai-lab/FastVideo
import dataclasses import dataclasses
import os import os
import time
from typing import Optional from typing import Optional
import imageio import imageio
@@ -11,7 +12,11 @@ from einops import rearrange
from fastapi import UploadFile from fastapi import UploadFile
from sglang.multimodal_gen.configs.sample.base import DataType from sglang.multimodal_gen.configs.sample.base import DataType
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger from sglang.multimodal_gen.runtime.utils.logging_utils import (
init_logger,
log_batch_completion,
log_generation_timer,
)
logger = init_logger(__name__) logger = init_logger(__name__)
@@ -98,16 +103,23 @@ async def process_generation_batch(
scheduler_client, scheduler_client,
batch, batch,
): ):
result = await scheduler_client.forward([batch]) total_start_time = time.perf_counter()
if result.output is None: with log_generation_timer(logger, batch.prompt):
raise RuntimeError("Model generation returned no output.") result = await scheduler_client.forward([batch])
if result.output is None:
raise RuntimeError("Model generation returned no output.")
save_file_path = str(os.path.join(batch.output_path, batch.output_file_name))
post_process_sample(
result.output[0],
batch.data_type,
batch.fps,
batch.save_output,
save_file_path,
)
total_time = time.perf_counter() - total_start_time
log_batch_completion(logger, 1, total_time)
save_file_path = str(os.path.join(batch.output_path, batch.output_file_name))
post_process_sample(
result.output[0],
batch.data_type,
batch.fps,
batch.save_output,
save_file_path,
)
return save_file_path return save_file_path
@@ -8,6 +8,7 @@ import datetime
import logging import logging
import os import os
import sys import sys
import time
import warnings import warnings
from contextlib import contextmanager from contextlib import contextmanager
from functools import lru_cache, partial from functools import lru_cache, partial
@@ -420,3 +421,62 @@ def suppress_other_loggers(not_suppress_on_main_rank: bool = False):
if should_suppress: if should_suppress:
for logger_name, level in original_levels.items(): for logger_name, level in original_levels.items():
logging.getLogger(logger_name).setLevel(level) logging.getLogger(logger_name).setLevel(level)
class GenerationTimer:
def __init__(self):
self.start_time = 0.0
self.end_time = 0.0
self.duration = 0.0
@contextmanager
def log_generation_timer(
logger: logging.Logger,
prompt: str,
request_idx: int | None = None,
total_requests: int | None = None,
):
if request_idx is not None and total_requests is not None:
logger.info(
"Processing prompt %d/%d: %s",
request_idx,
total_requests,
prompt[:100],
)
else:
max_len = 100
suffix = "..." if len(prompt) > max_len else ""
logger.info(f"Processing prompt: {prompt[:100]}{suffix}")
timer = GenerationTimer()
timer.start_time = time.perf_counter()
try:
yield timer
timer.end_time = time.perf_counter()
timer.duration = timer.end_time - timer.start_time
logger.info("Pixel data generated successfully in %.2f seconds", timer.duration)
except Exception as e:
if request_idx is not None:
logger.error(
"Failed to generate output for prompt %d: %s",
request_idx,
e,
exc_info=True,
)
else:
logger.error(
f"Failed to generate output for prompt: {e}",
exc_info=True,
)
raise
def log_batch_completion(
logger: logging.Logger, num_outputs: int, total_time: float
) -> None:
logger.info(
"Completed batch processing. Generated %d outputs in %.2f seconds.",
num_outputs,
total_time,
)