Skip to content

Commit 9cb8ee6

Browse files
committed
Account V2 CUDA graph pool memory
Signed-off-by: lesj0610 <lesj0610@users.noreply.github.com>
1 parent 99c0875 commit 9cb8ee6

3 files changed

Lines changed: 382 additions & 22 deletions

File tree

tests/v1/worker/test_cudagraph_memory_profiling.py

Lines changed: 205 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,11 @@ def null_context(*args, **kwargs):
525525
"set_current_vllm_config",
526526
lambda *args, **kwargs: null_context(),
527527
)
528+
monkeypatch.setattr(
529+
gpu_model_runner_v2.current_platform,
530+
"is_cuda",
531+
lambda: True,
532+
)
528533

529534
def reserve_attention_workspace():
530535
events.append("reserve")
@@ -534,14 +539,212 @@ def reserve_attention_workspace():
534539
runner._reserve_attention_workspace_for_cudagraph_capture = (
535540
reserve_attention_workspace
536541
)
542+
543+
def profile_cudagraph_memory_graph_pool():
544+
events.append("graph_pool")
545+
return 2048
546+
547+
runner._profile_cudagraph_memory_graph_pool = profile_cudagraph_memory_graph_pool
537548
runner._cleanup_profiling_kv_cache = lambda: events.append("cleanup")
538549

539550
estimate = runner.profile_cudagraph_memory()
540551

