Skip to content

Commit 561cfb5

Browse files
committed
Fix V2 CUDA graph pool memory sampling
Signed-off-by: lesj0610 <lesj0610@users.noreply.github.com>
1 parent 9cb8ee6 commit 561cfb5

2 files changed

Lines changed: 93 additions & 13 deletions

File tree

tests/v1/worker/test_cudagraph_memory_profiling.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ def test_v2_profile_is_noop_on_non_cuda(monkeypatch):
573573
assert runner.cudagraph_memory_graph_pool_estimate == 0
574574

575575

576-
def test_v2_cuda_graph_pool_sample_uses_peak(monkeypatch):
576+
def test_v2_cuda_graph_pool_sample_separates_residual_and_peak(monkeypatch):
577577
from vllm.v1.worker.gpu import model_runner as gpu_model_runner_v2
578578
from vllm.v1.worker.gpu.model_runner import GPUModelRunner
579579

@@ -623,18 +623,77 @@ def test_v2_cuda_graph_pool_sample_uses_peak(monkeypatch):
623623
lambda device: 530,
624624
)
625625

626-
assert (
627-
runner._measure_cuda_graph_pool_sample(lambda: events.append("capture")) == 120
628-
)
626+
sample = runner._measure_cuda_graph_pool_sample(lambda: events.append("capture"))
627+
assert sample.residual == 50
628+
assert sample.peak == 120
629629
assert events == [
630630
"sync",
631631
"empty_cache",
632632
"reset_peak",
633633
"capture",
634634
"sync",
635+
"empty_cache",
635636
]
636637

637638

639+
def test_v2_graph_pool_profile_does_not_multiply_transient_peak(monkeypatch):
640+
from vllm.v1.worker.gpu import model_runner as gpu_model_runner_v2
641+
from vllm.v1.worker.gpu.cudagraph_utils import BatchExecutionDescriptor
642+
from vllm.v1.worker.gpu.model_runner import (
643+
CudaGraphMemorySample,
644+
CUDAGraphMode,
645+
GPUModelRunner,
646+
)
647+
648+
descs = [
649+
BatchExecutionDescriptor(CUDAGraphMode.PIECEWISE, 128, None),
650+
BatchExecutionDescriptor(CUDAGraphMode.PIECEWISE, 64, None),
651+
BatchExecutionDescriptor(CUDAGraphMode.PIECEWISE, 32, None),
652+
]
653+
654+
class FakeCudaGraphManager:
655+
pool = "manager-original"
656+
graphs = {}
657+
_graphs_captured = False
658+
hidden_states = None
659+
aux_hidden_states = []
660+
intermediate_tensors = None
661+
use_aux_hidden_state_outputs = False
662+
use_breakable_cg = False
663+
breakable_cg_runner = None
664+
665+
def get_capture_descs(self):
666+
return [(CUDAGraphMode.PIECEWISE, descs)]
667+
668+
runner = GPUModelRunner.__new__(GPUModelRunner)
669+
runner.cudagraph_manager = FakeCudaGraphManager()
670+
samples = iter(
671+
[
672+
CudaGraphMemorySample(residual=10 << 20, peak=200 << 20),
673+
CudaGraphMemorySample(residual=2 << 20, peak=180 << 20),
674+
]
675+
)
676+
677+
monkeypatch.setattr(
678+
gpu_model_runner_v2.current_platform,
679+
"graph_pool_handle",
680+
lambda: "profile-pool",
681+
)
682+
monkeypatch.setattr(
683+
gpu_model_runner_v2.torch.accelerator,
684+
"empty_cache",
685+
lambda: None,
686+
)
687+
monkeypatch.setattr(
688+
runner,
689+
"_measure_cuda_graph_pool_sample",
690+
lambda capture_fn: capture_fn() or next(samples),
691+
)
692+
monkeypatch.setattr(runner, "_capture_model_cudagraphs", lambda **kwargs: None)
693+
694+
assert runner._profile_cudagraph_memory_graph_pool() == 204 << 20
695+
696+
638697
def test_v2_graph_pool_profile_restores_capture_state(monkeypatch):
639698
from vllm.v1.worker.gpu import model_runner as gpu_model_runner_v2
640699
from vllm.v1.worker.gpu.cudagraph_utils import BatchExecutionDescriptor
@@ -680,7 +739,16 @@ def init_breakable_cg_runner(self, model):
680739
runner = GPUModelRunner.__new__(GPUModelRunner)
681740
runner.model = "model"
682741
runner.cudagraph_manager = FakeCudaGraphManager()
683-
sample_values = iter([1_000, 2_000_000, 800, 3_000_000])
742+
from vllm.v1.worker.gpu.model_runner import CudaGraphMemorySample
743+
744+
sample_values = iter(
745+
[
746+
CudaGraphMemorySample(residual=1_000, peak=10_000),
747+
CudaGraphMemorySample(residual=2_000_000, peak=200_000_000),
748+
CudaGraphMemorySample(residual=800, peak=8_000),
749+
CudaGraphMemorySample(residual=3_000_000, peak=300_000_000),
750+
]
751+
)
684752
capture_calls = []
685753

