[diffusion] feat: add maybe_record_function profiler spans for request phases (#35922)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
2bb25dc18b
commit
14444c6a04
@@ -44,6 +44,7 @@ from sglang.multimodal_gen.configs.sample.sampling_params import (
|
|||||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
||||||
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 CYAN, RESET, init_logger
|
from sglang.multimodal_gen.runtime.utils.logging_utils import CYAN, RESET, init_logger
|
||||||
|
from sglang.multimodal_gen.runtime.utils.profiler import maybe_record_function
|
||||||
from sglang.srt.observability.trace import TraceReqContext
|
from sglang.srt.observability.trace import TraceReqContext
|
||||||
|
|
||||||
logger = init_logger(__name__)
|
logger = init_logger(__name__)
|
||||||
@@ -610,21 +611,29 @@ def _try_save_cuda_video_direct(
|
|||||||
assert buffer.tensor is not None
|
assert buffer.tensor is not None
|
||||||
for start in range(0, num_frames, chunk_frames):
|
for start in range(0, num_frames, chunk_frames):
|
||||||
end = min(start + chunk_frames, num_frames)
|
end = min(start + chunk_frames, num_frames)
|
||||||
frames = (
|
with maybe_record_function(
|
||||||
(video[:, start:end] * 255).clamp_(0, 255).to(torch.uint8)
|
f"VIDEO_CHUNK frames {start}-{end} convert+pipe_to_x264"
|
||||||
)
|
):
|
||||||
frames = frames.permute(1, 2, 3, 0).contiguous()
|
frames = (
|
||||||
buffer.tensor[: end - start].copy_(frames, non_blocking=True)
|
(video[:, start:end] * 255)
|
||||||
torch.cuda.current_stream(video.device).synchronize()
|
.clamp_(0, 255)
|
||||||
del frames
|
.to(torch.uint8)
|
||||||
_sendfile_all(
|
)
|
||||||
process.stdin.fileno(),
|
frames = frames.permute(1, 2, 3, 0).contiguous()
|
||||||
buffer.fd,
|
buffer.tensor[: end - start].copy_(
|
||||||
(end - start) * height * width * 3,
|
frames, non_blocking=True
|
||||||
)
|
)
|
||||||
process.stdin.close()
|
torch.cuda.current_stream(video.device).synchronize()
|
||||||
process.stdin = None
|
del frames
|
||||||
returncode = process.wait()
|
_sendfile_all(
|
||||||
|
process.stdin.fileno(),
|
||||||
|
buffer.fd,
|
||||||
|
(end - start) * height * width * 3,
|
||||||
|
)
|
||||||
|
with maybe_record_function("FFMPEG_FLUSH stdin_close+wait"):
|
||||||
|
process.stdin.close()
|
||||||
|
process.stdin = None
|
||||||
|
returncode = process.wait()
|
||||||
finally:
|
finally:
|
||||||
if process.stdin is not None:
|
if process.stdin is not None:
|
||||||
process.stdin.close()
|
process.stdin.close()
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ from sglang.multimodal_gen.runtime.utils.perf_logger import (
|
|||||||
PerformanceLogger,
|
PerformanceLogger,
|
||||||
capture_memory_snapshot,
|
capture_memory_snapshot,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.utils.profiler import maybe_record_function
|
||||||
from sglang.multimodal_gen.runtime.utils.realtime_video import (
|
from sglang.multimodal_gen.runtime.utils.realtime_video import (
|
||||||
RAW_RGB_CONTENT_TYPE,
|
RAW_RGB_CONTENT_TYPE,
|
||||||
build_raw_rgb_frame_batches,
|
build_raw_rgb_frame_batches,
|
||||||
@@ -550,7 +551,9 @@ class GPUWorker(GPUWorkerPostTrainingMixin):
|
|||||||
for metrics in output_metrics:
|
for metrics in output_metrics:
|
||||||
metrics.total_duration_ms = duration_ms
|
metrics.total_duration_ms = duration_ms
|
||||||
|
|
||||||
self._materialize_output_transport(output_batch, req, save_output_paths)
|
req_label = req.request_id[:8] if req.request_id else "unnamed"
|
||||||
|
with maybe_record_function(f"SAVE_OUTPUTS {req_label}"):
|
||||||
|
self._materialize_output_transport(output_batch, req, save_output_paths)
|
||||||
self._record_output_peak_memory(output_batch)
|
self._record_output_peak_memory(output_batch)
|
||||||
|
|
||||||
collect_perf = (
|
collect_perf = (
|
||||||
@@ -572,7 +575,8 @@ class GPUWorker(GPUWorkerPostTrainingMixin):
|
|||||||
and output_batch.output is None
|
and output_batch.output is None
|
||||||
and not req.return_raw_frames
|
and not req.return_raw_frames
|
||||||
):
|
):
|
||||||
torch.get_device_module().empty_cache()
|
with maybe_record_function("EMPTY_CACHE"):
|
||||||
|
torch.get_device_module().empty_cache()
|
||||||
|
|
||||||
if req.perf_dump_path is not None or envs.SGLANG_DIFFUSION_STAGE_LOGGING:
|
if req.perf_dump_path is not None or envs.SGLANG_DIFFUSION_STAGE_LOGGING:
|
||||||
if not req.is_warmup:
|
if not req.is_warmup:
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ from sglang.multimodal_gen.runtime.server_warmup import (
|
|||||||
from sglang.multimodal_gen.runtime.utils.common import get_zmq_socket
|
from sglang.multimodal_gen.runtime.utils.common import get_zmq_socket
|
||||||
from sglang.multimodal_gen.runtime.utils.distributed import broadcast_pyobj
|
from sglang.multimodal_gen.runtime.utils.distributed import broadcast_pyobj
|
||||||
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.runtime.utils.profiler import maybe_record_function
|
||||||
from sglang.multimodal_gen.runtime.utils.trace_wrapper import DiffStage, trace_slice
|
from sglang.multimodal_gen.runtime.utils.trace_wrapper import DiffStage, trace_slice
|
||||||
|
|
||||||
logger = init_logger(__name__)
|
logger = init_logger(__name__)
|
||||||
@@ -724,25 +725,38 @@ class Scheduler(SchedulerWarmupMixin, SchedulerPostTrainingMixin, SchedulerDisag
|
|||||||
replies to client, only on rank 0
|
replies to client, only on rank 0
|
||||||
"""
|
"""
|
||||||
if not should_not_return and self.receiver is not None and identity is not None:
|
if not should_not_return and self.receiver is not None and identity is not None:
|
||||||
# if the server is local, use temp file to spill the frame array instead of
|
with maybe_record_function("REPLY spill+pickle+send"):
|
||||||
# leaving it in OutputBatch to be pickled later
|
# if the server is local, use temp file to spill the frame array
|
||||||
if is_local_endpoint(self.server_args.scheduler_endpoint):
|
# instead of leaving it in OutputBatch to be pickled later
|
||||||
|
if is_local_endpoint(self.server_args.scheduler_endpoint):
|
||||||
|
with self._record_return_stage(
|
||||||
|
output_batch, "Scheduler.return_result.spill_arrays"
|
||||||
|
):
|
||||||
|
output_batch.output = spill_large_arrays_to_file_refs(
|
||||||
|
output_batch.output
|
||||||
|
)
|
||||||
|
|
||||||
with self._record_return_stage(
|
with self._record_return_stage(
|
||||||
output_batch, "Scheduler.return_result.spill_arrays"
|
output_batch, "Scheduler.return_result.pickle"
|
||||||
):
|
):
|
||||||
output_batch.output = spill_large_arrays_to_file_refs(
|
payload = pickle.dumps(output_batch)
|
||||||
output_batch.output
|
|
||||||
)
|
|
||||||
|
|
||||||
with self._record_return_stage(
|
with self._record_return_stage(
|
||||||
output_batch, "Scheduler.return_result.pickle"
|
output_batch, "Scheduler.return_result.send"
|
||||||
):
|
):
|
||||||
payload = pickle.dumps(output_batch)
|
self.receiver.send_multipart([identity, b"", payload])
|
||||||
|
|
||||||
with self._record_return_stage(
|
@staticmethod
|
||||||
output_batch, "Scheduler.return_result.send"
|
def _req_label(items: list) -> str:
|
||||||
):
|
"""Short request tag for profiler span names."""
|
||||||
self.receiver.send_multipart([identity, b"", payload])
|
req = items[0][1] if items else None
|
||||||
|
if isinstance(req, list) and req:
|
||||||
|
req = req[0]
|
||||||
|
# request_id is Optional; server warmup and bare server-test
|
||||||
|
# requests arrive without one.
|
||||||
|
if isinstance(req, Req) and req.request_id:
|
||||||
|
return req.request_id[:8]
|
||||||
|
return type(req).__name__
|
||||||
|
|
||||||
def _return_item_result(
|
def _return_item_result(
|
||||||
self,
|
self,
|
||||||
@@ -1231,7 +1245,10 @@ class Scheduler(SchedulerWarmupMixin, SchedulerPostTrainingMixin, SchedulerDisag
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handler_result = self._dispatch_items(items)
|
with maybe_record_function(
|
||||||
|
f"REQ {self._req_label(items)} dispatch+forward"
|
||||||
|
):
|
||||||
|
handler_result = self._dispatch_items(items)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Error executing request in scheduler event loop: {e}",
|
f"Error executing request in scheduler event loop: {e}",
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ from sglang.multimodal_gen.runtime.server_args import ServerArgs
|
|||||||
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.runtime.utils.nvtx_pytorch_hooks import maybe_nvtx_range
|
from sglang.multimodal_gen.runtime.utils.nvtx_pytorch_hooks import maybe_nvtx_range
|
||||||
from sglang.multimodal_gen.runtime.utils.perf_logger import StageProfiler
|
from sglang.multimodal_gen.runtime.utils.perf_logger import StageProfiler
|
||||||
from sglang.multimodal_gen.runtime.utils.profiler import SGLDiffusionProfiler
|
from sglang.multimodal_gen.runtime.utils.profiler import (
|
||||||
|
SGLDiffusionProfiler,
|
||||||
|
maybe_record_function,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# Only for type checkers; avoids runtime circular import
|
# Only for type checkers; avoids runtime circular import
|
||||||
@@ -112,10 +115,11 @@ class PipelineExecutor(ABC):
|
|||||||
) -> Any:
|
) -> Any:
|
||||||
stage_name = stage._component_stage_name()
|
stage_name = stage._component_stage_name()
|
||||||
self.before_stage(stage, stage_index, payload, server_args)
|
self.before_stage(stage, stage_index, payload, server_args)
|
||||||
with maybe_nvtx_range(f"stage_{stage_name}", use_nvtx):
|
with maybe_record_function(f"STAGE {stage_name}"):
|
||||||
payload = self.run_stage_with_context(
|
with maybe_nvtx_range(f"stage_{stage_name}", use_nvtx):
|
||||||
stage, payload, server_args, run_stage
|
payload = self.run_stage_with_context(
|
||||||
)
|
stage, payload, server_args, run_stage
|
||||||
|
)
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import contextlib
|
||||||
import gzip
|
import gzip
|
||||||
import os
|
import os
|
||||||
|
from collections.abc import Iterator
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
@@ -19,6 +21,21 @@ if current_platform.is_npu():
|
|||||||
logger = init_logger(__name__)
|
logger = init_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def maybe_record_function(name: str, enabled: bool = True) -> Iterator[None]:
|
||||||
|
"""Named ``torch.profiler`` span, near-free when no profiler is active.
|
||||||
|
``enabled`` mirrors :func:`maybe_nvtx_range` for per-request gates such
|
||||||
|
as warmup exclusion.
|
||||||
|
"""
|
||||||
|
# Gate on the process-wide module flag; the thread-local _profiler_enabled()
|
||||||
|
# is False under profile_all_threads=True and on non-initiating threads.
|
||||||
|
if not enabled or not torch.autograd.profiler._is_profiler_enabled:
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
with torch.profiler.record_function(name):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
def _resolve_profiler_log_dir(log_dir: str | None) -> str:
|
def _resolve_profiler_log_dir(log_dir: str | None) -> str:
|
||||||
if log_dir is not None:
|
if log_dir is not None:
|
||||||
return log_dir
|
return log_dir
|
||||||
|
|||||||
Reference in New Issue
Block a user