Skip to content

Commit 020de70

Browse files
committed
make sure the memory of the LLM is freed (before the judge LLM is eventually called)
1 parent b42fa89 commit 020de70

1 file changed

Lines changed: 60 additions & 2 deletions

File tree

src/lighteval/models/vllm/vllm_model.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import itertools
2626
import logging
2727
import os
28+
import time
2829
from typing import Coroutine, Optional
2930

3031
import torch
@@ -252,14 +253,71 @@ def tokenizer(self):
252253
return self._tokenizer
253254

254255
def cleanup(self):
255-
destroy_model_parallel()
256+
# Explicitly shut down the vLLM engine so that its GPU memory is released before
257+
# any subsequently loaded engine (e.g. the vLLM LLM-as-judge used by some metrics)
258+
# tries to allocate. Relying on `del` alone is not enough: with the vLLM V1 engine
259+
# the worker keeps the allocation until the engine core is shut down, and that
260+
# teardown can be asynchronous -- so we also wait until the memory is reclaimed.
256261
if self.model is not None:
257-
del self.model
262+
try:
263+
engine_core = getattr(getattr(self.model, "llm_engine", None), "engine_core", None)
264+
if engine_core is not None and hasattr(engine_core, "shutdown"):
265+
engine_core.shutdown()
266+
else:
267+
logger.warning(
268+
"Could not find a vLLM engine_core to shut down explicitly "
269+
"(V0 engine or unsupported vLLM version). GPU memory may not be "
270+
"released before the next engine loads."
271+
)
272+
except Exception as e:
273+
logger.warning(f"Could not explicitly shut down the vLLM engine: {e}")
274+
275+
destroy_model_parallel()
276+
self.model = None # drops the only strong reference to the LLM object
258277
gc.collect()
259278
ray.shutdown()
260279
destroy_distributed_environment()
261280
torch.cuda.empty_cache()
262281

282+
# Wait until the GPU memory is actually freed (engine teardown may be asynchronous),
283+
# so the next engine to load sees a clean device instead of failing to allocate.
284+
# The free-memory heuristic does not work when the GPU is shared with another process
285+
# or on hardware with unified memory (e.g. DGX Spark). Set
286+
# LIGHTEVAL_VLLM_SKIP_MEMORY_WAIT=1 to disable the wait in those cases.
287+
if os.environ.get("LIGHTEVAL_VLLM_SKIP_MEMORY_WAIT"):
288+
return
289+
if not torch.cuda.is_available():
290+
return
291+
292+
timeout_s = 60
293+
threshold = 0.7
294+
device_count = torch.cuda.device_count()
295+
per_device = []
296+
for _ in range(timeout_s):
297+
per_device = [torch.cuda.mem_get_info(d) for d in range(device_count)]
298+
valid = [(free, total) for free, total in per_device if total > 0]
299+
if not valid:
300+
return
301+
# Use the *minimum* free-ratio across devices so we do not exit early when
302+
# only device 0 has been reclaimed while a tensor-parallel peer is still busy.
303+
if min(free / total for free, total in valid) > threshold:
304+
return
305+
gc.collect()
306+
torch.cuda.empty_cache()
307+
time.sleep(1)
308+
309+
usage_str = ", ".join(
310+
f"GPU{d}: {free / total:.0%} free" if total > 0 else f"GPU{d}: n/a"
311+
for d, (free, total) in enumerate(per_device)
312+
)
313+
logger.warning(
314+
f"vLLM GPU memory was not fully reclaimed within {timeout_s}s after engine "
315+
f"shutdown (threshold {threshold:.0%} free; current state: {usage_str}). "
316+
"The next engine to load may OOM. "
317+
"To skip this wait (e.g. on a GPU shared with another process, or on hardware "
318+
"with unified memory like DGX Spark), set LIGHTEVAL_VLLM_SKIP_MEMORY_WAIT=1."
319+
)
320+
263321
@property
264322
def add_special_tokens(self):
265323
return self._add_special_tokens

0 commit comments

Comments
 (0)