A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 15, 16:00 UTC
[Misc] Enable V1 LoRA by default (#15320)
Signed-off-by: Varun Sundar Rabindranath <varun@neuralmagic.com> Co-authored-by: Varun Sundar Rabindranath <varun@neuralmagic.com>
Varun Sundar Rabindranath committed
Apr 1, 2025 at 04:53 UTC
79455cf42113efb026e39342997f7a878af8bc38
12 files changed
+125
-87
tests/entrypoints/openai/test_chat.py
+57
-3
@@ -24,7 +24,23 @@ GUIDED_DECODING_BACKENDS = ["outlines", "lm-format-enforcer", "xgrammar"]
24
25
26
@pytest.fixture(scope="module")
27
-def server(zephyr_lora_files, zephyr_lora_added_tokens_files): # noqa: F811
27
+def monkeypatch_module():
28
+ from _pytest.monkeypatch import MonkeyPatch
29
+ mpatch = MonkeyPatch()
30
+ yield mpatch
31
+ mpatch.undo()
32
+
33
+
34
+@pytest.fixture(scope="module", params=[False, True])
35
+def server(
36
+ request,
37
+ monkeypatch_module,
38
+ zephyr_lora_files, #noqa: F811
39
+ zephyr_lora_added_tokens_files): # noqa: F811
40
+
41
+ use_v1 = request.param
42
+ monkeypatch_module.setenv('VLLM_USE_V1', '1' if use_v1 else '0')
43
+
44
args = [
45
# use half precision for speed and memory savings in CI environment
46
"--dtype",
@@ -49,6 +65,13 @@ def server(zephyr_lora_files, zephyr_lora_added_tokens_files): # noqa: F811
65
yield remote_server
66
67
68
+@pytest.fixture
69
+def is_v1_server(server):
70
+ import os
71
+ assert os.environ['VLLM_USE_V1'] in ['0', '1']
72
+ return os.environ['VLLM_USE_V1'] == '1'
73
+
74
+
75
@pytest_asyncio.fixture
76
async def client(server):
77
async with server.get_async_client() as async_client:
@@ -471,8 +494,13 @@ async def test_chat_completion_stream_options(client: openai.AsyncOpenAI,
494
@pytest.mark.asyncio
495
@pytest.mark.parametrize("guided_decoding_backend", GUIDED_DECODING_BACKENDS)
496
async def test_guided_choice_chat(client: openai.AsyncOpenAI,
497
+ is_v1_server: bool,
498
guided_decoding_backend: str,
499
sample_guided_choice):
500
+
501
+ if is_v1_server and guided_decoding_backend != 'xgrammar':
502
+ pytest.skip("Only xgrammar backend is supported with V1")
503
+
504
messages = [{
505
"role": "system",
506
"content": "you are a helpful assistant"
@@ -511,9 +539,13 @@ async def test_guided_choice_chat(client: openai.AsyncOpenAI,
539
540
@pytest.mark.asyncio
541
@pytest.mark.parametrize("guided_decoding_backend", GUIDED_DECODING_BACKENDS)
514
-async def test_guided_json_chat(client: openai.AsyncOpenAI,
542
+async def test_guided_json_chat(client: openai.AsyncOpenAI, is_v1_server: bool,
543
guided_decoding_backend: str,
544
sample_json_schema):
545
+
546
+ if is_v1_server:
547
+ pytest.skip("sample_json_schema has features unsupported in V1")
548
+
549
messages = [{
550
"role": "system",
551
"content": "you are a helpful assistant"
@@ -559,7 +591,12 @@ async def test_guided_json_chat(client: openai.AsyncOpenAI,
591
@pytest.mark.asyncio
592
@pytest.mark.parametrize("guided_decoding_backend", GUIDED_DECODING_BACKENDS)
593
async def test_guided_regex_chat(client: openai.AsyncOpenAI,
594
+ is_v1_server: bool,
595
guided_decoding_backend: str, sample_regex):
596
+
597
+ if is_v1_server and guided_decoding_backend != 'xgrammar':
598
+ pytest.skip("Only xgrammar backend is supported with V1")
599
+
600
messages = [{
601
"role": "system",
602
"content": "you are a helpful assistant"
@@ -617,8 +654,13 @@ async def test_guided_decoding_type_error(client: openai.AsyncOpenAI):
654
@pytest.mark.asyncio
655
@pytest.mark.parametrize("guided_decoding_backend", GUIDED_DECODING_BACKENDS)
656
async def test_guided_choice_chat_logprobs(client: openai.AsyncOpenAI,
657
+ is_v1_server: bool,
658
guided_decoding_backend: str,
659
sample_guided_choice):
660
+
661
+ if is_v1_server and guided_decoding_backend != 'xgrammar':
662
+ pytest.skip("Only xgrammar backend is supported with V1")
663
+
664
messages = [{
665
"role": "system",
666
"content": "you are a helpful assistant"
@@ -648,9 +690,13 @@ async def test_guided_choice_chat_logprobs(client: openai.AsyncOpenAI,
690
691
@pytest.mark.asyncio
692
@pytest.mark.parametrize("guided_decoding_backend", GUIDED_DECODING_BACKENDS)
651
-async def test_named_tool_use(client: openai.AsyncOpenAI,
693
+async def test_named_tool_use(client: openai.AsyncOpenAI, is_v1_server: bool,
694
guided_decoding_backend: str,
695
sample_json_schema):
696
+
697
+ if is_v1_server:
698
+ pytest.skip("sample_json_schema has features unsupported on V1")
699
+
700
messages = [{
701
"role": "system",
702
"content": "you are a helpful assistant"
@@ -742,6 +788,10 @@ async def test_named_tool_use(client: openai.AsyncOpenAI,
788
@pytest.mark.asyncio
789
async def test_required_tool_use_not_yet_supported(client: openai.AsyncOpenAI,
790
sample_json_schema):
791
+
792
+ if is_v1_server:
793
+ pytest.skip("sample_json_schema has features unsupported on V1")
794
+
795
messages = [{
796
"role": "system",
797
"content": "you are a helpful assistant"
@@ -787,6 +837,10 @@ async def test_required_tool_use_not_yet_supported(client: openai.AsyncOpenAI,
837
@pytest.mark.asyncio
838
async def test_inconsistent_tool_choice_and_tools(client: openai.AsyncOpenAI,
839
sample_json_schema):
840
+
841
+ if is_v1_server:
842
+ pytest.skip("sample_json_schema has features unsupported on V1")
843
+
844
messages = [{
845
"role": "system",
846
"content": "you are a helpful assistant"
tests/lora/test_baichuan.py
+8
-8
@@ -11,6 +11,14 @@ MODEL_PATH = "baichuan-inc/Baichuan-7B"
11
PROMPT_TEMPLATE = """I want you to act as a SQL terminal in front of an example database, you need only to return the sql command to me.Below is an instruction that describes a task, Write a response that appropriately completes the request.\n"\n##Instruction:\nconcert_singer contains tables such as stadium, singer, concert, singer_in_concert. Table stadium has columns such as Stadium_ID, Location, Name, Capacity, Highest, Lowest, Average. Stadium_ID is the primary key.\nTable singer has columns such as Singer_ID, Name, Country, Song_Name, Song_release_year, Age, Is_male. Singer_ID is the primary key.\nTable concert has columns such as concert_ID, concert_Name, Theme, Stadium_ID, Year. concert_ID is the primary key.\nTable singer_in_concert has columns such as concert_ID, Singer_ID. concert_ID is the primary key.\nThe Stadium_ID of concert is the foreign key of Stadium_ID of stadium.\nThe Singer_ID of singer_in_concert is the foreign key of Singer_ID of singer.\nThe concert_ID of singer_in_concert is the foreign key of concert_ID of concert.\n\n###Input:\n{query}\n\n###Response:""" # noqa: E501
12
13
14
+@pytest.fixture(autouse=True)
15
+def v1(run_with_both_engines_lora):
16
+ # Simple autouse wrapper to run both engines for each test
17
+ # This can be promoted up to conftest.py to run for every
18
+ # test in a package
19
+ pass
20
+
21
+
22
def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
23
prompts = [
24
PROMPT_TEMPLATE.format(query="How many singers do we have?"),
@@ -40,14 +48,6 @@ def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
48
return generated_texts
49
50
43
-@pytest.fixture(autouse=True)
44
-def v1(run_with_both_engines_lora):
45
- # Simple autouse wrapper to run both engines for each test
46
- # This can be promoted up to conftest.py to run for every
47
- # test in a package
48
- pass
49
-
50
-
51
def test_baichuan_lora(baichuan_lora_files):
52
llm = vllm.LLM(MODEL_PATH,
53
max_model_len=1024,
tests/lora/test_chatglm3_tp.py
+8
-8
@@ -18,6 +18,14 @@ EXPECTED_LORA_OUTPUT = [
18
]
19
20
21
+@pytest.fixture(autouse=True)
22
+def v1(run_with_both_engines_lora):
23
+ # Simple autouse wrapper to run both engines for each test
24
+ # This can be promoted up to conftest.py to run for every
25
+ # test in a package
26
+ pass
27
+
28
+
29
def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
30
prompts = [
31
PROMPT_TEMPLATE.format(query="How many singers do we have?"),
@@ -46,14 +54,6 @@ def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
54
return generated_texts
55
56
49
-@pytest.fixture(autouse=True)
50
-def v1(run_with_both_engines_lora):
51
- # Simple autouse wrapper to run both engines for each test
52
- # This can be promoted up to conftest.py to run for every
53
- # test in a package
54
- pass
55
-
56
-
57
@create_new_process_for_each_test()
58
def test_chatglm3_lora(chatglm3_lora_files):
59
llm = vllm.LLM(MODEL_PATH,
tests/lora/test_gemma.py
+8
-8
@@ -9,6 +9,14 @@ from vllm.platforms import current_platform
9
MODEL_PATH = "google/gemma-7b"
10
11
12
+@pytest.fixture(autouse=True)
13
+def v1(run_with_both_engines_lora):
14
+ # Simple autouse wrapper to run both engines for each test
15
+ # This can be promoted up to conftest.py to run for every
16
+ # test in a package
17
+ pass
18
+
19
+
20
def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
21
prompts = [
22
"Quote: Imagination is",
@@ -31,14 +39,6 @@ def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
39
return generated_texts
40
41
34
-@pytest.fixture(autouse=True)
35
-def v1(run_with_both_engines_lora):
36
- # Simple autouse wrapper to run both engines for each test
37
- # This can be promoted up to conftest.py to run for every
38
- # test in a package
39
- pass
40
-
41
-
42
# The V1 lora test for this model requires more than 24GB.
43
@pytest.mark.skip_v1
44
@pytest.mark.xfail(current_platform.is_rocm(),
tests/lora/test_layers.py
-5
@@ -1,6 +1,5 @@
1
# SPDX-License-Identifier: Apache-2.0
2
3
-import importlib
3
import random
4
from copy import deepcopy
5
from dataclasses import dataclass
@@ -82,10 +81,6 @@ def v1(run_with_both_engines_lora):
81
# This can be promoted up to conftest.py to run for every
82
# test in a package
83
85
- # Reload punica_gpu as the kernels used are tied to engine type.
86
- from vllm.lora.punica_wrapper import punica_gpu
87
- importlib.reload(punica_gpu)
88
-
84
# Release any memory we might be holding on to. CI runs OOMs otherwise.
85
from vllm.lora.ops.triton_ops.utils import (_LORA_A_PTR_DICT,
86
_LORA_B_PTR_DICT)
tests/lora/test_llama_tp.py
+8
-12
@@ -28,6 +28,14 @@ EXPECTED_LORA_OUTPUT = [
28
]
29
30
31
+@pytest.fixture(autouse=True)
32
+def v1(run_with_both_engines_lora):
33
+ # Simple autouse wrapper to run both engines for each test
34
+ # This can be promoted up to conftest.py to run for every
35
+ # test in a package
36
+ pass
37
+
38
+
39
def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
40
prompts = [
41
"[user] Write a SQL query to answer the question based on the table schema.\n\n context: CREATE TABLE table_name_74 (icao VARCHAR, airport VARCHAR)\n\n question: Name the ICAO for lilongwe international airport [/user] [assistant]", # noqa: E501
@@ -71,16 +79,6 @@ def generate_and_test(llm, sql_lora_files):
79
print("removing lora")
80
81
74
-@pytest.fixture(autouse=True)
75
-def v1(run_with_both_engines_lora):
76
- # Simple autouse wrapper to run both engines for each test
77
- # This can be promoted up to conftest.py to run for every
78
- # test in a package
79
- pass
80
-
81
-
82
-# V1 Test: Failing due to numerics on V1.
83
-@pytest.mark.skip_v1
82
@create_new_process_for_each_test()
83
def test_llama_lora(sql_lora_files):
84
@@ -126,8 +124,6 @@ def test_llama_lora_warmup(sql_lora_files):
124
"less when using lora than when not using lora")
125
126
129
-# V1 Test: Failing due to numerics on V1.
130
-@pytest.mark.skip_v1
127
@multi_gpu_test(num_gpus=4)
128
@create_new_process_for_each_test()
129
def test_llama_lora_tp4(sql_lora_files):
tests/lora/test_lora_manager.py
+11
-3
@@ -7,7 +7,6 @@ import torch
7
from safetensors.torch import load_file
8
from torch import nn
9
10
-from vllm import envs
10
from vllm.config import LoRAConfig
11
from vllm.lora.layers import (ColumnParallelLinearWithLoRA,
12
MergedColumnParallelLinearWithLoRA,
@@ -33,6 +32,17 @@ DEVICES = ([
32
] if current_platform.is_cuda_alike() else ["cpu"])
33
34
35
+@pytest.fixture(scope="function", autouse=True)
36
+def use_v0_only(monkeypatch: pytest.MonkeyPatch):
37
+ """
38
+ Some tests depend on V0 internals. Since both V0 and V1 use the same
39
+ LoRAModelManager it is okay to just test V0.
40
+ """
41
+ with monkeypatch.context() as m:
42
+ m.setenv('VLLM_USE_V1', '0')
43
+ yield
44
+
45
+
46
@pytest.mark.parametrize("device", DEVICES)
47
def test_from_lora_tensors(sql_lora_files, device):
48
tensors = load_file(
@@ -411,7 +421,6 @@ def test_lru_lora_model_manager(dist_init, dummy_model, device):
421
assert manager.device == device
422
423
414
-@pytest.mark.skipif(envs.VLLM_USE_V1, reason="Test leverages V0 internals.")
424
@pytest.mark.parametrize("device", DEVICES)
425
def test_lru_cache_worker_adapter_manager(llama_2_7b_model_extra_embeddings,
426
sql_lora_files, device):
@@ -491,7 +500,6 @@ def test_lru_cache_worker_adapter_manager(llama_2_7b_model_extra_embeddings,
500
device)
501
502
494
-@pytest.mark.skipif(envs.VLLM_USE_V1, reason="Test leverages V0 internals.")
503
@pytest.mark.parametrize("device", DEVICES)
504
def test_worker_adapter_manager(llama_2_7b_model_extra_embeddings,
505
sql_lora_files, device):
tests/lora/test_phi.py
+8
-8
@@ -10,6 +10,14 @@ MODEL_PATH = "microsoft/phi-2"
10
PROMPT_TEMPLATE = "### Instruct: {sql_prompt}\n\n### Context: {context}\n\n### Output:" # noqa: E501
11
12
13
+@pytest.fixture(autouse=True)
14
+def v1(run_with_both_engines_lora):
15
+ # Simple autouse wrapper to run both engines for each test
16
+ # This can be promoted up to conftest.py to run for every
17
+ # test in a package
18
+ pass
19
+
20
+
21
def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
22
prompts = [
23
PROMPT_TEMPLATE.format(
@@ -48,14 +56,6 @@ def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
56
return generated_texts
57
58
51
-@pytest.fixture(autouse=True)
52
-def v1(run_with_both_engines_lora):
53
- # Simple autouse wrapper to run both engines for each test
54
- # This can be promoted up to conftest.py to run for every
55
- # test in a package
56
- pass
57
-
58
-
59
# Skipping for V1 for now as we are hitting,
60
# "Head size 80 is not supported by FlashAttention." error.
61
@pytest.mark.skip_v1
tests/lora/test_quant_model.py
+8
-8
@@ -37,6 +37,14 @@ else:
37
]
38
39
40
+@pytest.fixture(autouse=True)
41
+def v1(run_with_both_engines_lora):
42
+ # Simple autouse wrapper to run both engines for each test
43
+ # This can be promoted up to conftest.py to run for every
44
+ # test in a package
45
+ pass
46
+
47
+
48
def do_sample(llm: vllm.LLM,
49
lora_path: str,
50
lora_id: int,
@@ -69,14 +77,6 @@ def do_sample(llm: vllm.LLM,
77
return generated_texts
78
79
72
-@pytest.fixture(autouse=True)
73
-def v1(run_with_both_engines_lora):
74
- # Simple autouse wrapper to run both engines for each test
75
- # This can be promoted up to conftest.py to run for every
76
- # test in a package
77
- pass
78
-
79
-
80
@pytest.mark.parametrize("model", MODELS)
81
@pytest.mark.parametrize("tp_size", [1])
82
def test_quant_model_lora(tinyllama_lora_files, num_gpus_available, model,
tests/lora/test_transfomers_model.py
+8
-11
@@ -18,6 +18,14 @@ EXPECTED_LORA_OUTPUT = [
18
]
19
20
21
+@pytest.fixture(autouse=True)
22
+def v1(run_with_both_engines_lora):
23
+ # Simple autouse wrapper to run both engines for each test
24
+ # This can be promoted up to conftest.py to run for every
25
+ # test in a package
26
+ pass
27
+
28
+
29
def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
30
prompts = [
31
PROMPT_TEMPLATE.format(query="How many singers do we have?"),
@@ -46,15 +54,6 @@ def do_sample(llm: vllm.LLM, lora_path: str, lora_id: int) -> list[str]:
54
return generated_texts
55
56
49
-@pytest.fixture(autouse=True)
50
-def v1(run_with_both_engines_lora):
51
- # Simple autouse wrapper to run both engines for each test
52
- # This can be promoted up to conftest.py to run for every
53
- # test in a package
54
- pass
55
-
56
-
57
-@pytest.mark.skip_v1
57
@create_new_process_for_each_test()
58
def test_ilama_lora(ilama_lora_files):
59
llm = vllm.LLM(MODEL_PATH,
@@ -74,7 +73,6 @@ def test_ilama_lora(ilama_lora_files):
73
assert output2[i] == EXPECTED_LORA_OUTPUT[i]
74
75
77
-@pytest.mark.skip_v1
76
@multi_gpu_test(num_gpus=4)
77
@create_new_process_for_each_test()
78
def test_ilama_lora_tp4(ilama_lora_files):
@@ -96,7 +94,6 @@ def test_ilama_lora_tp4(ilama_lora_files):
94
assert output2[i] == EXPECTED_LORA_OUTPUT[i]
95
96
99
-@pytest.mark.skip_v1
97
@multi_gpu_test(num_gpus=4)
98
@create_new_process_for_each_test()
99
def test_ilama_lora_tp4_fully_sharded_loras(ilama_lora_files):
tests/v1/test_oracle.py
+1
-9
@@ -104,14 +104,6 @@ def test_enable_by_default_fallback(monkeypatch):
104
assert envs.VLLM_USE_V1
105
m.delenv("VLLM_USE_V1")
106
107
- # Should fall back to V0 for experimental config.
108
- _ = AsyncEngineArgs(
109
- model=MODEL,
110
- enable_lora=True,
111
- ).create_engine_config()
112
- assert not envs.VLLM_USE_V1
113
- m.delenv("VLLM_USE_V1")
114
-
107
# Should fall back to V0 for supported model.
108
_ = AsyncEngineArgs(
109
model=UNSUPPORTED_MODELS_V1[0]).create_engine_config()
@@ -125,7 +117,7 @@ def test_v1_llm_by_default(monkeypatch):
117
m.delenv("VLLM_USE_V1")
118
119
# Should default to V1 for supported config.
128
- model = LLM(MODEL, enforce_eager=True)
120
+ model = LLM(MODEL, enforce_eager=True, enable_lora=True)
121
print(model.generate("Hello my name is"))
122
assert hasattr(model.llm_engine, "engine_core")
123
m.delenv("VLLM_USE_V1")
vllm/engine/arg_utils.py
-4
@@ -1512,10 +1512,6 @@ class EngineArgs:
1512
and _warn_or_fallback("Engine in background thread")):
1513
return False
1514
1515
- # LoRA is supported on V1, but off by default for now.
1516
- if self.enable_lora and _warn_or_fallback("LORA"):
1517
- return False
1518
-
1515
# PP is supported on V1 with Ray distributed executor,
1516
# but off for MP distributed executor for now.
1517
if (self.pipeline_parallel_size > 1