Skip to content

Commit eed399c

Browse files
authored
[BugFix] Revert pre-KV ACL graph profiling (vllm-project#9865) to fix lmhead TP hang (vllm-project#11562)
### What this PR does / why we need it? This PR reverts vllm-project#9865 / commit `9099b7f66d123ea704e329d7586333ad8b08db50`, which introduced pre-KV ACL graph memory profiling in `determine_available_memory()`. After that change, the combined scenario below can hang during inference and eventually fail with an HCCL timeout in lmhead TP communication: - MTP enabled - `finegrained_tp_config.lmhead_tensor_parallel_size > 0` - ACL graph enabled - AIV/HCCL graph communication path enabled The likely trigger is that the pre-KV graph memory profiling path runs an additional graph warmup/capture before normal KV cache allocation and normal graph capture. In the MTP + lmhead TP path, the draft graph can execute `compute_logits()`, which enters lmhead TP `all_gather` / `all_to_all`. With AIV enabled, this introduces an extra collective graph/capture path before the regular runtime path, and can leave the lmhead TP communication sequence or graph/stream state inconsistent. ### Does this PR introduce _any_ user-facing change? Yes. This reverts the ACL graph memory estimation added by vllm-project#9865. KV cache auto-sizing will no longer subtract the estimated ACL graph pool memory during `determine_available_memory()`. This restores the previous behavior and may increase the computed KV cache budget compared with the reverted implementation. ### How was this patch tested? This PR is a targeted revert. Validation focus: - MTP + lmhead TP + ACL graph + AIV inference no longer hangs in lmhead TP communication. - Existing initialization and graph capture flow returns to the behavior before vllm-project#9865. No new unit test is added because the failure requires multi-node NPU runtime, HCCL communication, ACL graph capture, MTP, lmhead TP, and AIV enabled together, which is not covered by local UT. - vLLM version: v0.23.0 - vLLM main: vllm-project/vllm@1f486d9 Signed-off-by: Yizhou Liu <liu_yizhou@outlook.com>
1 parent a08cca6 commit eed399c

8 files changed

Lines changed: 18 additions & 225 deletions

File tree

tests/e2e/pull_request/four_card/test_graph_mode.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,8 @@
346346
"data_parallel_size": 1,
347347
"enable_expert_parallel": False,
348348
"golden_answers": {"short": QWEN3_PROMPTS_SHORT_BASELINE, "long": QWEN3_PROMPTS_LONG_BASELINE},
349-
"baseline_capture_mem": 0.20,
349+
# TODO: it increases after profile graph memory is disabled, invetigate later
350+
"baseline_capture_mem": 0.30,
350351
"capture_mem_tolerance": 1.3,
351352
}
352353

tests/ut/worker/a2/test_worker_multi_instance.py

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from types import SimpleNamespace
1919
from unittest.mock import MagicMock, patch
2020

21-
from vllm.config import CUDAGraphMode
2221
from vllm.utils.mem_constants import GiB_bytes
2322

