feat: add NVTX markers for the scheduler main loop (#27901)

This commit is contained in:
JoyFuture
2026-06-13 17:16:53 -07:00
committed by GitHub
parent 93b402580c
commit a3fd5c24be
6 changed files with 110 additions and 0 deletions
@@ -85,6 +85,7 @@ from sglang.srt.observability.req_time_stats import (
)
from sglang.srt.utils import get_num_new_pages
from sglang.srt.utils.network import NetworkAddress
from sglang.srt.utils.nvtx_utils import nvtx_annotated_method
from sglang.srt.utils.torch_memory_saver_adapter import TorchMemorySaverAdapter
logger = logging.getLogger(__name__)
@@ -1824,6 +1825,7 @@ class SchedulerDisaggregationDecodeMixin:
return GenerationBatchResult()
@nvtx_annotated_method("scheduler.get_next_batch_to_run")
def get_next_disagg_decode_batch_to_run(
self: Scheduler,
) -> Optional[ScheduleBatch]:
@@ -61,6 +61,7 @@ from sglang.srt.mem_cache.common import (
)
from sglang.srt.mem_cache.deepseek_v4_memory_pool import DeepSeekV4TokenToKVPool
from sglang.srt.observability.req_time_stats import set_schedule_time_batch
from sglang.srt.utils.nvtx_utils import nvtx_annotated_method
if TYPE_CHECKING:
from torch.distributed import ProcessGroup
@@ -405,6 +406,7 @@ class SchedulerDisaggregationPrefillMixin:
if room is not None and room in kv_mgr.transfer_infos:
prefetch(room)
@nvtx_annotated_method("scheduler.get_next_batch_to_run")
def get_next_disagg_prefill_batch_to_run(
self: Scheduler,
) -> Optional[ScheduleBatch]:
+1
View File
@@ -243,6 +243,7 @@ class Envs:
SGLANG_PROFILE_WITH_STACK = EnvBool(True)
SGLANG_PROFILE_RECORD_SHAPES = EnvBool(True)
SGLANG_PROFILE_V2 = EnvBool(False)
SGLANG_ENABLE_NVTX = EnvBool(False)
SGLANG_RECORD_STEP_TIME = EnvBool(False)
SGLANG_FORCE_SHUTDOWN = EnvBool(False)
SGLANG_DEBUG_MEMORY_POOL = EnvBool(False)
+5
View File
@@ -263,6 +263,7 @@ from sglang.srt.utils.hf_transformers_utils import (
get_tokenizer_from_processor,
)
from sglang.srt.utils.numa_utils import get_numa_node_if_available, numa_bind_to_node
from sglang.srt.utils.nvtx_utils import nvtx_annotated_method
from sglang.srt.utils.tensor_bridge import use_mlx
from sglang.srt.utils.torch_memory_saver_adapter import TorchMemorySaverAdapter
from sglang.utils import TypeBasedDispatcher, get_exception_traceback
@@ -1585,6 +1586,7 @@ class Scheduler(
return disable_overlap_for_batch or need_grammar_sync
@nvtx_annotated_method("scheduler.process_input_requests")
def process_input_requests(self, recv_reqs: List):
now = time.monotonic()
self.session_controller.maybe_reap(now)
@@ -2461,6 +2463,7 @@ class Scheduler(
# todo hisparse, maybe other info to contain for the new batch
return batch
@nvtx_annotated_method("scheduler.get_next_batch_to_run")
def get_next_batch_to_run(self) -> Optional[ScheduleBatch]:
if self.enable_fpm:
self._fpm_batch_t0 = time.monotonic()
@@ -3048,6 +3051,7 @@ class Scheduler(
else:
batch.sampling_info = sched_sampling_info
@nvtx_annotated_method("scheduler.run_batch")
def run_batch(
self,
batch: ScheduleBatch,
@@ -3269,6 +3273,7 @@ class Scheduler(
if batch_result.logits_output is not None:
batch_result.logits_output.next_token_logits = None
@nvtx_annotated_method("scheduler.process_batch_result")
def process_batch_result(
self,
batch: ScheduleBatch,
@@ -29,6 +29,7 @@ from sglang.srt.utils import (
broadcast_pyobj,
point_to_point_pyobj,
)
from sglang.srt.utils.nvtx_utils import nvtx_annotated_method
if TYPE_CHECKING:
from sglang.srt.configs.model_config import ModelConfig
@@ -67,6 +68,7 @@ class SchedulerRequestReceiver:
return False
return num_recv_reqs >= self.max_recv_per_poll
@nvtx_annotated_method("scheduler.recv_requests")
def recv_requests(
self,
) -> List[Union[TokenizedGenerateReqInput, TokenizedEmbeddingReqInput, Any]]:
+98
View File
@@ -0,0 +1,98 @@
# Copyright 2023-2024 SGLang Team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Lightweight NVTX annotations for the scheduler main loop.
Enabled via the ``SGLANG_ENABLE_NVTX`` environment variable (off by default).
When disabled, the decorator/context manager add zero runtime overhead so they
are safe to leave on hot scheduler paths.
"""
import logging
from contextlib import contextmanager, nullcontext
from functools import wraps
import torch
from sglang.srt.environ import envs
logger = logging.getLogger(__name__)
_NVTX_ENV_ENABLED = envs.SGLANG_ENABLE_NVTX.get()
_nvtx_module = None
if _NVTX_ENV_ENABLED:
try:
import nvtx as _nvtx_module # type: ignore
except ImportError:
logger.warning(
"SGLANG_ENABLE_NVTX is set, but the `nvtx` package is missing. "
"NVTX annotations are disabled."
)
NVTX_ENABLED = _nvtx_module is not None
# Colors are assigned per scheduler main-loop stage so the markers are easy to
# distinguish in Nsight Systems.
_NVTX_COLOR_MAP = {
# === Scheduler main loop (pipeline order) ===
"scheduler.recv_requests": "blue",
"scheduler.process_input_requests": "purple",
"scheduler.get_next_batch_to_run": "green",
"scheduler.run_batch": "red",
"scheduler.process_batch_result": "cyan",
}
@contextmanager
def _nvtx_range_enabled(debug_name: str):
color = _NVTX_COLOR_MAP.get(debug_name)
# record_function carries a non-trivial (~microseconds) cost per call even
# when no PyTorch profiler is collecting, so only pay it when one is active
# (e.g. Chrome-trace export). For Nsight-only runs the nvtx.annotate marker
# alone is enough.
if torch.autograd._profiler_enabled():
with torch.autograd.profiler.record_function(debug_name):
with _nvtx_module.annotate(debug_name, color=color):
yield
else:
with _nvtx_module.annotate(debug_name, color=color):
yield
if NVTX_ENABLED:
nvtx_range = _nvtx_range_enabled
else:
# When NVTX is disabled, hand back a shared no-op context manager so hot
# paths using `with nvtx_range(...)` pay no per-call generator overhead.
_NULL_CONTEXT = nullcontext()
def nvtx_range(debug_name: str):
return _NULL_CONTEXT
def nvtx_annotated_method(debug_name: str):
# Decide at decoration time. When NVTX is disabled this returns the
# original function untouched, so decorated methods on hot paths have zero
# runtime cost.
if not NVTX_ENABLED:
return lambda func: func
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
with nvtx_range(debug_name):
return func(*args, **kwargs)
return wrapper
return decorator