541-
assert estimate == 4096
552+
assert estimate == 6144
542553
assert runner.cudagraph_memory_persistent_estimate == 4096
554+
assert runner.cudagraph_memory_graph_pool_estimate == 2048
555+
assert events == ["init", "reserve", "graph_pool", "cleanup"]
556+
557+
558+
def test_v2_profile_is_noop_on_non_cuda(monkeypatch):
559+
from vllm.v1.worker.gpu import model_runner as gpu_model_runner_v2
560+
from vllm.v1.worker.gpu.model_runner import GPUModelRunner
561+
562+
runner = GPUModelRunner.__new__(GPUModelRunner)
563+
564+
monkeypatch.setattr(
565+
gpu_model_runner_v2.current_platform,
566+
"is_cuda",
567+
lambda: False,
568+
)
569+
runner._init_minimal_kv_cache_for_profiling = pytest.fail
570+
571+
assert runner.profile_cudagraph_memory() == 0
572+
assert runner.cudagraph_memory_persistent_estimate == 0
543573
assert runner.cudagraph_memory_graph_pool_estimate == 0
544-
assert events == ["init", "reserve", "cleanup"]
574+
575+
576+
def test_v2_cuda_graph_pool_sample_uses_peak(monkeypatch):
577+
from vllm.v1.worker.gpu import model_runner as gpu_model_runner_v2
578+
from vllm.v1.worker.gpu.model_runner import GPUModelRunner
579+
580+
runner = GPUModelRunner.__new__(GPUModelRunner)
581+
runner.device = torch.device("cuda:0")
582+
events = []
583+
584+
get_memory_info_values = iter([(10_000, 0), (9_950, 0)])
585+
monkeypatch.setattr(
586+
gpu_model_runner_v2.torch.accelerator,
587+
"synchronize",
588+
lambda: events.append("sync"),
589+
)
590+
monkeypatch.setattr(
591+
gpu_model_runner_v2.torch.accelerator,
592+
"empty_cache",
593+
lambda: events.append("empty_cache"),
594+
)
595+
monkeypatch.setattr(
596+
gpu_model_runner_v2.torch.accelerator,
597+
"get_memory_info",
598+
lambda: next(get_memory_info_values),
599+
)
600+
monkeypatch.setattr(
601+
gpu_model_runner_v2.torch.accelerator,
602+
"memory_reserved",
603+
lambda device: 1_000,
604+
)
605+
monkeypatch.setattr(
606+
gpu_model_runner_v2.torch.accelerator,
607+
"memory_allocated",
608+
lambda device: 500,
609+
)
610+
monkeypatch.setattr(
611+
gpu_model_runner_v2.torch.accelerator,
612+
"reset_peak_memory_stats",
613+
lambda device: events.append("reset_peak"),
614+
)
615+
monkeypatch.setattr(
616+
gpu_model_runner_v2.torch.accelerator,
617+
"max_memory_reserved",
618+
lambda device: 1_120,
619+
)
620+
monkeypatch.setattr(
621+
gpu_model_runner_v2.torch.accelerator,
622+
"max_memory_allocated",
623+
lambda device: 530,
624+
)
625+
626+
assert (
627+
runner._measure_cuda_graph_pool_sample(lambda: events.append("capture")) == 120
628+
)
629+
assert events == [
630+
"sync",
631+
"empty_cache",
632+
"reset_peak",
633+
"capture",
634+
"sync",
635+
]
636+
637+
638+
def test_v2_graph_pool_profile_restores_capture_state(monkeypatch):
639+
from vllm.v1.worker.gpu import model_runner as gpu_model_runner_v2
640+
from vllm.v1.worker.gpu.cudagraph_utils import BatchExecutionDescriptor
641+
from vllm.v1.worker.gpu.model_runner import CUDAGraphMode, GPUModelRunner
642+
643+
piecewise_descs = [
644+
BatchExecutionDescriptor(CUDAGraphMode.PIECEWISE, 128, None),
645+
BatchExecutionDescriptor(CUDAGraphMode.PIECEWISE, 64, None),
646+
BatchExecutionDescriptor(CUDAGraphMode.PIECEWISE, 32, None),
647+
]
648+
full_descs = [
649+
BatchExecutionDescriptor(CUDAGraphMode.FULL, 80, 4),
650+
BatchExecutionDescriptor(CUDAGraphMode.FULL, 40, 2),
651+
]
652+
original_graph = object()
653+
original_breakable_entry = object()
654+
breakable_runner = SimpleNamespace(
655+
graph_pool="breakable-original",
656+
entries={"old": original_breakable_entry},
657+
)
658+
659+
class FakeCudaGraphManager:
660+
def __init__(self):
661+
self.pool = "manager-original"
662+
self.graphs = {"old": original_graph}
663+
self._graphs_captured = False
664+
self.hidden_states = "hidden-original"
665+
self.aux_hidden_states = ["aux-original"]
666+
self.intermediate_tensors = "intermediate-original"
667+
self.use_aux_hidden_state_outputs = False
668+
self.use_breakable_cg = True
669+
self.breakable_cg_runner = breakable_runner
670+
671+
def get_capture_descs(self):
672+
return [
673+
(CUDAGraphMode.PIECEWISE, piecewise_descs),
674+
(CUDAGraphMode.FULL, full_descs),
675+
]
676+
677+
def init_breakable_cg_runner(self, model):
678+
assert model == "model"
679+
680+
runner = GPUModelRunner.__new__(GPUModelRunner)
681+
runner.model = "model"
682+
runner.cudagraph_manager = FakeCudaGraphManager()
683+
sample_values = iter([1_000, 2_000_000, 800, 3_000_000])
684+
capture_calls = []
685+
686+
monkeypatch.setattr(
687+
gpu_model_runner_v2.current_platform,
688+
"graph_pool_handle",
689+
lambda: "profile-pool",
690+
)
691+
empty_cache_calls = []
692+
monkeypatch.setattr(
693+
gpu_model_runner_v2.torch.accelerator,
694+
"empty_cache",
695+
lambda: empty_cache_calls.append("empty_cache"),
696+
)
697+
monkeypatch.setattr(
698+
gpu_model_runner_v2.compilation_counter,
699+
"num_cudagraph_captured",
700+
11,
701+
)
702+
monkeypatch.setattr(
703+
runner,
704+
"_measure_cuda_graph_pool_sample",
705+
lambda capture_fn: capture_fn() or next(sample_values),
706+
)
707+
708+
def capture_model_cudagraphs(**kwargs):
709+
capture_calls.append(kwargs)
710+
assert kwargs["capture_speculator"] is False
711+
assert runner.cudagraph_manager.pool == "profile-pool"
712+
assert breakable_runner.graph_pool == "profile-pool"
713+
runner.cudagraph_manager.graphs["profile"] = object()
714+
runner.cudagraph_manager._graphs_captured = True
715+
runner.cudagraph_manager.hidden_states = "hidden-profile"
716+
runner.cudagraph_manager.aux_hidden_states = ["aux-profile"]
717+
runner.cudagraph_manager.intermediate_tensors = "intermediate-profile"
718+
runner.cudagraph_manager.use_aux_hidden_state_outputs = True
719+
breakable_runner.entries["profile"] = object()
720+
gpu_model_runner_v2.compilation_counter.num_cudagraph_captured = 99
721+
722+
monkeypatch.setattr(
723+
runner,
724+
"_capture_model_cudagraphs",
725+
capture_model_cudagraphs,
726+
)
727+
728+
estimate = runner._profile_cudagraph_memory_graph_pool()
729+
730+
assert estimate == 7_001_000
731+
assert [call["capture_descs"] for call in capture_calls] == [
732+
{CUDAGraphMode.PIECEWISE: [piecewise_descs[0]]},
733+
{CUDAGraphMode.PIECEWISE: [piecewise_descs[1]]},
734+
{CUDAGraphMode.FULL: [full_descs[0]]},
735+
{CUDAGraphMode.FULL: [full_descs[1]]},
736+
]
737+
assert runner.cudagraph_manager.pool == "manager-original"
738+
assert runner.cudagraph_manager.graphs == {"old": original_graph}
739+
assert runner.cudagraph_manager._graphs_captured is False
740+
assert runner.cudagraph_manager.hidden_states == "hidden-original"
741+
assert runner.cudagraph_manager.aux_hidden_states == ["aux-original"]
742+
assert runner.cudagraph_manager.intermediate_tensors == "intermediate-original"
743+
assert runner.cudagraph_manager.use_aux_hidden_state_outputs is False
744+
assert breakable_runner.graph_pool == "breakable-original"
745+
assert breakable_runner.entries == {"old": original_breakable_entry}
746+
assert gpu_model_runner_v2.compilation_counter.num_cudagraph_captured == 11
747+
assert empty_cache_calls == ["empty_cache"]
545748

