Signed-off-by: 浩韵 <zuohaiyu.zhy@alibaba-inc.com> Co-authored-by: Kevin H. Luu <khluu000@gmail.com>
103 lines
3.3 KiB
Python
103 lines
3.3 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""The `get_language_model` cache must not own the models it keys on.
|
|
|
|
A strong-keyed module-level dict pins every model ever loaded in the process
|
|
for the life of the interpreter. In-process teardown
|
|
(``VLLM_ENABLE_V1_MULTIPROCESSING=0``) then keeps the weights -- and the KV
|
|
cache aliased onto the model's layers -- resident after engine shutdown.
|
|
"""
|
|
|
|
import gc
|
|
import weakref
|
|
|
|
import pytest
|
|
import torch
|
|
import torch.nn as nn
|
|
|
|
from vllm.model_executor.models.interfaces import (
|
|
SupportsMultiModal,
|
|
_language_model_by_module,
|
|
)
|
|
|
|
pytestmark = pytest.mark.cpu_test
|
|
|
|
|
|
class _LanguageModel(nn.Module):
|
|
def embed_input_ids(self, input_ids): # pragma: no cover - never called
|
|
raise NotImplementedError
|
|
|
|
|
|
class _MultiModalModel(nn.Module):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.language_model = _LanguageModel()
|
|
|
|
|
|
def test_cache_is_weak_keyed():
|
|
assert isinstance(_language_model_by_module, weakref.WeakKeyDictionary)
|
|
|
|
|
|
def test_cached_entry_does_not_pin_the_model():
|
|
model = _MultiModalModel()
|
|
_language_model_by_module[model] = model.language_model
|
|
ref = weakref.ref(model)
|
|
|
|
del model
|
|
gc.collect()
|
|
|
|
assert ref() is None, (
|
|
"the get_language_model cache is keeping the model alive; in-process "
|
|
"engine shutdown will not release its weights or KV cache"
|
|
)
|
|
|
|
|
|
def test_cached_entry_is_dropped_with_its_key():
|
|
model = _MultiModalModel()
|
|
_language_model_by_module[model] = model.language_model
|
|
assert model in _language_model_by_module
|
|
|
|
del model
|
|
gc.collect()
|
|
|
|
assert not any(isinstance(k, _MultiModalModel) for k in _language_model_by_module)
|
|
|
|
|
|
@pytest.mark.parametrize("marked", [False, True])
|
|
@pytest.mark.parametrize("text_model", ["clip", "siglip"])
|
|
def test_text_tower_without_loader_hook_is_found(text_model, marked, caplog):
|
|
from vllm.model_executor.models.clip import CLIPTextTransformer
|
|
from vllm.model_executor.models.siglip import SiglipTextTransformer
|
|
|
|
cls = CLIPTextTransformer if text_model == "clip" else SiglipTextTransformer
|
|
# Only token embeddings are needed to exercise text tower discovery.
|
|
tower = cls.__new__(cls)
|
|
nn.Module.__init__(tower)
|
|
tower.embeddings = nn.Module()
|
|
tower.embeddings.token_embedding = nn.Embedding(4, 2)
|
|
model = nn.Module()
|
|
model.text_model = tower
|
|
model._language_model_names = ["text_model"] if marked else []
|
|
|
|
caplog.clear()
|
|
language_model = SupportsMultiModal.get_language_model(model)
|
|
|
|
assert language_model is tower
|
|
input_ids = torch.tensor([0, 1])
|
|
torch.testing.assert_close(
|
|
language_model.embed_input_ids(input_ids),
|
|
tower.embeddings.token_embedding.weight[:2],
|
|
)
|
|
assert not caplog.records
|
|
|
|
|
|
@pytest.mark.parametrize("marked", [False, True])
|
|
@pytest.mark.parametrize("method", ["embed_input_ids", "forward"])
|
|
def test_language_model_discovery_rejects_noncallable_methods(method, marked):
|
|
model = _MultiModalModel()
|
|
model._language_model_names = ["language_model"] if marked else []
|
|
setattr(model.language_model, method, None)
|
|
|
|
with pytest.raises(NotImplementedError, match="No language model found"):
|
|
SupportsMultiModal.get_language_model(model)
|