2929
3030import vllm .envs as envs
3131from vllm .compilation .counter import compilation_counter
32- from vllm .config import VllmConfig
32+ from vllm .config import VllmConfig , set_current_vllm_config
3333from vllm .config .compilation import CUDAGraphMode
3434from vllm .distributed .parallel_state import (
3535 get_dcp_group ,
5050from vllm .utils .torch_utils import PIN_MEMORY , STR_DTYPE_TO_TORCH_DTYPE
5151from vllm .v1 .core .sched .output import GrammarOutput , SchedulerOutput
5252from vllm .v1 .kv_cache_interface import KVCacheConfig , MambaSpec
53+ from vllm .v1 .kv_cache_spec_registry import KVCacheSpecRegistry
5354from vllm .v1 .outputs import DraftTokenIds , ModelRunnerOutput
5455from vllm .v1 .worker .cp_utils import check_attention_cp_compatibility
5556from vllm .v1 .worker .gpu .async_utils import AsyncOutput , AsyncPoolingOutput
113114from vllm .v1 .worker .gpu .structured_outputs import StructuredOutputsWorker
114115from vllm .v1 .worker .lora_model_runner_mixin import LoRAModelRunnerMixin
115116from vllm .v1 .worker .utils import KVBlockZeroer
117+ from vllm .v1 .worker .workspace import lock_workspace
116118
117119logger = init_logger (__name__ )
118120
@@ -402,7 +404,9 @@ def main_stream(self) -> torch.cuda.Stream:
402404 def get_kv_cache_spec (self ):
403405 return get_kv_cache_spec (self .vllm_config )
404406
405- def initialize_kv_cache (self , kv_cache_config : KVCacheConfig ) -> None :
407+ def initialize_kv_cache (
408+ self , kv_cache_config : KVCacheConfig , is_profiling : bool = False
409+ ) -> None :
406410 kv_cache_config = deepcopy (kv_cache_config )
407411 self .kv_cache_config = kv_cache_config
408412
@@ -475,7 +479,7 @@ def initialize_kv_cache(self, kv_cache_config: KVCacheConfig) -> None:
475479 self .speculator .init_cudagraph_manager (cudagraph_mode )
476480
477481 check_attention_cp_compatibility (self .vllm_config )
478- if isinstance (self .speculator , DraftModelSpeculator ):
482+ if isinstance (self .speculator , DraftModelSpeculator ) and not is_profiling :
479483 # HACK(woosuk)
480484 self .speculator .set_attn (
481485 self .model_state , self .kv_cache_config , self .block_tables
@@ -492,7 +496,8 @@ def initialize_kv_cache(self, kv_cache_config: KVCacheConfig) -> None:
492496 self .kernel_block_sizes ,
493497 self .vllm_config ,
494498 )
495- self .kv_connector = get_kv_connector (self .vllm_config , kv_caches_dict )
499+ if not is_profiling :
500+ self .kv_connector = get_kv_connector (self .vllm_config , kv_caches_dict )
496501
497502 def _init_kv_zero_meta (self ) -> None :
498503 """Build KV-block zeroing metadata; invoked from gpu_worker."""
@@ -682,9 +687,106 @@ def _get_num_input_tokens(self, num_scheduled_tokens: int) -> int:
682687 # SP is not supported yet.
683688 return num_scheduled_tokens
684689
690+ def _init_minimal_kv_cache_for_profiling (self ) -> None :
691+ from vllm .v1 .core .kv_cache_utils import (
692+ get_kv_cache_config_from_groups ,
693+ get_kv_cache_groups ,
694+ )
695+
696+ kv_cache_spec = self .get_kv_cache_spec ()
697+ KVCacheSpecRegistry .check_kv_cache_spec_registry (kv_cache_spec )
698+ kv_cache_groups = get_kv_cache_groups (self .vllm_config , kv_cache_spec )
699+ min_blocks = self .compilation_config .max_cudagraph_capture_size or 1
700+
701+ # Temporarily allocate just enough KV cache state to instantiate
702+ # attention metadata builders for workspace sizing.
703+ saved_override = self .cache_config .num_gpu_blocks_override
704+ self .cache_config .num_gpu_blocks_override = min_blocks
705+ try :
706+ minimal_config = get_kv_cache_config_from_groups (
707+ self .vllm_config ,
708+ kv_cache_groups ,
709+ available_memory = 0 ,
710+ )
711+ finally :
712+ self .cache_config .num_gpu_blocks_override = saved_override
713+
714+ self .initialize_kv_cache (minimal_config , is_profiling = True )
715+ self .cache_config .num_gpu_blocks = minimal_config .num_blocks
716+
717+ logger .debug ("Initialized minimal KV cache for CUDA graph profiling" )
718+
719+ def _cleanup_profiling_kv_cache (self ) -> None :
720+ torch .accelerator .synchronize ()
721+
722+ if hasattr (self , "kv_caches" ) and self .kv_caches :
723+ for i in range (len (self .kv_caches )):
724+ self .kv_caches [i ] = None # type: ignore[assignment]
725+ self .kv_caches .clear ()
726+ if hasattr (self , "attn_groups" ):
727+ self .attn_groups .clear ()
728+ for attr in (
729+ "attn_groups" ,
730+ "kv_cache_config" ,
731+ "block_tables" ,
732+ "kernel_block_sizes" ,
733+ "cudagraph_manager" ,
734+ ):
735+ if hasattr (self , attr ):
736+ delattr (self , attr )
737+ self .cache_config .num_gpu_blocks = None
738+
739+ for layer in self .compilation_config .static_forward_context .values ():
740+ if hasattr (layer , "kv_cache" ):
741+ kv_cache = layer .kv_cache
742+ layer .kv_cache = (
743+ torch .tensor ([]) if isinstance (kv_cache , torch .Tensor ) else []
744+ )
745+ # Clean up quantized KV cache scale views
746+ # (int8_per_token_head, fp8_per_token_head).
747+ if hasattr (layer , "impl" ):
748+ if hasattr (layer .impl , "_k_scale_cache" ):
749+ layer .impl ._k_scale_cache = None
750+ if hasattr (layer .impl , "_v_scale_cache" ):
751+ layer .impl ._v_scale_cache = None
752+
753+ gc .collect ()
754+ torch .accelerator .empty_cache ()
755+ logger .debug ("Cleaned up profiling KV cache and CUDA graphs" )
756+
757+ def _reserve_attention_workspace_for_cudagraph_capture (self ) -> int :
758+ if not getattr (self , "attn_groups" , None ):
759+ return 0
760+
761+ reserved_before = torch .accelerator .memory_reserved (self .device )
762+ for groups in self .attn_groups :
763+ for attn_group in groups :
764+ for builder in attn_group .metadata_builders :
765+ builder .reserve_workspace_for_cudagraph_capture ()
766+ torch .accelerator .synchronize ()
767+ torch .accelerator .empty_cache ()
768+ reserved_after = torch .accelerator .memory_reserved (self .device )
769+ return max (reserved_after - reserved_before , 0 )
770+
685771 def profile_cudagraph_memory (self ) -> int :
686- # NOTE(woosuk): It is TBD whether we keep this API or not.
687- return 0
772+ self .cudagraph_memory_persistent_estimate = 0
773+ self .cudagraph_memory_graph_pool_estimate = 0
774+
775+ try :
776+ with set_current_vllm_config (self .vllm_config ):
777+ self ._init_minimal_kv_cache_for_profiling ()
778+ persistent_estimate = (
779+ self ._reserve_attention_workspace_for_cudagraph_capture ()
780+ )
781+ finally :
782+ self ._cleanup_profiling_kv_cache ()
783+
784+ self .cudagraph_memory_persistent_estimate = int (persistent_estimate )
785+ logger .info (
786+ "Estimated CUDA graph persistent workspace memory: %.2f GiB" ,
787+ persistent_estimate / (1 << 30 ),
788+ )
789+ return int (persistent_estimate )
688790
689791 @torch .inference_mode ()
690792 def capture_model (self ) -> int :
@@ -700,6 +802,8 @@ def capture_model(self) -> int:
700802
701803 start_time = time .perf_counter ()
702804 gc .collect ()
805+ self ._reserve_attention_workspace_for_cudagraph_capture ()
806+ torch .accelerator .synchronize ()
703807 torch .accelerator .empty_cache ()
704808 start_free_gpu_memory = torch .accelerator .get_memory_info ()[0 ]
705809
@@ -729,6 +833,7 @@ def capture_model(self) -> int:
729833 elapsed_time ,
730834 cuda_graph_size / (1 << 30 ),
731835 )
836+ lock_workspace ()
732837 return cuda_graph_size
733838
734839 def _remove_request (self , req_id : str ) -> bool :
0 commit comments