2423
from tests.ut.base import TestBase
@@ -51,10 +50,6 @@ def _make_worker(
5150
worker.model_runner = MagicMock()
5251
worker.model_runner.model_memory_usage = model_memory_usage
5352

54-
mock_vllm_config = MagicMock()
55-
mock_vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.NONE
56-
worker.vllm_config = mock_vllm_config
57-
5853
mock_cache_config = MagicMock()
5954
mock_cache_config.kv_cache_memory_bytes = None
6055
mock_cache_config.gpu_memory_utilization = requested_memory / init_total_memory
@@ -140,18 +135,14 @@ def test_single_instance_positive_kv_cache(self, mock_logger):
140135
self.assertGreater(result, 0)
141136

142137
@patch("vllm_ascend.worker.worker.logger")
143-
def test_deepseek_v4_compressed_skips_npugraph_memory_profile(self, mock_logger):
144-
"""DSV4 DSA must not run the pre-KV graph memory profiling path."""
138+
def test_determine_available_memory_does_not_profile_npugraph_memory(self, mock_logger):
145139
total = int(64 * GiB_bytes)
146140
requested_memory = int(total * 0.9)
147141
init_free = int(60 * GiB_bytes)
148142
non_kv_cache = int(1 * GiB_bytes)
149143

150144
worker = self._make_worker(requested_memory, init_free, total)
151-
worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.FULL_DECODE_ONLY
152-
worker.model_config.hf_config.model_type = "deepseek_v4"
153-
worker.model_runner.use_compress = True
154-
worker.model_runner.profile_cudagraph_memory.return_value = int(2 * GiB_bytes)
145+
worker.model_runner.profile_cudagraph_memory = MagicMock()
155146
profile_result = self._make_profile_result(
156147
free_memory_after=init_free - non_kv_cache,
157148
non_kv_cache_memory=non_kv_cache,
@@ -162,42 +153,7 @@ def test_deepseek_v4_compressed_skips_npugraph_memory_profile(self, mock_logger)
162153

163154
worker.model_runner.profile_run.assert_called_once()
164155
worker.model_runner.profile_cudagraph_memory.assert_not_called()
165-
self.assertEqual(
166-
worker.vllm_config.compilation_config.cudagraph_mode,
167-
CUDAGraphMode.FULL_DECODE_ONLY,
168-
)
169-
self.assertEqual(worker.npugraph_memory_estimate, 0)
170-
self.assertEqual(result, requested_memory - non_kv_cache)
171-
172-
@patch("vllm_ascend.worker.worker.logger")
173-
def test_non_deepseek_compressed_still_profiles_npugraph_memory(self, mock_logger):
174-
"""The DSV4 guard must not disable graph memory profiling globally."""
175-
total = int(64 * GiB_bytes)
176-
requested_memory = int(total * 0.9)
177-
init_free = int(60 * GiB_bytes)
178-
non_kv_cache = int(1 * GiB_bytes)
179-
npugraph_memory = int(2 * GiB_bytes)
180-
181-
worker = self._make_worker(requested_memory, init_free, total)
182-
worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.FULL_DECODE_ONLY
183-
worker.model_runner.use_compress = True
184-
worker.model_runner.profile_cudagraph_memory.return_value = npugraph_memory
185-
profile_result = self._make_profile_result(
186-
free_memory_after=init_free - non_kv_cache,
187-
non_kv_cache_memory=non_kv_cache,
188-
)
189-
190-
with (
191-
self._patch_memory_profiling(profile_result),
192-
patch(
193-
"vllm_ascend.worker.worker.envs_vllm.VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS",
194-
False,
195-
),
196-
):
197-
result = worker.determine_available_memory()
198-
199-
worker.model_runner.profile_cudagraph_memory.assert_called_once_with()
200-
self.assertEqual(worker.npugraph_memory_estimate, npugraph_memory)
156+
self.assertFalse(hasattr(worker, "npugraph_memory_estimate"))
201157
self.assertEqual(result, requested_memory - non_kv_cache)
202158

203159
@patch("vllm_ascend.worker.worker.logger")

tests/ut/worker/a2/test_worker_v1.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from unittest.mock import MagicMock, patch
33

44
import torch
5-
from vllm.config import CacheConfig, CUDAGraphMode, ModelConfig, ParallelConfig, ProfilerConfig, VllmConfig
5+
from vllm.config import CacheConfig, ModelConfig, ParallelConfig, ProfilerConfig, VllmConfig
66

77
from tests.ut.base import TestBase
88

@@ -582,8 +582,6 @@ def test_determine_available_memory_normal_case(
582582
worker.requested_memory = 10000 * 0.8
583583
worker.model_runner = MagicMock()
584584
worker.model_runner.model_memory_usage = 500
585-
worker.vllm_config = MagicMock()
586-
worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.NONE
587585
worker.cache_config = MagicMock()
588586
worker.cache_config.gpu_memory_utilization = 0.8
589587
worker.cache_config.kv_cache_memory_bytes = None
@@ -645,8 +643,6 @@ def test_determine_available_memory_with_non_torch_allocations(
645643
worker.requested_memory = 10000 * 0.9
646644
worker.model_runner = MagicMock()
647645
worker.model_runner.model_memory_usage = 500
648-
worker.vllm_config = MagicMock()
649-
worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.NONE
650646
worker.cache_config = MagicMock()
651647
worker.cache_config.gpu_memory_utilization = 0.9
652648
worker.cache_config.kv_cache_memory_bytes = None
@@ -665,14 +661,8 @@ def test_determine_available_memory_with_non_torch_allocations(
665661
@patch("torch.npu.mem_get_info")
666662
@patch("torch.npu.reset_peak_memory_stats")
667663
@patch("torch.npu.empty_cache")
668-
@patch("torch_npu.npu.memory_stats")
669664
def test_determine_available_memory_memory_profiling_error(
670-
self,
671-
mock_torch_memory_stats,
672-
mock_torch_empty_cache,
673-
mock_torch_reset_peak_memory_stats,
674-
mock_torch_mem_get_info,
675-
mock_memory_profiling,
665+
self, mock_torch_empty_cache, mock_torch_reset_peak_memory_stats, mock_torch_mem_get_info, mock_memory_profiling
676666
):
677667
"""Test determine_available_memory throws exception on memory profiling error"""
678668
from vllm_ascend.worker.worker import NPUWorker
@@ -701,16 +691,11 @@ def test_determine_available_memory_memory_profiling_error(
701691
worker.init_snapshot = mock_init_snapshot
702692
worker.requested_memory = 10000 * 0.8
703693
worker.model_runner = MagicMock()
704-
worker.model_runner.model_memory_usage = 0
705-
worker.vllm_config = MagicMock()
706-
worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.NONE
707694
worker.cache_config = MagicMock()
708695
worker.cache_config.gpu_memory_utilization = 0.8
709696
worker.cache_config.kv_cache_memory_bytes = None
710697
worker.device = torch.device("npu:0")
711698

712-
mock_torch_memory_stats.return_value = {"allocated_bytes.all.peak": 0}
713-
714699
# Test should throw assertion error
715700
with self.assertRaises(AssertionError) as cm:
716701
worker.determine_available_memory()
@@ -760,8 +745,6 @@ def test_determine_available_memory_negative_result(
760745
worker.requested_memory = 10000 * 0.8
761746
worker.model_runner = MagicMock()
762747
worker.model_runner.model_memory_usage = 500
763-
worker.vllm_config = MagicMock()
764-
worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.NONE
765748
worker.cache_config = MagicMock()
766749
worker.cache_config.gpu_memory_utilization = 0.8
767750
worker.cache_config.kv_cache_memory_bytes = None

vllm_ascend/_310p/model_runner_310p.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,6 @@ def _check_and_update_cudagraph_mode(
710710
self,
711711
attention_backends,
712712
kv_cache_groups,
713-
is_profiling=False,
714713
) -> None:
715714
# 910B does not need this branch because runner/dispatcher query_len are
716715
# naturally consistent there. 310P ngram needs temporary alignment.

vllm_ascend/compilation/acl_graph.py

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections.abc import Callable
77
from contextlib import ExitStack
88
from dataclasses import dataclass
9-
from typing import Any, ClassVar
9+
from typing import Any
1010
from unittest.mock import patch
1111

1212
import torch
@@ -82,13 +82,6 @@ class ACLGraphWrapper:
8282
guaranteed when VLLM_LOGGING_LEVEL == "DEBUG".
8383
"""
8484

85-
_all_instances: ClassVar[weakref.WeakSet["ACLGraphWrapper"]] = weakref.WeakSet()
86-
87-
@classmethod
88-
def clear_all_graphs(cls) -> None:
89-
for instance in list(cls._all_instances):
90-
instance.clear_graphs()
91-
9285
def __init__(
9386
self,
9487
runnable: Callable,
@@ -123,8 +116,6 @@ def __init__(
123116
self.use_eagle = use_eagle
124117
_acl_graph_wrappers.add(self)
125118

126-
ACLGraphWrapper._all_instances.add(self)
127-
128119
def __getattr__(self, key: str):
129120
# allow accessing the attributes of the runnable.
130121
if hasattr(self.runnable, key):
@@ -139,13 +130,6 @@ def unwrap(self) -> Callable:
139130
# in case we need to access the original runnable.
140131
return self.runnable
141132

142-
@property
143-
def cudagraph_wrapper(self) -> "ACLGraphWrapper":
144-
return self
145-
146-
def clear_graphs(self) -> None:
147-
self.concrete_aclgraph_entries.clear()
148-
149133
def __call__(self, *args, **kwargs):
150134
forward_context = get_forward_context()
151135
batch_descriptor = forward_context.batch_descriptor
@@ -341,13 +325,6 @@ class GraphParams:
341325
_graph_params: GraphParams | None = None
342326

343327

344-
def reset_graph_params():
345-
global _graph_params, _draft_graph_params, _draft_graph_prefill_params
346-
_graph_params = None
347-
_draft_graph_params = None
348-
_draft_graph_prefill_params = None
349-
350-
351328
def set_graph_params(aclgraph_capture_sizes: list[int]):
352329
global _graph_params
353330
if _graph_params is not None:

vllm_ascend/worker/model_runner_v1.py

Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
# Adapted from vllm-project/vllm/vllm/worker/gpu_model_runner.py
1818
#
1919

20-
import gc
2120
import logging
2221
import math
2322
import sys
@@ -114,7 +113,6 @@
114113
# yapf: disable
115114
from vllm_ascend.compilation.acl_graph import (
116115
ACLGraphWrapper,
117-
reset_graph_params,
118116
set_draft_graph_params,
119117
set_graph_params,
120118
update_full_graph_params,
@@ -3945,11 +3943,7 @@ def _finalize_dump_data(self, **kwargs) -> None:
39453943

39463944
self.debugger.step(**kwargs)
39473945

3948-
def initialize_kv_cache(
3949-
self,
3950-
kv_cache_config: KVCacheConfig,
3951-
is_profiling: bool = False,
3952-
) -> None:
3946+
def initialize_kv_cache(self, kv_cache_config: KVCacheConfig) -> None:
39533947
"""
39543948
Initialize KV cache based on `kv_cache_config`.
39553949
Args:
@@ -3963,7 +3957,7 @@ def initialize_kv_cache(
39633957
self.may_add_encoder_only_layers_to_kv_cache_config()
39643958
self.maybe_add_kv_sharing_layers_to_kv_cache_groups(kv_cache_config)
39653959
# NOTE(cmq): initialize_attn_backend must before using self.attn_groups
3966-
self.initialize_attn_backend(kv_cache_config, is_profiling=is_profiling)
3960+
self.initialize_attn_backend(kv_cache_config)
39673961
self.use_hybrid_blocks = len(self.attn_groups) > 1
39683962
# NOTE: Currently, we determine whether we need `num_accepted_tokens` through `MambaSpec`.
39693963
self.need_accepted_tokens = any(
@@ -3989,7 +3983,7 @@ def initialize_kv_cache(
39893983
self.kernel_block_sizes, list) else self.kernel_block_sizes)
39903984
self.drafter.initialize_attn_backend(kv_cache_config, block_size)
39913985

3992-
if has_kv_transfer_group() and not is_profiling:
3986+
if has_kv_transfer_group():
39933987
get_kv_transfer_group().register_kv_caches(kv_caches)
39943988

39953989
if self.model_config.enable_return_routed_experts:
@@ -4846,11 +4840,7 @@ def may_reinitialize_input_batch(self, kv_cache_config: KVCacheConfig) -> None:
48464840
cp_kv_cache_interleave_size=self.parallel_config.cp_kv_cache_interleave_size,
48474841
)
48484842

4849-
def initialize_attn_backend(
4850-
self,
4851-
kv_cache_config: KVCacheConfig,
4852-
is_profiling: bool = False,
4853-
) -> None:
4843+
def initialize_attn_backend(self, kv_cache_config: KVCacheConfig) -> None:
48544844
"""
48554845
Initialize the attention backends and attention metadata builders.
48564846
"""
@@ -4912,11 +4902,7 @@ def create_attn_groups(
49124902
attention_backend_maps.append(attn_backends[0])
49134903
attention_backend_list.append(attn_backends[1])
49144904

4915-
self._check_and_update_cudagraph_mode(
4916-
attention_backend_list,
4917-
kv_cache_config.kv_cache_groups,
4918-
is_profiling=is_profiling,
4919-
)
4905+
self._check_and_update_cudagraph_mode(attention_backend_list, kv_cache_config.kv_cache_groups)
49204906

49214907
for i, attn_backend_map in enumerate(attention_backend_maps):
49224908
self.attn_groups.append(create_attn_groups(attn_backend_map, i))
@@ -5054,7 +5040,6 @@ def _check_and_update_cudagraph_mode(
50545040
self,
50555041
attention_backends: list[set[type[AttentionBackend]]],
50565042
kv_cache_groups: list[KVCacheGroupSpec],
5057-
is_profiling: bool = False,
50585043
) -> None:
50595044
min_cg_support = AttentionCGSupport.ALWAYS
50605045
min_cg_attn_backend = None
@@ -5079,7 +5064,6 @@ def _check_and_update_cudagraph_mode(
50795064
self.parallel_config.tensor_parallel_size,
50805065
self.kv_cache_config,
50815066
self.max_num_reqs,
5082-
is_profiling=is_profiling,
50835067
)
50845068
self.cudagraph_dispatcher.initialize_cudagraph_keys(
50855069
cudagraph_mode, self.uniform_decode_query_len
@@ -5108,35 +5092,11 @@ def _check_and_update_cudagraph_mode(
51085092

51095093
# NOTE: Since aclgraph_batch_sizes cannot be determined until here,
51105094
# we set the graph params right before initializing the keys.
5111-
# Profiling still runs real graph warmup/capture paths, so the NPU-side
5112-
# graph params must exist there as well.
51135095
if self.use_aclgraph:
51145096
set_graph_params(capture_sizes)
51155097
if self.speculative_config:
51165098
set_draft_graph_params(capture_sizes)
51175099

5118-
def profile_cudagraph_memory(self) -> int:
5119-
parent_module_name = _get_gpu_model_runner_module_name(self)
5120-
with _torch_cuda_wrapper(), _replace_gpu_model_runner_function_wrapper(parent_module_name):
5121-
result = GPUModelRunner.profile_cudagraph_memory(self)
5122-
5123-
reset_graph_params()
5124-
5125-
# NOTE: This is a serious problem that we maintain two extra copies of the KV cache as the instance
5126-
# variable of the attention layers, when they are local variables in the upstream vLLM code.
5127-
# We have to manually clear them here to release memory after profiling.
5128-
for layer in self.compilation_config.static_forward_context.values():
5129-
if hasattr(layer, "impl"):
5130-
if hasattr(layer.impl, "key_cache"):
5131-
layer.impl.key_cache = None
5132-
if hasattr(layer.impl, "value_cache"):
5133-
layer.impl.value_cache = None
5134-
5135-
gc.collect()
5136-
torch.accelerator.empty_cache()
5137-
5138-
return result
5139-
51405100
def capture_model(self) -> int:
51415101
"""Capture NPU graphs and return actual graph pool memory bytes consumed."""
51425102
parent_module_name = _get_gpu_model_runner_module_name(self)
@@ -5267,23 +5227,16 @@ def _replace_gpu_model_runner_function_wrapper(target_module_name):
52675227
_encoder_mgr_orig = _vllm_encoder_cudagraph.EncoderCudaGraphManager
52685228
_vllm_encoder_cudagraph.EncoderCudaGraphManager = EncoderAclGraphManager
52695229
target_module = None
5270-
original_attrs = {}
52715230
try:
52725231
target_module = sys.modules[target_module_name]
5273-
if hasattr(target_module, "graph_capture"):
5274-
original_attrs["graph_capture"] = target_module.graph_capture
52755232
setattr(target_module, "graph_capture", graph_capture) # noqa: B010
5276-
if hasattr(target_module, "CUDAGraphWrapper"):
5277-
original_attrs["CUDAGraphWrapper"] = target_module.CUDAGraphWrapper
5278-
setattr(target_module, "CUDAGraphWrapper", ACLGraphWrapper) # noqa: B010
52795233
yield
52805234
except Exception as e:
52815235
raise RuntimeError(f"NPUModelRunner failed, error is {e}")
52825236
finally:
52835237
_vllm_encoder_cudagraph.EncoderCudaGraphManager = _encoder_mgr_orig
52845238
if target_module is not None:
5285-
for attr_name, attr_value in original_attrs.items():
5286-
setattr(target_module, attr_name, attr_value) # noqa: B010
5239+
setattr(target_module, "graph_capture", graph_capture) # noqa: B010
52875240

52885241

52895242
# TODO: remove it when flash_comm1 is removed

0 commit comments

Comments
 (0)