686754
monkeypatch.setattr(
@@ -727,7 +795,7 @@ def capture_model_cudagraphs(**kwargs):
727795

728796
estimate = runner._profile_cudagraph_memory_graph_pool()
729797

730-
assert estimate == 7_001_000
798+
assert estimate == 7_010_000
731799
assert [call["capture_descs"] for call in capture_calls] == [
732800
{CUDAGraphMode.PIECEWISE: [piecewise_descs[0]]},
733801
{CUDAGraphMode.PIECEWISE: [piecewise_descs[1]]},

vllm/v1/worker/gpu/model_runner.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@
121121
logger = init_logger(__name__)
122122

123123

124+
class CudaGraphMemorySample(NamedTuple):
125+
residual: int
126+
peak: int
127+
128+
124129
class GPUModelRunner(LoRAModelRunnerMixin):
125130
def __init__(self, vllm_config: VllmConfig, device: torch.device):
126131
self.vllm_config = vllm_config
@@ -796,7 +801,9 @@ def _capture_model_cudagraphs(
796801
if capture_speculator and self.speculator is not None:
797802
self.speculator.capture(attn_states)
798803

799-
def _measure_cuda_graph_pool_sample(self, capture_fn: Callable[[], None]) -> int:
804+
def _measure_cuda_graph_pool_sample(
805+
self, capture_fn: Callable[[], None]
806+
) -> CudaGraphMemorySample:
800807
torch.accelerator.synchronize()
801808
torch.accelerator.empty_cache()
802809
free_before = torch.accelerator.get_memory_info()[0]
@@ -807,8 +814,6 @@ def _measure_cuda_graph_pool_sample(self, capture_fn: Callable[[], None]) -> int
807814
capture_fn()
808815
torch.accelerator.synchronize()
809816

810-
free_after = torch.accelerator.get_memory_info()[0]
811-
residual_delta = max(free_before - free_after, 0)
812817
peak_reserved_delta = max(
813818
torch.accelerator.max_memory_reserved(self.device) - reserved_before,
814819
0,
@@ -817,7 +822,11 @@ def _measure_cuda_graph_pool_sample(self, capture_fn: Callable[[], None]) -> int
817822
torch.accelerator.max_memory_allocated(self.device) - allocated_before,
818823
0,
819824
)
820-
return max(residual_delta, peak_reserved_delta, peak_allocated_delta)
825+
torch.accelerator.empty_cache()
826+
free_after = torch.accelerator.get_memory_info()[0]
827+
residual_delta = max(free_before - free_after, 0)
828+
peak_delta = max(residual_delta, peak_reserved_delta, peak_allocated_delta)
829+
return CudaGraphMemorySample(residual=residual_delta, peak=peak_delta)
821830

822831
def _profile_cudagraph_memory_graph_pool(self) -> int:
823832
assert self.cudagraph_manager is not None
@@ -858,7 +867,7 @@ def _profile_cudagraph_memory_graph_pool(self) -> int:
858867

859868
for mode, descs in capture_descs:
860869
profile_descs = descs[:2]
861-
mem_samples: list[int] = []
870+
mem_samples: list[CudaGraphMemorySample] = []
862871

863872
for desc in profile_descs:
864873
sample_descs = {mode: [desc]}
@@ -878,8 +887,11 @@ def capture_sample(
878887
self._measure_cuda_graph_pool_sample(capture_sample)
879888
)
880889

881-
first_capture = mem_samples[0]
882-
per_graph = max(mem_samples[1] if len(mem_samples) > 1 else 0, 1 << 20)
890+
first_capture = mem_samples[0].peak
891+
per_graph = max(
892+
mem_samples[1].residual if len(mem_samples) > 1 else 0,
893+
1 << 20,
894+
)
883895
shared_memory_estimate[mode] = first_capture
884896
per_graph_estimate[mode] = per_graph * (len(descs) - 1)
885897
logger.debug(

0 commit comments

Comments
 (0)