546749

547750
def test_v2_cleanup_profiling_kv_cache_releases_builder_refs(monkeypatch):

vllm/v1/worker/gpu/cudagraph_utils.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,22 @@ def _init_candidates(self) -> None:
257257
def needs_capture(self) -> bool:
258258
return len(self._capture_descs) > 0
259259

260+
def get_capture_descs(
261+
self,
262+
) -> list[tuple[CUDAGraphMode, list[BatchExecutionDescriptor]]]:
263+
return [
264+
(mode, list(self._capture_descs[mode]))
265+
for mode in [CUDAGraphMode.PIECEWISE, CUDAGraphMode.FULL]
266+
if mode in self._capture_descs
267+
]
268+
260269
@torch.inference_mode()
261270
def capture(
262271
self,
263272
create_forward_fn: CreateForwardFn,
264273
progress_bar_desc: str = "Capturing CUDA graphs",
274+
capture_descs: dict[CUDAGraphMode, list[BatchExecutionDescriptor]]
275+
| None = None,
265276
) -> dict[BatchExecutionDescriptor, AttentionStatePair]:
266277
"""Capture CUDA graphs.
267278
@@ -275,15 +286,18 @@ def capture(
275286
capture.
276287
"""
277288
attn_states: dict[BatchExecutionDescriptor, AttentionStatePair] = {}
289+
capture_descs = (
290+
capture_descs if capture_descs is not None else self._capture_descs
291+
)
278292
with graph_capture(device=self.device):
279293
# Capture in order: PIECEWISE first, then FULL. PIECEWISE has larger
280294
# activations so FULL activations should fit in already allocated
281295
# buffers in the graph pool.
282296
for mode in [CUDAGraphMode.PIECEWISE, CUDAGraphMode.FULL]:
283-
if mode not in self._capture_descs:
297+
if mode not in capture_descs:
284298
continue
285299

286-
descs = self._capture_descs[mode]
300+
descs = capture_descs[mode]
287301
if is_global_first_rank():
288302
descs = tqdm(descs, desc=f"{progress_bar_desc} ({mode.name})")
289303
for desc in descs:
@@ -422,6 +436,8 @@ def capture(
422436
use_aux_hidden_state_outputs: bool = False,
423437
lora_capture_hook: Callable[[int, int, int], None] | None = None,
424438
progress_bar_desc: str = "Capturing CUDA graphs",
439+
capture_descs: dict[CUDAGraphMode, list[BatchExecutionDescriptor]]
440+
| None = None,
425441
) -> dict[BatchExecutionDescriptor, AttentionStatePair]:
426442
"""Capture CUDA graphs for model forward pass."""
427443
self.use_aux_hidden_state_outputs = use_aux_hidden_state_outputs
@@ -534,7 +550,7 @@ def forward_fn(cg_mode: CUDAGraphMode) -> None:
534550

535551
return forward_fn, AttentionState(attn_metadata, slot_mappings)
536552

537-
return super().capture(create_forward_fn, progress_bar_desc)
553+
return super().capture(create_forward_fn, progress_bar_desc, capture_descs)
538554

539555
def run_fullgraph(
540556
self, desc: BatchExecutionDescriptor

0 commit comments

Comments
 (0)