A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 8, 08:45 UTC
[Core] Profiler improvements and lazy initialization (#33198)
Signed-off-by: Jaewon Lee <jaewon@meta.com> Co-authored-by: Lu Fang <30275821+houseroad@users.noreply.github.com>
Jaewon committed
Feb 12, 2026 at 16:16 UTC
4453ba8d9ec8e35d68084a118f35ce5c48b5dae6
10 files changed
+117
-37
vllm/distributed/utils.py
+40
@@ -524,3 +524,43 @@ def stateless_destroy_torch_distributed_process_group(pg: ProcessGroup) -> None:
524
"""
525
pg.shutdown()
526
_unregister_process_group(pg.group_name)
527
+
528
+
529
+def get_worker_rank_suffix(global_rank: int | None = None) -> str:
530
+ """Generate a descriptive rank suffix for worker identification.
531
+
532
+ Returns a string like 'dp0_pp0_tp0_dcp0_ep0_rank0' including all
533
+ parallel dimensions: DP, PP, TP, DCP, EP.
534
+
535
+ Args:
536
+ global_rank: Optional global rank to append. If not provided,
537
+ only parallel dimension ranks are included.
538
+
539
+ Returns:
540
+ A string suffix identifying the worker's position in the
541
+ distributed topology.
542
+ """
543
+ from vllm.distributed.parallel_state import (
544
+ get_dcp_group,
545
+ get_dp_group,
546
+ get_ep_group,
547
+ get_pp_group,
548
+ get_tp_group,
549
+ )
550
+
551
+ try:
552
+ dp_rank = get_dp_group().rank_in_group
553
+ pp_rank = get_pp_group().rank_in_group
554
+ tp_rank = get_tp_group().rank_in_group
555
+ dcp_rank = get_dcp_group().rank_in_group
556
+ ep_rank = get_ep_group().rank_in_group
557
+
558
+ suffix = f"dp{dp_rank}_pp{pp_rank}_tp{tp_rank}_dcp{dcp_rank}_ep{ep_rank}"
559
+ if global_rank is not None:
560
+ suffix = f"{suffix}_rank{global_rank}"
561
+ return suffix
562
+ except Exception:
563
+ # Fallback if parallel state not initialized
564
+ if global_rank is not None:
565
+ return f"rank{global_rank}"
566
+ return ""
vllm/entrypoints/llm.py
+9
-2
@@ -1685,8 +1685,15 @@ class LLM:
1685
tokenization_kwargs=encode_kwargs,
1686
)
1687
1688
- def start_profile(self) -> None:
1689
- self.llm_engine.start_profile()
1688
+ def start_profile(self, profile_prefix: str | None = None) -> None:
1689
+ """Start profiling with optional custom trace prefix.
1690
+
1691
+ Args:
1692
+ profile_prefix: Optional prefix for the trace file names. If provided,
1693
+ trace files will be named as "<prefix>_dp<X>_pp<Y>_tp<Z>".
1694
+ If not provided, default naming will be used.
1695
+ """
1696
+ self.llm_engine.start_profile(profile_prefix)
1697
1698
def stop_profile(self) -> None:
1699
self.llm_engine.stop_profile()
vllm/v1/engine/async_llm.py
+2
-2
@@ -911,8 +911,8 @@ class AsyncLLM(EngineClient):
911
if self.errored:
912
raise self.dead_error
913
914
- async def start_profile(self) -> None:
915
- coros = [self.engine_core.profile_async(True)]
914
+ async def start_profile(self, profile_prefix: str | None = None) -> None:
915
+ coros = [self.engine_core.profile_async(True, profile_prefix)]
916
if self.profiler is not None:
917
coros.append(asyncio.to_thread(self.profiler.start))
918
await asyncio.gather(*coros)
vllm/v1/engine/core.py
+2
-2
@@ -568,8 +568,8 @@ class EngineCore:
568
if self.scheduler:
569
self.scheduler.shutdown()
570
571
- def profile(self, is_start: bool = True):
572
- self.model_executor.profile(is_start)
571
+ def profile(self, is_start: bool = True, profile_prefix: str | None = None):
572
+ self.model_executor.profile(is_start, profile_prefix)
573
574
def reset_mm_cache(self):
575
# NOTE: Since this is mainly for debugging, we don't attempt to
vllm/v1/engine/core_client.py
+12
-8
@@ -135,7 +135,7 @@ class EngineCoreClient(ABC):
135
def add_request(self, request: EngineCoreRequest) -> None:
136
raise NotImplementedError
137
138
- def profile(self, is_start: bool = True) -> None:
138
+ def profile(self, is_start: bool = True, profile_prefix: str | None = None) -> None:
139
raise NotImplementedError
140
141
def reset_mm_cache(self) -> None:
@@ -210,7 +210,9 @@ class EngineCoreClient(ABC):
210
async def add_request_async(self, request: EngineCoreRequest) -> None:
211
raise NotImplementedError
212
213
- async def profile_async(self, is_start: bool = True) -> None:
213
+ async def profile_async(
214
+ self, is_start: bool = True, profile_prefix: str | None = None
215
+ ) -> None:
216
raise NotImplementedError
217
218
async def reset_mm_cache_async(self) -> None:
@@ -295,8 +297,8 @@ class InprocClient(EngineCoreClient):
297
def shutdown(self) -> None:
298
self.engine_core.shutdown()
299
298
- def profile(self, is_start: bool = True) -> None:
299
- self.engine_core.profile(is_start)
300
+ def profile(self, is_start: bool = True, profile_prefix: str | None = None) -> None:
301
+ self.engine_core.profile(is_start, profile_prefix)
302
303
def reset_mm_cache(self) -> None:
304
self.engine_core.reset_mm_cache()
@@ -765,8 +767,8 @@ class SyncMPClient(MPClient):
767
if request_ids and not self.resources.engine_dead:
768
self._send_input(EngineCoreRequestType.ABORT, request_ids)
769
768
- def profile(self, is_start: bool = True) -> None:
769
- self.call_utility("profile", is_start)
770
+ def profile(self, is_start: bool = True, profile_prefix: str | None = None) -> None:
771
+ self.call_utility("profile", is_start, profile_prefix)
772
773
def reset_mm_cache(self) -> None:
774
self.call_utility("reset_mm_cache")
@@ -987,8 +989,10 @@ class AsyncMPClient(MPClient):
989
"""Resume the scheduler after a pause."""
990
await self.call_utility_async("resume_scheduler")
991
990
- async def profile_async(self, is_start: bool = True) -> None:
991
- await self.call_utility_async("profile", is_start)
992
+ async def profile_async(
993
+ self, is_start: bool = True, profile_prefix: str | None = None
994
+ ) -> None:
995
+ await self.call_utility_async("profile", is_start, profile_prefix)
996
997
async def reset_mm_cache_async(self) -> None:
998
await self.call_utility_async("reset_mm_cache")
vllm/v1/engine/llm_engine.py
+2
-2
@@ -326,8 +326,8 @@ class LLMEngine:
326
327
return processed_outputs.request_outputs
328
329
- def start_profile(self):
330
- self.engine_core.profile(True)
329
+ def start_profile(self, profile_prefix: str | None = None):
330
+ self.engine_core.profile(True, profile_prefix)
331
332
def stop_profile(self):
333
self.engine_core.profile(False)
vllm/v1/executor/abstract.py
+2
-2
@@ -238,8 +238,8 @@ class Executor(ABC):
238
def max_concurrent_batches(self) -> int:
239
return 1
240
241
- def profile(self, is_start: bool = True):
242
- self.collective_rpc("profile", args=(is_start,))
241
+ def profile(self, is_start: bool = True, profile_prefix: str | None = None):
242
+ self.collective_rpc("profile", args=(is_start, profile_prefix))
243
244
def save_sharded_state(
245
self,
vllm/v1/metrics/loggers.py
+2
-2
@@ -1305,8 +1305,8 @@ class StatLoggerManager:
1305
):
1306
if engine_idx is None:
1307
engine_idx = 0
1308
- for logger in self.stat_loggers:
1309
- logger.record(
1308
+ for stat_logger in self.stat_loggers:
1309
+ stat_logger.record(
1310
scheduler_stats,
1311
iteration_stats,
1312
mm_cache_stats=mm_cache_stats,
vllm/v1/worker/cpu_worker.py
+1
-1
@@ -212,7 +212,7 @@ class CPUWorker(Worker):
212
)
213
return ",".join([str(x.id) for x in logical_cpu_list])
214
215
- def profile(self, is_start: bool = True):
215
+ def profile(self, is_start: bool = True, profile_prefix: str | None = None):
216
if self.profiler is None:
217
raise RuntimeError("Profiler is not enabled.")
218
if is_start:
vllm/v1/worker/gpu_worker.py
+45
-16
@@ -103,20 +103,14 @@ class Worker(WorkerBase):
103
)
104
105
# Torch/CUDA profiler. Enabled and configured through profiler_config.
106
+ # Profiler wrapper is created lazily in profile() when start is called,
107
+ # so we have all the information needed for proper trace naming.
108
self.profiler: Any | None = None
107
- profiler_config = vllm_config.profiler_config
108
- if profiler_config.profiler == "torch":
109
- worker_name = f"{vllm_config.instance_id}-rank-{self.rank}"
110
- self.profiler = TorchProfilerWrapper(
111
- profiler_config,
112
- worker_name=worker_name,
113
- local_rank=self.local_rank,
114
- activities=["CPU", "CUDA"],
115
- )
116
- elif profiler_config.profiler == "cuda":
117
- self.profiler = CudaProfilerWrapper(profiler_config)
118
- else:
119
- self.profiler = None
109
+ self.profiler_config = vllm_config.profiler_config
110
+
111
+ # Only validate profiler config is valid, don't instantiate yet
112
+ if self.profiler_config.profiler not in ("torch", "cuda", None):
113
+ raise ValueError(f"Unknown profiler type: {self.profiler_config.profiler}")
114
115
self.use_v2_model_runner = envs.VLLM_USE_V2_MODEL_RUNNER
116
@@ -677,17 +671,52 @@ class Worker(WorkerBase):
671
def take_draft_token_ids(self) -> DraftTokenIds | None:
672
return self.model_runner.take_draft_token_ids()
673
680
- def profile(self, is_start: bool = True):
681
- if self.profiler is None:
674
+ def profile(self, is_start: bool = True, profile_prefix: str | None = None):
675
+ # Check if profiling is enabled
676
+ if self.profiler_config is None or self.profiler_config.profiler is None:
677
raise RuntimeError(
678
"Profiling is not enabled. Please set --profiler-config to enable "
679
"profiling. Example: "
680
"'--profiler-config.profiler=torch --profiler-config.torch_profiler_dir"
681
"=YOUR_DIR_PATH_TO_DUMP_TRACE'"
682
)
683
+
684
if is_start:
689
- self.profiler.start()
685
+ # Generate the trace name by combining prefix with comprehensive rank suffix
686
+ from vllm.distributed.utils import get_worker_rank_suffix
687
+
688
+ rank_suffix = get_worker_rank_suffix(global_rank=self.rank)
689
+
690
+ # Build the full trace name
691
+ if profile_prefix:
692
+ trace_name = f"{profile_prefix}_{rank_suffix}"
693
+ else:
694
+ trace_name = rank_suffix
695
+
696
+ # Create the profiler wrapper only on the first start call
697
+ if self.profiler is None:
698
+ if self.profiler_config.profiler == "torch":
699
+ self.profiler = TorchProfilerWrapper(
700
+ self.profiler_config,
701
+ worker_name=trace_name,
702
+ local_rank=self.local_rank,
703
+ activities=["CPU", "CUDA"],
704
+ )
705
+ logger.debug(
706
+ "Starting torch profiler with trace name: %s", trace_name
707
+ )
708
+ elif self.profiler_config.profiler == "cuda":
709
+ self.profiler = CudaProfilerWrapper(self.profiler_config)
710
+ logger.debug("Starting CUDA profiler")
711
+ self.profiler.start()
712
+ else:
713
+ # Profiler already initialized. Restart profiling but keep
714
+ # the original trace name from the first initialization.
715
+ self.profiler.start()
716
else:
717
+ if self.profiler is None:
718
+ logger.warning("Profiler was not started, nothing to stop.")
719
+ return
720
self.profiler.stop()
721
722
def execute_dummy_batch(self) -> None: