@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / e19bce40a1
Fork 0

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 14, 05:30 UTC

[V0 Deprecation] Remove AsyncLLMEngine (#25025)

Signed-off-by: Woosuk Kwon <woosuk@thinkingmachines.ai> Signed-off-by: Woosuk Kwon <woosuk.kwon@berkeley.edu>

Woosuk Kwon committed Sep 18, 2025 at 11:07 UTC e19bce40a1660cb7c03b790d0b000db155cf925d
11 files changed +78 -1969
tests/entrypoints/openai/test_chat.py
+19 -35
@@ -28,11 +28,9 @@ def monkeypatch_module():
28 mpatch.undo()
29
30
31 -@pytest.fixture(scope="module", params=[False, True])
32 -def server(request, monkeypatch_module, zephyr_lora_files): #noqa: F811
33 -
34 - use_v1 = request.param
35 - monkeypatch_module.setenv('VLLM_USE_V1', '1' if use_v1 else '0')
31 +@pytest.fixture(scope="module")
32 +def server(monkeypatch_module, zephyr_lora_files): #noqa: F811
33 + monkeypatch_module.setenv('VLLM_USE_V1', '1')
34
35 args = [
36 # use half precision for speed and memory savings in CI environment
@@ -57,13 +55,6 @@ def server(request, monkeypatch_module, zephyr_lora_files): #noqa: F811
55 yield remote_server
56
57
60 -@pytest.fixture
61 -def is_v1_server(server):
62 - import os
63 - assert os.environ['VLLM_USE_V1'] in ['0', '1']
64 - return os.environ['VLLM_USE_V1'] == '1'
65 -
66 -
58 @pytest_asyncio.fixture
59 async def client(server):
60 async with server.get_async_client() as async_client:
@@ -481,10 +472,9 @@ async def test_chat_completion_stream_options(client: openai.AsyncOpenAI,
472
473 @pytest.mark.asyncio
474 async def test_structured_outputs_choice_chat(
484 - client: openai.AsyncOpenAI, sample_structured_outputs_choices,
485 - is_v1_server: bool):
486 - if not is_v1_server:
487 - pytest.skip("Structured outputs is only supported in v1 engine")
475 + client: openai.AsyncOpenAI,
476 + sample_structured_outputs_choices,
477 +):
478 messages = [{
479 "role": "system",
480 "content": "you are a helpful assistant"
@@ -522,12 +512,10 @@ async def test_structured_outputs_choice_chat(
512
513
514 @pytest.mark.asyncio
525 -async def test_structured_outputs_json_chat(client: openai.AsyncOpenAI,
526 - sample_json_schema,
527 - is_v1_server: bool):
528 - if not is_v1_server:
529 - pytest.skip("Structured outputs is only supported in v1 engine")
530 -
515 +async def test_structured_outputs_json_chat(
516 + client: openai.AsyncOpenAI,
517 + sample_json_schema,
518 +):
519 messages = [{
520 "role": "system",
521 "content": "you are a helpful assistant"
@@ -569,10 +557,10 @@ async def test_structured_outputs_json_chat(client: openai.AsyncOpenAI,
557
558
559 @pytest.mark.asyncio
572 -async def test_structured_outputs_regex_chat(client: openai.AsyncOpenAI,
573 - sample_regex, is_v1_server: bool):
574 - if not is_v1_server:
575 - pytest.skip("Structured outputs is only supported in v1 engine")
560 +async def test_structured_outputs_regex_chat(
561 + client: openai.AsyncOpenAI,
562 + sample_regex,
563 +):
564
565 messages = [{
566 "role": "system",
@@ -660,10 +648,10 @@ async def test_structured_outputs_choice_chat_logprobs(
648
649
650 @pytest.mark.asyncio
663 -async def test_named_tool_use(client: openai.AsyncOpenAI, sample_json_schema,
664 - is_v1_server: bool):
665 - if not is_v1_server:
666 - pytest.skip("Tool use is only supported in v1 engine")
651 +async def test_named_tool_use(
652 + client: openai.AsyncOpenAI,
653 + sample_json_schema,
654 +):
655 messages = [{
656 "role": "system",
657 "content": "you are a helpful assistant"
@@ -821,11 +809,7 @@ async def test_response_format_json_object(client: openai.AsyncOpenAI):
809
810
811 @pytest.mark.asyncio
824 -async def test_response_format_json_schema(client: openai.AsyncOpenAI,
825 - is_v1_server: bool):
826 - if not is_v1_server:
827 - pytest.skip(
828 - "JSON schema response format is only supported in v1 engine")
812 +async def test_response_format_json_schema(client: openai.AsyncOpenAI):
813 prompt = 'what is 1+1? The format is "result": 2'
814 # Check that this prompt cannot lead to a valid JSON without json_schema
815 for _ in range(2):
tests/entrypoints/openai/test_completion.py deleted
-830
@@ -1,830 +0,0 @@
1 -# SPDX-License-Identifier: Apache-2.0
2 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 -# imports for structured outputs tests
4 -import json
5 -import os
6 -from typing import Optional
7 -
8 -import jsonschema
9 -import openai # use the official client for correctness check
10 -import pytest
11 -import pytest_asyncio
12 -import regex as re
13 -import requests
14 -# downloading lora to test lora requests
15 -from openai import BadRequestError
16 -
17 -from vllm.transformers_utils.tokenizer import get_tokenizer
18 -
19 -from ...utils import RemoteOpenAIServer
20 -
21 -# any model with a chat template should work here
22 -MODEL_NAME = "HuggingFaceH4/zephyr-7b-beta"
23 -# technically these adapters use a different base model,
24 -# but we're not testing generation quality here
25 -
26 -
27 -@pytest.fixture(scope="module")
28 -def default_server_args(zephyr_lora_files):
29 - return [
30 - # use half precision for speed and memory savings in CI environment
31 - "--dtype",
32 - "bfloat16",
33 - "--max-model-len",
34 - "8192",
35 - "--max-num-seqs",
36 - "128",
37 - "--enforce-eager",
38 - # lora config
39 - "--enable-lora",
40 - "--lora-modules",
41 - f"zephyr-lora={zephyr_lora_files}",
42 - "--max-lora-rank",
43 - "64",
44 - "--max-cpu-loras",
45 - "2",
46 - ]
47 -
48 -
49 -@pytest.fixture(scope="module",
50 - params=["", "--disable-frontend-multiprocessing"])
51 -def server(default_server_args, request):
52 - if request.param:
53 - default_server_args.append(request.param)
54 -
55 - original_value = os.environ.get('VLLM_USE_V1')
56 - os.environ['VLLM_USE_V1'] = '0'
57 - try:
58 - with RemoteOpenAIServer(MODEL_NAME,
59 - default_server_args) as remote_server:
60 - yield remote_server
61 - finally:
62 - # Restore original env value
63 - if original_value is None:
64 - os.environ.pop('VLLM_USE_V1', None)
65 - else:
66 - os.environ['VLLM_USE_V1'] = original_value
67 -
68 -
69 -@pytest.fixture
70 -def is_v1_server(server):
71 - import os
72 -
73 - # For completion tests, we assume v0 since there's no explicit v1 setup
74 - return os.environ.get('VLLM_USE_V1', '0') == '1'
75 -
76 -
77 -@pytest_asyncio.fixture
78 -async def client(server):
79 - async with server.get_async_client() as async_client:
80 - yield async_client
81 -
82 -
83 -@pytest.mark.asyncio
84 -@pytest.mark.parametrize(
85 - # first test base model, then test loras
86 - "model_name",
87 - [MODEL_NAME, "zephyr-lora"],
88 -)
89 -async def test_single_completion(client: openai.AsyncOpenAI, model_name: str):
90 - completion = await client.completions.create(model=model_name,
91 - prompt="Hello, my name is",
92 - max_tokens=5,
93 - temperature=0.0)
94 -
95 - assert completion.id is not None
96 - assert completion.choices is not None and len(completion.choices) == 1
97 -
98 - choice = completion.choices[0]
99 - assert len(choice.text) >= 5
100 - assert choice.finish_reason == "length"
101 - assert completion.usage == openai.types.CompletionUsage(
102 - completion_tokens=5, prompt_tokens=6, total_tokens=11)
103 -
104 - # test using token IDs
105 - completion = await client.completions.create(
106 - model=model_name,
107 - prompt=[0, 0, 0, 0, 0],
108 - max_tokens=5,
109 - temperature=0.0,
110 - )
111 - assert len(completion.choices[0].text) >= 1
112 - assert completion.choices[0].prompt_logprobs is None
113 -
114 -
115 -@pytest.mark.asyncio
116 -async def test_added_lora_tokens_base_model(client: openai.AsyncOpenAI):
117 - # test using token IDs
118 - with pytest.raises(openai.BadRequestError, match="out of vocabulary"):
119 - # Added tokens should be rejected by the base model
120 - await client.completions.create(
121 - model=MODEL_NAME,
122 - prompt=[0, 0, 32000, 32001, 32002],
123 - echo=True,
124 - max_tokens=5,
125 - temperature=0.0,
126 - )
127 -
128 -
129 -@pytest.mark.asyncio
130 -@pytest.mark.parametrize(
131 - # first test base model, then test loras
132 - "model_name",
133 - [MODEL_NAME, "zephyr-lora"],
134 -)
135 -async def test_no_logprobs(client: openai.AsyncOpenAI, model_name: str):
136 - # test using token IDs
137 - completion = await client.completions.create(
138 - model=model_name,
139 - prompt=[0, 0, 0, 0, 0],
140 - max_tokens=5,
141 - temperature=0.0,
142 - logprobs=None,
143 - )
144 - choice = completion.choices[0]
145 - assert choice.logprobs is None
146 -
147 -
148 -@pytest.mark.asyncio
149 -@pytest.mark.parametrize(
150 - # just test 1 lora
151 - "model_name",
152 - [MODEL_NAME, "zephyr-lora"],
153 -)
154 -async def test_zero_logprobs(client: openai.AsyncOpenAI, model_name: str):
155 - # test using token IDs
156 - completion = await client.completions.create(
157 - model=model_name,
158 - prompt=[0, 0, 0, 0, 0],
159 - max_tokens=5,
160 - temperature=0.0,
161 - logprobs=0,
162 - )
163 - choice = completion.choices[0]
164 - assert choice.logprobs is not None
165 - assert choice.logprobs.token_logprobs is not None
166 - assert choice.logprobs.top_logprobs is not None
167 - assert len(choice.logprobs.top_logprobs[0]) == 1
168 -
169 -
170 -@pytest.mark.asyncio
171 -@pytest.mark.parametrize(
172 - "model_name",
173 - [MODEL_NAME, "zephyr-lora"],
174 -)
175 -async def test_some_logprobs(client: openai.AsyncOpenAI, model_name: str):
176 - # test using token IDs
177 - completion = await client.completions.create(
178 - model=model_name,
179 - prompt=[0, 0, 0, 0, 0],
180 - max_tokens=5,
181 - temperature=0.0,
182 - logprobs=5,
183 - )
184 - choice = completion.choices[0]
185 - assert choice.logprobs is not None
186 - assert choice.logprobs.token_logprobs is not None
187 - assert choice.logprobs.top_logprobs is not None
188 - assert 5 <= len(choice.logprobs.top_logprobs[0]) <= 6
189 -
190 -
191 -@pytest.mark.asyncio
192 -@pytest.mark.parametrize(
193 - "model_name",
194 - [MODEL_NAME, "zephyr-lora"],
195 -)
196 -async def test_too_many_completion_logprobs(client: openai.AsyncOpenAI,
197 - model_name: str):
198 -
199 - with pytest.raises(
200 - (openai.BadRequestError, openai.APIError)): # test using token IDs
201 - await client.completions.create(
202 - model=model_name,
203 - prompt=[0, 0, 0, 0, 0],
204 - max_tokens=5,
205 - temperature=0.0,
206 - # vLLM has higher default max_logprobs (20 instead of 5) to support
207 - # both Completion API and Chat Completion API
208 - logprobs=21,
209 - )
210 - ...
211 - with pytest.raises(
212 - (openai.BadRequestError, openai.APIError)): # test using token IDs
213 - stream = await client.completions.create(
214 - model=model_name,
215 - prompt=[0, 0, 0, 0, 0],
216 - max_tokens=5,
217 - temperature=0.0,
218 - # vLLM has higher default max_logprobs (20 instead of 5) to support
219 - # both Completion API and Chat Completion API
220 - logprobs=30,
221 - stream=True,
222 - )
223 - async for chunk in stream:
224 - ...
225 -
226 - # the server should still work afterwards
227 - completion = await client.completions.create(
228 - model=model_name,
229 - prompt=[0, 0, 0, 0, 0],
230 - max_tokens=5,
231 - temperature=0.0,
232 - )
233 - assert len(completion.choices[0].text) >= 0
234 -
235 -
236 -@pytest.mark.asyncio
237 -@pytest.mark.parametrize("model_name, prompt_logprobs", [(MODEL_NAME, -1),
238 - (MODEL_NAME, 0),
239 - (MODEL_NAME, 1),
240 - (MODEL_NAME, None)])
241 -async def test_prompt_logprobs_completion(client: openai.AsyncOpenAI,
242 - model_name: str,
243 - prompt_logprobs: Optional[int]):
244 - params: dict = {
245 - "prompt": ["A robot may not injure another robot", "My name is"],
246 - "model": model_name,
247 - }
248 - if prompt_logprobs is not None:
249 - params["extra_body"] = {"prompt_logprobs": prompt_logprobs}
250 -
251 - if prompt_logprobs is not None and prompt_logprobs < 0:
252 - with pytest.raises(BadRequestError):
253 - await client.completions.create(**params)
254 - else:
255 - completion = await client.completions.create(**params)
256 - if prompt_logprobs is not None:
257 - assert completion.choices[0].prompt_logprobs is not None
258 - assert len(completion.choices[0].prompt_logprobs) > 0
259 -
260 - assert completion.choices[1].prompt_logprobs is not None
261 - assert len(completion.choices[1].prompt_logprobs) > 0
262 -
263 - else:
264 - assert completion.choices[0].prompt_logprobs is None
265 -
266 -
267 -@pytest.mark.asyncio
268 -@pytest.mark.parametrize(
269 - "model_name",
270 - [MODEL_NAME, "zephyr-lora"],
271 -)
272 -async def test_completion_streaming(client: openai.AsyncOpenAI,
273 - model_name: str):
274 - prompt = "What is an LLM?"
275 -
276 - single_completion = await client.completions.create(
277 - model=model_name,
278 - prompt=prompt,
279 - max_tokens=5,
280 - temperature=0.0,
281 - )
282 - single_output = single_completion.choices[0].text
283 - stream = await client.completions.create(model=model_name,
284 - prompt=prompt,
285 - max_tokens=5,
286 - temperature=0.0,
287 - stream=True)
288 - chunks: list[str] = []
289 - finish_reason_count = 0
290 - async for chunk in stream:
291 - chunks.append(chunk.choices[0].text)
292 - if chunk.choices[0].finish_reason is not None:
293 - finish_reason_count += 1
294 - # finish reason should only return in last block
295 - assert finish_reason_count == 1
296 - assert chunk.choices[0].finish_reason == "length"
297 - assert chunk.choices[0].text
298 - assert "".join(chunks) == single_output
299 -
300 -
301 -@pytest.mark.asyncio
302 -@pytest.mark.parametrize(
303 - "model_name",
304 - [MODEL_NAME, "zephyr-lora"],
305 -)
306 -async def test_parallel_streaming(client: openai.AsyncOpenAI, model_name: str):
307 - """Streaming for parallel sampling.
308 - The tokens from multiple samples, are flattened into a single stream,
309 - with an index to indicate which sample the token belongs to.
310 - """
311 -
312 - prompt = "What is an LLM?"
313 - n = 3
314 - max_tokens = 5
315 -
316 - stream = await client.completions.create(model=model_name,
317 - prompt=prompt,
318 - max_tokens=max_tokens,
319 - n=n,
320 - stream=True)
321 - chunks: list[list[str]] = [[] for i in range(n)]
322 - finish_reason_count = 0
323 - async for chunk in stream:
324 - index = chunk.choices[0].index
325 - text = chunk.choices[0].text
326 - chunks[index].append(text)
327 - if chunk.choices[0].finish_reason is not None:
328 - finish_reason_count += 1
329 - assert finish_reason_count == n
330 - for chunk in chunks:
331 - assert len(chunk) == max_tokens
332 - print("".join(chunk))
333 -
334 -
335 -@pytest.mark.asyncio
336 -@pytest.mark.parametrize(
337 - "model_name",
338 - [MODEL_NAME, "zephyr-lora"],
339 -)
340 -async def test_completion_stream_options(client: openai.AsyncOpenAI,
341 - model_name: str):
342 - prompt = "What is the capital of France?"
343 -
344 - # Test stream=True, stream_options=
345 - # {"include_usage": False, "continuous_usage_stats": False}
346 - stream = await client.completions.create(model=model_name,
347 - prompt=prompt,
348 - max_tokens=5,
349 - temperature=0.0,
350 - stream=True,
351 - stream_options={
352 - "include_usage": False,
353 - "continuous_usage_stats":
354 - False,
355 - })
356 -
357 - async for chunk in stream:
358 - assert chunk.usage is None
359 -
360 - # Test stream=True, stream_options=
361 - # {"include_usage": False, "continuous_usage_stats": True}
362 - stream = await client.completions.create(model=model_name,
363 - prompt=prompt,
364 - max_tokens=5,
365 - temperature=0.0,
366 - stream=True,
367 - stream_options={
368 - "include_usage": False,
369 - "continuous_usage_stats":
370 - True,
371 - })
372 - async for chunk in stream:
373 - assert chunk.usage is None
374 -
375 - # Test stream=True, stream_options=
376 - # {"include_usage": True, "continuous_usage_stats": False}
377 - stream = await client.completions.create(model=model_name,
378 - prompt=prompt,
379 - max_tokens=5,
380 - temperature=0.0,
381 - stream=True,
382 - stream_options={
383 - "include_usage": True,
384 - "continuous_usage_stats":
385 - False,
386 - })
387 - async for chunk in stream:
388 - if chunk.choices[0].finish_reason is None:
389 - assert chunk.usage is None
390 - else:
391 - assert chunk.usage is None
392 - final_chunk = await stream.__anext__()
393 - assert final_chunk.usage is not None
394 - assert final_chunk.usage.prompt_tokens > 0
395 - assert final_chunk.usage.completion_tokens > 0
396 - assert final_chunk.usage.total_tokens == (
397 - final_chunk.usage.prompt_tokens +
398 - final_chunk.usage.completion_tokens)
399 - assert final_chunk.choices == []
400 -
401 - # Test stream=True, stream_options=
402 - # {"include_usage": True, "continuous_usage_stats": True}
403 - stream = await client.completions.create(model=model_name,
404 - prompt=prompt,
405 - max_tokens=5,
406 - temperature=0.0,
407 - stream=True,
408 - stream_options={
409 - "include_usage": True,
410 - "continuous_usage_stats":
411 - True,
412 - })
413 - async for chunk in stream:
414 - assert chunk.usage is not None
415 - assert chunk.usage.prompt_tokens > 0
416 - assert chunk.usage.completion_tokens > 0
417 - assert chunk.usage.total_tokens == (chunk.usage.prompt_tokens +
418 - chunk.usage.completion_tokens)
419 - if chunk.choices[0].finish_reason is not None:
420 - final_chunk = await stream.__anext__()
421 - assert final_chunk.usage is not None
422 - assert final_chunk.usage.prompt_tokens > 0
423 - assert final_chunk.usage.completion_tokens > 0
424 - assert final_chunk.usage.total_tokens == (
425 - final_chunk.usage.prompt_tokens +
426 - final_chunk.usage.completion_tokens)
427 - assert final_chunk.choices == []
428 -
429 - # Test stream=False, stream_options=
430 - # {"include_usage": None}
431 - with pytest.raises(BadRequestError):
432 - await client.completions.create(model=model_name,
433 - prompt=prompt,
434 - max_tokens=5,
435 - temperature=0.0,
436 - stream=False,
437 - stream_options={"include_usage": None})
438 -
439 - # Test stream=False, stream_options=
440 - # {"include_usage": True}
441 - with pytest.raises(BadRequestError):
442 - await client.completions.create(model=model_name,
443 - prompt=prompt,
444 - max_tokens=5,
445 - temperature=0.0,
446 - stream=False,
447 - stream_options={"include_usage": True})
448 -
449 - # Test stream=False, stream_options=
450 - # {"continuous_usage_stats": None}
451 - with pytest.raises(BadRequestError):
452 - await client.completions.create(
453 - model=model_name,
454 - prompt=prompt,
455 - max_tokens=5,
456 - temperature=0.0,
457 - stream=False,
458 - stream_options={"continuous_usage_stats": None})
459 -
460 - # Test stream=False, stream_options=
461 - # {"continuous_usage_stats": True}
462 - with pytest.raises(BadRequestError):
463 - await client.completions.create(
464 - model=model_name,
465 - prompt=prompt,
466 - max_tokens=5,
467 - temperature=0.0,
468 - stream=False,
469 - stream_options={"continuous_usage_stats": True})
470 -
471 -
472 -@pytest.mark.asyncio
473 -@pytest.mark.parametrize(
474 - "model_name",
475 - [MODEL_NAME, "zephyr-lora"],
476 -)
477 -async def test_batch_completions(client: openai.AsyncOpenAI, model_name: str):
478 - # test both text and token IDs
479 - for prompts in (["Hello, my name is"] * 2, [[0, 0, 0, 0, 0]] * 2):
480 - # test simple list
481 - batch = await client.completions.create(
482 - model=model_name,
483 - prompt=prompts,
484 - max_tokens=5,
485 - temperature=0.0,
486 - )
487 - assert len(batch.choices) == 2
488 - assert batch.choices[0].text == batch.choices[1].text
489 -
490 - # test n = 2
491 - batch = await client.completions.create(
492 - model=model_name,
493 - prompt=prompts,
494 - n=2,
495 - max_tokens=5,
496 - temperature=0.0,
497 - extra_body=dict(
498 - # NOTE: this has to be true for n > 1 in vLLM, but
499 - # not necessary for official client.
500 - use_beam_search=True),
501 - )
502 - assert len(batch.choices) == 4
503 - assert batch.choices[0].text != batch.choices[
504 - 1].text, "beam search should be different"
505 - assert batch.choices[0].text == batch.choices[
506 - 2].text, "two copies of the same prompt should be the same"
507 - assert batch.choices[1].text == batch.choices[
508 - 3].text, "two copies of the same prompt should be the same"
509 -
510 - # test streaming
511 - batch = await client.completions.create(
512 - model=model_name,
513 - prompt=prompts,
514 - max_tokens=5,
515 - temperature=0.0,
516 - stream=True,
517 - )
518 - texts = [""] * 2
519 - async for chunk in batch:
520 - assert len(chunk.choices) == 1
521 - choice = chunk.choices[0]
522 - texts[choice.index] += choice.text
523 - assert texts[0] == texts[1]
524 -
525 -
526 -@pytest.mark.asyncio
527 -async def test_logits_bias(client: openai.AsyncOpenAI):
528 - prompt = "Hello, my name is"
529 - max_tokens = 5
530 - tokenizer = get_tokenizer(tokenizer_name=MODEL_NAME)
531 -
532 - # Test exclusive selection
533 - token_id = 1000
534 - completion = await client.completions.create(
535 - model=MODEL_NAME,
536 - prompt=prompt,
537 - max_tokens=max_tokens,
538 - temperature=0.0,
539 - logit_bias={str(token_id): 100},
540 - seed=42,
541 - )
542 - assert len(completion.choices[0].text) >= 5
543 - response_tokens = tokenizer(completion.choices[0].text,
544 - add_special_tokens=False)["input_ids"]
545 - expected_tokens = tokenizer(tokenizer.decode([token_id] * 5),
546 - add_special_tokens=False)["input_ids"]
547 - assert all([
548 - response == expected
549 - for response, expected in zip(response_tokens, expected_tokens)
550 - ])
551 -
552 - # Test ban
553 - completion = await client.completions.create(
554 - model=MODEL_NAME,
555 - prompt=prompt,
556 - max_tokens=max_tokens,
557 - temperature=0.0,
558 - )
559 - response_tokens = tokenizer(completion.choices[0].text,
560 - add_special_tokens=False)["input_ids"]
561 - first_response = completion.choices[0].text
562 - completion = await client.completions.create(
563 - model=MODEL_NAME,
564 - prompt=prompt,
565 - max_tokens=max_tokens,
566 - temperature=0.0,
567 - logit_bias={str(token): -100
568 - for token in response_tokens},
569 - )
570 - assert first_response != completion.choices[0].text
571 -
572 -
573 -@pytest.mark.asyncio
574 -async def test_allowed_token_ids(client: openai.AsyncOpenAI):
575 - prompt = "Hello, my name is"
576 - max_tokens = 1
577 - tokenizer = get_tokenizer(tokenizer_name=MODEL_NAME)
578 -
579 - # Test exclusive selection
580 - allowed_ids = [21555, 21557, 21558]
581 - completion = await client.completions.create(
582 - model=MODEL_NAME,
583 - prompt=prompt,
584 - max_tokens=max_tokens,
585 - temperature=0.0,
586 - seed=42,
587 - extra_body=dict(allowed_token_ids=allowed_ids),
588 - logprobs=1,
589 - )
590 - response_tokens = completion.choices[0].logprobs.tokens
591 - assert len(response_tokens) == 1
592 - assert tokenizer.convert_tokens_to_ids(response_tokens)[0] in allowed_ids
593 -
594 -
595 -@pytest.mark.asyncio
596 -async def test_structured_outputs_json_completion(
597 - client: openai.AsyncOpenAI,
598 - sample_json_schema,
599 - is_v1_server: bool,
600 -):
601 - if not is_v1_server:
602 - pytest.skip("structured outputs is only supported in v1 engine")
603 -
604 - completion = await client.completions.create(
605 - model=MODEL_NAME,
606 - prompt=f"Give an example JSON for an employee profile "
607 - f"that fits this schema: {sample_json_schema}",
608 - n=3,
609 - temperature=1.0,
610 - max_tokens=500,
611 - extra_body=dict(structured_outputs=dict(json=sample_json_schema)))
612 -
613 - assert completion.id is not None
614 - assert len(completion.choices) == 3
615 - for i in range(3):
616 - output_json = json.loads(completion.choices[i].text)
617 - jsonschema.validate(instance=output_json, schema=sample_json_schema)
618 -
619 -
620 -@pytest.mark.asyncio
621 -async def test_structured_outputs_regex_completion(
622 - client: openai.AsyncOpenAI,
623 - sample_regex,
624 - is_v1_server: bool,
625 -):
626 - if not is_v1_server:
627 - pytest.skip("structured outputs is only supported in v1 engine")
628 -
629 - completion = await client.completions.create(
630 - model=MODEL_NAME,
631 - prompt=f"Give an example IPv4 address with this regex: {sample_regex}",
632 - n=3,
633 - temperature=1.0,
634 - max_tokens=20,
635 - extra_body=dict(structured_outputs=dict(regex=sample_regex)))
636 -
637 - assert completion.id is not None
638 - assert len(completion.choices) == 3
639 - for i in range(3):
640 - assert re.fullmatch(sample_regex,
641 - completion.choices[i].text) is not None
642 -
643 -
644 -@pytest.mark.asyncio
645 -async def test_structured_outputs_choice_completion(
646 - client: openai.AsyncOpenAI,
647 - sample_structured_outputs_choices,
648 - is_v1_server: bool,
649 -):
650 - if not is_v1_server:
651 - pytest.skip("structured outputs is only supported in v1 engine")
652 -
653 - completion = await client.completions.create(
654 - model=MODEL_NAME,
655 - prompt="The best language for type-safe systems programming is ",
656 - n=2,
657 - temperature=1.0,
658 - max_tokens=10,
659 - extra_body=dict(structured_outputs=dict(
660 - choice=sample_structured_outputs_choices)))
661 -
662 - assert completion.id is not None
663 - assert len(completion.choices) == 2
664 - for i in range(2):
665 - assert completion.choices[i].text in sample_structured_outputs_choices
666 -
667 -
668 -@pytest.mark.asyncio
669 -async def test_structured_outputs_grammar(client: openai.AsyncOpenAI,
670 - sample_sql_statements,
671 - is_v1_server: bool):
672 - if not is_v1_server:
673 - pytest.skip("grammar is only supported in v1 engine")
674 -
675 - completion = await client.completions.create(
676 - model=MODEL_NAME,
677 - prompt=("Generate a sql state that select col_1 from "
678 - "table_1 where it is equals to 1"),
679 - temperature=1.0,
680 - max_tokens=500,
681 - extra_body=dict(
682 - structured_outputs=dict(grammar=sample_sql_statements), ))
683 -
684 - content = completion.choices[0].text
685 -
686 - # use Lark to parse the output, and make sure it's a valid parse tree
687 - from lark import Lark
688 - parser = Lark(sample_sql_statements)
689 - parser.parse(content)
690 -
691 - # remove spaces for comparison b/c we removed them in the grammar
692 - ground_truth = "SELECT col_1 from table_1 where col_1 = 1".replace(" ", "")
693 -
694 - assert content.strip() == ground_truth
695 -
696 -
697 -@pytest.mark.asyncio
698 -@pytest.mark.parametrize(
699 - # first test base model, then test loras
700 - "model_name",
701 - [MODEL_NAME, "zephyr-lora"],
702 -)
703 -@pytest.mark.parametrize("logprobs_arg", [1, 0])
704 -async def test_echo_logprob_completion(client: openai.AsyncOpenAI,
705 - model_name: str, logprobs_arg: int):
706 - tokenizer = get_tokenizer(tokenizer_name=MODEL_NAME)
707 - # test using text and token IDs
708 - for prompt in ("Hello, my name is", [0, 0, 0, 0, 0]):
709 - completion = await client.completions.create(model=model_name,
710 - prompt=prompt,
711 - max_tokens=5,
712 - temperature=0.0,
713 - echo=True,
714 - logprobs=logprobs_arg)
715 -
716 - prompt_text = tokenizer.decode(prompt) if isinstance(prompt,
717 - list) else prompt
718 - assert re.search(r"^" + prompt_text, completion.choices[0].text)
719 - logprobs = completion.choices[0].logprobs
720 - assert logprobs is not None
721 - assert len(logprobs.text_offset) > 5
722 - assert (len(logprobs.token_logprobs) > 5
723 - and logprobs.token_logprobs[0] is None)
724 - assert (len(logprobs.top_logprobs) > 5
725 - and logprobs.top_logprobs[0] is None)
726 - for top_logprobs in logprobs.top_logprobs[1:]:
727 - assert max(logprobs_arg,
728 - 1) <= len(top_logprobs) <= logprobs_arg + 1
729 - assert len(logprobs.tokens) > 5
730 -
731 -
732 -@pytest.mark.asyncio
733 -async def test_structured_outputs_type_error(client: openai.AsyncOpenAI,
734 - sample_json_schema, sample_regex,
735 - is_v1_server: bool):
736 - if not is_v1_server:
737 - pytest.skip("structured outputs is only supported in v1 engine")
738 -
739 - with pytest.raises(openai.BadRequestError):
740 - _ = await client.completions.create(
741 - model=MODEL_NAME,
742 - prompt="Give an example JSON that fits this schema: 42",
743 - extra_body=dict(structured_outputs=dict(json=42)))
744 -
745 - with pytest.raises(openai.BadRequestError):
746 - _ = await client.completions.create(
747 - model=MODEL_NAME,
748 - prompt="Give an example string that fits this regex",
749 - extra_body=dict(structured_outputs=dict(
750 - regex=sample_regex,
751 - json=sample_json_schema,
752 - )))
753 -
754 -
755 -@pytest.mark.asyncio
756 -@pytest.mark.parametrize(
757 - "model_name,stream,echo",
758 - [
759 - (MODEL_NAME, False, False),
760 - (MODEL_NAME, False, True),
761 - (MODEL_NAME, True, False),
762 - (MODEL_NAME, True, True) # should not raise BadRequestError error
763 - ],
764 -)
765 -async def test_echo_stream_completion(client: openai.AsyncOpenAI,
766 - model_name: str, stream: bool,
767 - echo: bool):
768 - saying: str = "Hello, my name is"
769 - result = await client.completions.create(model=model_name,
770 - prompt=saying,
771 - max_tokens=10,
772 - temperature=0.0,
773 - echo=echo,
774 - stream=stream)
775 -
776 - stop_reason = "length"
777 -
778 - if not stream:
779 - completion = result
780 - assert completion.id is not None
781 - assert completion.choices is not None and len(completion.choices) == 1
782 -
783 - choice = completion.choices[0]
784 - assert len(choice.text) >= 5
785 - assert choice.finish_reason == stop_reason
786 -
787 - if echo:
788 - assert choice.text is not None and saying in choice.text
789 - else:
790 - assert choice.text is not None and saying not in choice.text
791 -
792 - else:
793 - chunks: list[str] = []
794 - final_finish_reason = None
795 - async for chunk in result:
796 - if chunk.choices and chunk.choices[0].text:
797 - chunks.append(chunk.choices[0].text)
798 - if chunk.choices and chunk.choices[0].finish_reason:
799 - final_finish_reason = chunk.choices[0].finish_reason
800 -
801 - assert final_finish_reason == stop_reason
802 - content = "".join(chunks)
803 - if echo:
804 - assert content is not None and saying in content
805 - else:
806 - assert content is not None and saying not in content
807 -
808 -
809 -@pytest.mark.asyncio
810 -async def test_invocations(server: RemoteOpenAIServer,
811 - client: openai.AsyncOpenAI):
812 - request_args = {
813 - "model": MODEL_NAME,
814 - "prompt": "Hello, my name is",
815 - "max_tokens": 5,
816 - "temperature": 0.0,
817 - "logprobs": None,
818 - }
819 -
820 - completion = await client.completions.create(**request_args)
821 -
822 - invocation_response = requests.post(server.url_for("invocations"),
823 - json=request_args)
824 - invocation_response.raise_for_status()
825 -
826 - completion_output = completion.model_dump()
827 - invocation_output = invocation_response.json()
828 -
829 - assert completion_output.keys() == invocation_output.keys()
830 - assert completion_output["choices"] == invocation_output["choices"]
tests/entrypoints/openai/test_completion_with_prompt_embeds.py
+3
@@ -14,6 +14,9 @@ from transformers import AutoConfig
14
15 from ...utils import RemoteOpenAIServer
16
17 +pytest.skip("Skipping prompt_embeds test until V1 supports it.",
18 + allow_module_level=True)
19 +
20 # any model with a chat template should work here
21 MODEL_NAME = "HuggingFaceH4/zephyr-7b-beta"
22
tests/entrypoints/openai/test_lora_adapters.py
+3 -2
@@ -53,12 +53,13 @@ def monkeypatch_module():
53 mpatch.undo()
54
55
56 -@pytest.fixture(scope="module", params=[False, True])
56 +@pytest.fixture(scope="module", params=[True])
57 def server_with_lora_modules_json(request, monkeypatch_module,
58 zephyr_lora_files):
59
60 use_v1 = request.param
61 - monkeypatch_module.setenv('VLLM_USE_V1', '1' if use_v1 else '0')
61 + assert use_v1
62 + monkeypatch_module.setenv('VLLM_USE_V1', '1')
63
64 # Define the json format LoRA module configurations
65 lora_module_1 = {
tests/entrypoints/openai/test_metrics.py
+1 -1
@@ -22,7 +22,7 @@ MODEL_NAME = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
22 PREV_MINOR_VERSION = version._prev_minor_version()
23
24
25 -@pytest.fixture(scope="module", params=[True, False])
25 +@pytest.fixture(scope="module", params=[True])
26 def use_v1(request):
27 # Module-scoped variant of run_with_both_engines
28 #
tests/entrypoints/openai/test_return_tokens_as_ids.py
+24 -2
@@ -10,8 +10,30 @@ import pytest
10 from vllm.transformers_utils.tokenizer import get_tokenizer
11
12 from ...utils import RemoteOpenAIServer
13 -from .test_completion import default_server_args # noqa: F401
14 -from .test_completion import MODEL_NAME
13 +
14 +MODEL_NAME = "HuggingFaceH4/zephyr-7b-beta"
15 +
16 +
17 +@pytest.fixture(scope="module")
18 +def default_server_args(zephyr_lora_files):
19 + return [
20 + # use half precision for speed and memory savings in CI environment
21 + "--dtype",
22 + "bfloat16",
23 + "--max-model-len",
24 + "8192",
25 + "--max-num-seqs",
26 + "128",
27 + "--enforce-eager",
28 + # lora config
29 + "--enable-lora",
30 + "--lora-modules",
31 + f"zephyr-lora={zephyr_lora_files}",
32 + "--max-lora-rank",
33 + "64",
34 + "--max-cpu-loras",
35 + "2",
36 + ]
37
38
39 @pytest.fixture(scope="module")
tests/entrypoints/openai/test_skip_tokenizer.py
-8
@@ -15,14 +15,6 @@ MODEL_NAME = "ibm-nasa-geospatial/Prithvi-EO-2.0-300M-TL-Sen1Floods11"
15 DTYPE = "float16"
16
17
18 -@pytest.fixture(autouse=True)
19 -def v1(run_with_both_engines):
20 - # Simple autouse wrapper to run both engines for each test
21 - # This can be promoted up to conftest.py to run for every
22 - # test in a package
23 - pass
24 -
25 -
18 @pytest.fixture(scope="module")
19 def server():
20 args = [
tests/v1/test_oracle.py
-18
@@ -7,7 +7,6 @@ import pytest
7 import vllm.envs as envs
8 from vllm import LLM
9 from vllm.engine.arg_utils import AsyncEngineArgs
10 -from vllm.engine.async_llm_engine import AsyncLLMEngine
10
11 MODEL = "meta-llama/Llama-3.2-1B-Instruct"
12
@@ -96,20 +95,3 @@ def test_v1_attn_backend(monkeypatch):
95 _ = AsyncEngineArgs(model=MODEL).create_engine_config()
96 assert envs.VLLM_USE_V1
97 m.delenv("VLLM_USE_V1")
99 -
100 -
101 -def test_reject_using_constructor_directly(monkeypatch):
102 - with monkeypatch.context() as m:
103 - if os.getenv("VLLM_USE_V1", None):
104 - m.delenv("VLLM_USE_V1")
105 -
106 - # Sets VLLM_USE_V1=1.
107 - vllm_config = AsyncEngineArgs(model=MODEL).create_engine_config()
108 -
109 - # This uses the V0 constructor directly.
110 - with pytest.raises(ValueError):
111 - AsyncLLMEngine(vllm_config,
112 - AsyncLLMEngine._get_executor_cls(vllm_config),
113 - log_stats=True)
114 -
115 - m.delenv("VLLM_USE_V1")
vllm/engine/async_llm_engine.py
+2 -1028
@@ -1,1032 +1,6 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 -import asyncio
5 -import time
6 -import weakref
7 -from functools import partial
8 -from typing import (Any, AsyncGenerator, Callable, Dict, Iterable, List,
9 - Mapping, Optional, Set, Tuple, Type, Union)
10 -from weakref import ReferenceType
4 +from vllm.v1.engine.async_llm import AsyncLLM
5
12 -import vllm.envs as envs
13 -from vllm.config import (LoRAConfig, ModelConfig, ParallelConfig,
14 - SchedulerConfig, VllmConfig)
15 -from vllm.core.scheduler import SchedulerOutputs
16 -from vllm.engine.arg_utils import AsyncEngineArgs
17 -from vllm.engine.async_timeout import asyncio_timeout
18 -from vllm.engine.llm_engine import LLMEngine
19 -from vllm.engine.metrics_types import StatLoggerBase
20 -from vllm.engine.protocol import EngineClient
21 -from vllm.executor.executor_base import ExecutorBase
22 -from vllm.inputs import PromptType
23 -from vllm.inputs.preprocess import InputPreprocessor
24 -from vllm.logger import init_logger
25 -from vllm.lora.request import LoRARequest
26 -from vllm.model_executor.layers.sampler import SamplerOutput
27 -from vllm.outputs import PoolingRequestOutput, RequestOutput
28 -from vllm.pooling_params import PoolingParams
29 -from vllm.sampling_params import SamplingParams
30 -from vllm.sequence import ExecuteModelRequest
31 -from vllm.transformers_utils.tokenizer import AnyTokenizer
32 -from vllm.usage.usage_lib import UsageContext
33 -from vllm.utils import Device, deprecate_kwargs, weak_bind
34 -
35 -logger = init_logger(__name__)
36 -ENGINE_ITERATION_TIMEOUT_S = envs.VLLM_ENGINE_ITERATION_TIMEOUT_S
37 -
38 -
39 -class AsyncEngineDeadError(RuntimeError):
40 - pass
41 -
42 -
43 -def _log_task_completion(task: asyncio.Task,
44 - error_callback: Callable[[Exception], None]) -> None:
45 - """This function is only intended for the `engine.run_engine_loop()` task.
46 -
47 - In particular, that task runs a `while True` loop that can only exit if
48 - there is an exception.
49 - """
50 -
51 - exception = None
52 - try:
53 - return_value = task.result()
54 - raise AssertionError(
55 - f"The engine background task should never finish without an "
56 - f"exception. {return_value}")
57 - except asyncio.exceptions.CancelledError:
58 - # We assume that if the task is cancelled, we are gracefully shutting
59 - # down. This should only happen on program exit.
60 - logger.info("Engine is gracefully shutting down.")
61 - except Exception as e:
62 - exception = e
63 - logger.error("Engine background task failed", exc_info=e)
64 - error_callback(exception)
65 - raise AsyncEngineDeadError(
66 - "Task finished unexpectedly. This should never happen! "
67 - "Please open an issue on GitHub. See stack trace above for the "
68 - "actual cause.") from e
69 -
70 -
71 -STOP_ITERATION = Exception() # Sentinel
72 -
73 -
74 -class AsyncStream:
75 - """A stream of RequestOutputs for a request that can be iterated over
76 - asynchronously via an async generator."""
77 -
78 - def __init__(self, request_id: str, cancel: Callable[[str], None]) -> None:
79 - self.request_id = request_id
80 - self._cancel = cancel
81 - self._queue: asyncio.Queue = asyncio.Queue()
82 - self._finished = False
83 -
84 - def put(self, item: Union[RequestOutput, Exception]) -> None:
85 - if not self._finished:
86 - self._queue.put_nowait(item)
87 -
88 - def finish(
89 - self,
90 - exception: Optional[Union[BaseException, Type[BaseException]]] = None,
91 - ) -> None:
92 - if not self._finished:
93 - self._finished = True
94 - self._queue.put_nowait(
95 - exception if self._is_raisable(exception) else STOP_ITERATION)
96 -
97 - @property
98 - def finished(self) -> bool:
99 - return self._finished
100 -
101 - async def generator(self) -> AsyncGenerator[RequestOutput, None]:
102 - try:
103 - while True:
104 - result = await self._queue.get()
105 - if self._is_raisable(result):
106 - if result == STOP_ITERATION:
107 - return
108 - raise result
109 - yield result
110 - except GeneratorExit:
111 - self._cancel(self.request_id)
112 - raise asyncio.CancelledError from None
113 -
114 - @staticmethod
115 - def _is_raisable(value: Any):
116 - return isinstance(value, BaseException) or \
117 - (isinstance(value, type) and \
118 - issubclass(value, BaseException))
119 -
120 -
121 -class RequestTracker:
122 - """Synchronous abstraction for tracking requests."""
123 -
124 - def __init__(self) -> None:
125 - self._request_streams: Dict[str, AsyncStream] = {}
126 - self._aborted_requests: asyncio.Queue[str] = asyncio.Queue()
127 - self._new_requests: asyncio.Queue[Tuple[AsyncStream,
128 - dict]] = asyncio.Queue()
129 - self.new_requests_event = asyncio.Event()
130 -
131 - def __contains__(self, item):
132 - return item in self._request_streams
133 -
134 - def __len__(self) -> int:
135 - return len(self._request_streams)
136 -
137 - def propagate_exception(self,
138 - exc: Exception,
139 - request_id: Optional[str] = None) -> None:
140 - """Propagate an exception to request streams
141 - (all if request_id is None)."""
142 - if request_id is not None:
143 - self.abort_request(request_id, exception=exc)
144 - else:
145 - # NB: tuple() used here because self.abort_request pops the stream
146 - # out of self._request_streams, so we can't iterate on it directly
147 - for rid in tuple(self._request_streams.keys()):
148 - self.abort_request(rid, exception=exc)
149 -
150 - def process_request_output(self,
151 - request_output: RequestOutput,
152 - *,
153 - verbose: bool = False) -> None:
154 - """Process a request output from the engine."""
155 - request_id = request_output.request_id
156 - finished = request_output.finished
157 -
158 - if finished:
159 - stream = self._request_streams.pop(request_id, None)
160 - else:
161 - stream = self._request_streams.get(request_id)
162 - # Guard against a KeyError which can occur if the request was aborted
163 - # while the output was generated
164 - if stream is not None:
165 - stream.put(request_output)
166 - if finished:
167 - stream.finish()
168 -
169 - if verbose and finished:
170 - logger.info("Finished request %s.", request_id)
171 -
172 - def process_exception(self,
173 - request_id: str,
174 - exception: BaseException,
175 - *,
176 - verbose: bool = False) -> None:
177 - """Propagate an exception from the engine."""
178 - if verbose:
179 - logger.info("Finished request %s.", request_id)
180 - self.abort_request(request_id, exception=exception)
181 -
182 - def add_request(self,
183 - request_id: str,
184 - *,
185 - verbose: bool = False,
186 - **engine_add_request_kwargs) -> AsyncStream:
187 - """Add a request to be sent to the engine on the next background
188 - loop iteration."""
189 - if request_id in self._request_streams:
190 - raise KeyError(f"Request {request_id} already exists.")
191 -
192 - abort_request = partial(self.abort_request, verbose=verbose)
193 - stream = AsyncStream(request_id, abort_request)
194 - self._new_requests.put_nowait((stream, {
195 - "request_id": request_id,
196 - **engine_add_request_kwargs
197 - }))
198 -
199 - self.new_requests_event.set()
200 -
201 - if verbose:
202 - logger.info("Added request %s.", request_id)
203 -
204 - return stream
205 -
206 - def abort_request(self,
207 - request_id: str,
208 - *,
209 - exception: Optional[Union[BaseException,
210 - Type[BaseException]]] = None,
211 - verbose: bool = False) -> None:
212 - """Abort a request during next background loop iteration."""
213 - if verbose:
214 - logger.info("Aborted request %s.", request_id)
215 -
216 - self._aborted_requests.put_nowait(request_id)
217 -
218 - stream = self._request_streams.pop(request_id, None)
219 - if stream is not None:
220 - stream.finish(exception=exception)
221 -
222 - def get_new_and_aborted_requests(self) -> Tuple[List[Dict], Set[str]]:
223 - """Get the new requests and finished requests to be
224 - sent to the engine."""
225 - new_requests: List[Dict] = []
226 - finished_requests: Set[str] = set()
227 -
228 - while not self._aborted_requests.empty():
229 - request_id = self._aborted_requests.get_nowait()
230 - finished_requests.add(request_id)
231 -
232 - while not self._new_requests.empty():
233 - stream, new_request = self._new_requests.get_nowait()
234 - request_id = stream.request_id
235 - if request_id in finished_requests:
236 - # The request has already been aborted.
237 - stream.finish(asyncio.CancelledError)
238 - finished_requests.discard(request_id)
239 - else:
240 - self._request_streams[request_id] = stream
241 - new_requests.append(new_request)
242 -
243 - return new_requests, finished_requests
244 -
245 - async def wait_for_new_requests(self):
246 - if not self.has_new_requests():
247 - await self.new_requests_event.wait()
248 - self.new_requests_event.clear()
249 -
250 - def has_new_requests(self):
251 - return not self._new_requests.empty()
252 -
253 -
254 -class _AsyncLLMEngine(LLMEngine):
255 - """Extension of LLMEngine to add async methods."""
256 -
257 - def __init__(self, *args, **kwargs):
258 - super().__init__(*args, **kwargs)
259 -
260 - async def step_async(self, virtual_engine: int) -> List[RequestOutput]:
261 - """Performs one decoding iteration and returns newly generated results.
262 - The workers are ran asynchronously if possible.
263 -
264 - This function performs one decoding iteration of the engine. It first
265 - schedules the sequences to be executed in the next iteration and the
266 - token blocks to be swapped in/out/copy. Then, it executes the model
267 - and updates the scheduler with the model outputs. Finally, it decodes
268 - the sequences and returns the newly generated results.
269 - """
270 - # these are cached outputs from previous iterations. None if on first
271 - # iteration
272 - cached_outputs = self.cached_scheduler_outputs[virtual_engine]
273 - seq_group_metadata_list = cached_outputs.seq_group_metadata_list
274 - scheduler_outputs = cached_outputs.scheduler_outputs
275 - allow_async_output_proc = cached_outputs.allow_async_output_proc
276 -
277 - ctx = self.scheduler_contexts[virtual_engine]
278 -
279 - # Clear outputs for each new scheduler iteration
280 - ctx.request_outputs.clear()
281 -
282 - # skip the scheduler if there are any remaining steps in the seq groups.
283 - # This ensures that the scheduler is only called again when the current
284 - # batch has completed.
285 - if not self._has_remaining_steps(seq_group_metadata_list):
286 -
287 - # Schedule iteration
288 - (seq_group_metadata_list, scheduler_outputs,
289 - allow_async_output_proc
290 - ) = self.scheduler[virtual_engine].schedule()
291 -
292 - ctx.seq_group_metadata_list = seq_group_metadata_list
293 - ctx.scheduler_outputs = scheduler_outputs
294 -
295 - if not scheduler_outputs.is_empty():
296 - # this will cause mamba_cache/minimax_cache failed
297 - # to release finished_requests_ids of the last steps
298 - finished_requests_ids = self.scheduler[
299 - virtual_engine].get_and_reset_finished_requests_ids()
300 -
301 - # Maybe switch from async mode to sync mode
302 - if not allow_async_output_proc and len(ctx.output_queue) > 0:
303 - self._process_model_outputs(ctx=ctx)
304 -
305 - else:
306 - finished_requests_ids = list()
307 -
308 - assert seq_group_metadata_list is not None
309 - assert scheduler_outputs is not None
310 -
311 - if not scheduler_outputs.is_empty():
312 -
313 - # Check if we have a cached last_output from the previous iteration.
314 - # For supporting PP this is probably the best way to pass the
315 - # sampled_token_ids, as a separate broadcast over all the PP stages
316 - # will cause one virtual engine's microbatch to block the pipeline.
317 - last_sampled_token_ids = \
318 - self._get_last_sampled_token_ids(virtual_engine)
319 -
320 - execute_model_req = ExecuteModelRequest(
321 - seq_group_metadata_list=seq_group_metadata_list,
322 - blocks_to_swap_in=scheduler_outputs.blocks_to_swap_in,
323 - blocks_to_swap_out=scheduler_outputs.blocks_to_swap_out,
324 - blocks_to_copy=scheduler_outputs.blocks_to_copy,
325 - virtual_engine=virtual_engine,
326 - num_lookahead_slots=scheduler_outputs.num_lookahead_slots,
327 - running_queue_size=scheduler_outputs.running_queue_size,
328 - finished_requests_ids=finished_requests_ids,
329 - # We use ExecuteModelRequest to pass the last sampled_token_ids
330 - # to each of the non-last PP stages for in-place prepare_input.
331 - last_sampled_token_ids=last_sampled_token_ids)
332 -
333 - if allow_async_output_proc:
334 - execute_model_req.async_callback = self.async_callbacks[
335 - virtual_engine]
336 -
337 - # Execute the model.
338 - outputs = await self.model_executor.execute_model_async(
339 - execute_model_req)
340 -
341 - else:
342 - if len(ctx.output_queue) > 0:
343 - self._process_model_outputs(ctx=ctx)
344 - outputs = []
345 -
346 - if not self._has_remaining_steps(seq_group_metadata_list):
347 - # is_first_step_output is True only when the num_steps of all
348 - # the sequences are 1.
349 - is_first_step_output: bool = False if not seq_group_metadata_list \
350 - else seq_group_metadata_list[0].state.num_steps == 1
351 -
352 - ctx.append_output(outputs=outputs,
353 - seq_group_metadata_list=seq_group_metadata_list,
354 - scheduler_outputs=scheduler_outputs,
355 - is_async=allow_async_output_proc,
356 - is_last_step=True,
357 - is_first_step_output=is_first_step_output)
358 -
359 - if outputs and allow_async_output_proc:
360 - assert len(
361 - outputs
362 - ) == 1, "Async postprocessor expects only a single output set"
363 - self._advance_to_next_step(
364 - outputs[0], seq_group_metadata_list,
365 - scheduler_outputs.scheduled_seq_groups)
366 -
367 - if not allow_async_output_proc:
368 - self._process_model_outputs(ctx=ctx)
369 -
370 - # Log stats.
371 - self.do_log_stats(scheduler_outputs, outputs)
372 -
373 - # Tracing
374 - self.do_tracing(scheduler_outputs)
375 -
376 - else:
377 - # Multi-step case
378 - return ctx.request_outputs
379 -
380 - if not self.has_unfinished_requests():
381 - # Drain async postprocessor (if exists)
382 - if len(ctx.output_queue) > 0:
383 - self._process_model_outputs(ctx=ctx)
384 - assert len(ctx.output_queue) == 0
385 -
386 - return ctx.request_outputs
387 -
388 - async def stop_remote_worker_execution_loop_async(self) -> None:
389 - """Stop the remote worker execution loop."""
390 - await self.model_executor.stop_remote_worker_execution_loop_async()
391 -
392 - async def get_tokenizer_async(self) -> AnyTokenizer:
393 - return self.get_tokenizer()
394 -
395 - async def add_request_async(
396 - self,
397 - request_id: str,
398 - prompt: PromptType,
399 - params: SamplingParams,
400 - arrival_time: Optional[float] = None,
401 - lora_request: Optional[LoRARequest] = None,
402 - trace_headers: Optional[Mapping[str, str]] = None,
403 - priority: int = 0,
404 - data_parallel_rank: Optional[int] = None,
405 - tokenization_kwargs: Optional[dict[str, Any]] = None,
406 - ) -> None:
407 - """
408 - Async version of
409 - [`add_request`][vllm.engine.llm_engine.LLMEngine.add_request].
410 - """
411 - if lora_request is not None and not self.lora_config:
412 - raise ValueError(f"Got lora_request {lora_request} but LoRA is "
413 - "not enabled!")
414 - if priority != 0 and not self.scheduler_config.policy == "priority":
415 - raise ValueError(f"Got priority {priority} but "
416 - "Priority scheduling is not enabled.")
417 - if arrival_time is None:
418 - arrival_time = time.time()
419 -
420 - if data_parallel_rank is not None:
421 - raise ValueError("Targeting data_parallel_rank only supported "
422 - "in v1 client.")
423 -
424 - if (isinstance(prompt, dict)
425 - and prompt.get("prompt_embeds", None) is not None
426 - and not prompt.get("prompt_token_ids", None)):
427 - # We use the -2 dimension (instead of 0) in case a batched input
428 - # of batch size 1 is passed in.
429 - prompt["prompt_token_ids"] = [0
430 - ] * prompt["prompt_embeds"].shape[-2]
431 -
432 - processed_inputs = await self.input_preprocessor.preprocess_async(
433 - prompt,
434 - tokenization_kwargs=tokenization_kwargs,
435 - )
436 -
437 - self._add_processed_request(
438 - request_id=request_id,
439 - processed_inputs=processed_inputs,
440 - params=params,
441 - arrival_time=arrival_time,
442 - lora_request=lora_request,
443 - trace_headers=trace_headers,
444 - priority=priority,
445 - )
446 -
447 - async def check_health_async(self) -> None:
448 - self.model_executor.check_health()
449 -
450 - async def collective_rpc_async(self,
451 - method: str,
452 - timeout: Optional[float] = None,
453 - args: tuple = (),
454 - kwargs: Optional[dict] = None):
455 - raise NotImplementedError
456 -
457 -
458 -class AsyncLLMEngine(EngineClient):
459 - """An asynchronous wrapper for [`LLMEngine`][vllm.LLMEngine].
460 -
461 - This class is used to wrap the [`LLMEngine`][vllm.LLMEngine] class to
462 - make it asynchronous. It uses asyncio to create a background loop that keeps
463 - processing incoming requests. The [`LLMEngine`][vllm.LLMEngine] is kicked
464 - by the generate method when there are requests in the waiting queue. The
465 - generate method yields the outputs from the [`LLMEngine`][vllm.LLMEngine]
466 - to the caller.
467 -
468 - Args:
469 - log_requests: Whether to log the requests.
470 - start_engine_loop: If True, the background task to run the engine
471 - will be automatically started in the generate call.
472 - *args: Arguments for [`LLMEngine`][vllm.LLMEngine].
473 - **kwargs: Arguments for [`LLMEngine`][vllm.LLMEngine].
474 - """
475 -
476 - _engine_class: Type[_AsyncLLMEngine] = _AsyncLLMEngine
477 -
478 - def __init__(self,
479 - *args: Any,
480 - log_requests: bool = True,
481 - start_engine_loop: bool = True,
482 - **kwargs: Any) -> None:
483 - if envs.VLLM_USE_V1:
484 - raise ValueError(
485 - "Using V0 AsyncLLMEngine, but envs.VLLM_USE_V1=True. "
486 - "This should not happen. As a workaround, try using "
487 - "AsyncLLMEngine.from_vllm_config(...) or explicitly set "
488 - "VLLM_USE_V1=0 or 1 and report this issue on Github.")
489 -
490 - self.log_requests = log_requests
491 - self.engine = self._engine_class(*args, **kwargs)
492 -
493 - # This ensures quick processing of request outputs
494 - # so the append to asyncio queues is not delayed,
495 - # especially for multi-step.
496 - self.use_process_request_outputs_callback = (
497 - self.engine.model_config.use_async_output_proc)
498 -
499 - if self.use_process_request_outputs_callback:
500 - self.engine.process_request_outputs_callback = \
501 - weak_bind(self.process_request_outputs)
502 -
503 - self.background_loop: Optional[asyncio.Future] = None
504 - # We need to keep a reference to unshielded
505 - # task as well to prevent it from being garbage
506 - # collected
507 - self._background_loop_unshielded: Optional[asyncio.Task] = None
508 - self.start_engine_loop = start_engine_loop
509 - self._errored_with: Optional[BaseException] = None
510 -
511 - # Lazy initialized fields
512 - self._request_tracker: RequestTracker
513 -
514 - def __del__(self):
515 - if rt := getattr(self, "request_tracker", None):
516 - # Wake up engine loop so that it will exit cleanly
517 - rt.new_requests_event.set()
518 -
519 - @classmethod
520 - def _get_executor_cls(cls,
521 - engine_config: VllmConfig) -> Type[ExecutorBase]:
522 - return LLMEngine._get_executor_cls(engine_config)
523 -
524 - @classmethod
525 - @deprecate_kwargs(
526 - "disable_log_requests",
527 - additional_message=("This argument will have no effect. "
528 - "Use `enable_log_requests` instead."),
529 - )
530 - def from_vllm_config(
531 - cls,
532 - vllm_config: VllmConfig,
533 - start_engine_loop: bool = True,
534 - usage_context: UsageContext = UsageContext.ENGINE_CONTEXT,
535 - stat_loggers: Optional[dict[str, StatLoggerBase]] = None,
536 - enable_log_requests: bool = False,
537 - disable_log_stats: bool = False,
538 - disable_log_requests: bool = True, # Deprecated, will be removed
539 - ) -> "AsyncLLMEngine":
540 - """Create an AsyncLLMEngine from the EngineArgs."""
541 -
542 - return cls(
543 - vllm_config=vllm_config,
544 - executor_class=cls._get_executor_cls(vllm_config),
545 - start_engine_loop=start_engine_loop,
546 - log_requests=enable_log_requests,
547 - log_stats=not disable_log_stats,
548 - usage_context=usage_context,
549 - stat_loggers=stat_loggers,
550 - )
551 -
552 - @classmethod
553 - def from_engine_args(
554 - cls,
555 - engine_args: AsyncEngineArgs,
556 - start_engine_loop: bool = True,
557 - usage_context: UsageContext = UsageContext.ENGINE_CONTEXT,
558 - stat_loggers: Optional[Dict[str, StatLoggerBase]] = None,
559 - ) -> "AsyncLLMEngine":
560 - """Creates an async LLM engine from the engine arguments."""
561 -
562 - vllm_config = engine_args.create_engine_config(usage_context)
563 -
564 - async_engine_cls = cls
565 - if envs.VLLM_USE_V1:
566 - from vllm.v1.engine.async_llm import AsyncLLM as V1AsyncLLMEngine
567 - async_engine_cls = V1AsyncLLMEngine
568 -
569 - return async_engine_cls.from_vllm_config(
570 - vllm_config=vllm_config,
571 - start_engine_loop=start_engine_loop,
572 - usage_context=usage_context,
573 - stat_loggers=stat_loggers,
574 - disable_log_stats=engine_args.disable_log_stats,
575 - enable_log_requests=engine_args.enable_log_requests,
576 - )
577 -
578 - @property
579 - def is_running(self) -> bool:
580 - return (self.background_loop is not None
581 - and self._background_loop_unshielded is not None
582 - and not self._background_loop_unshielded.done())
583 -
584 - @property
585 - def is_stopped(self) -> bool:
586 - return self.errored or (self.background_loop is not None and
587 - self._background_loop_unshielded is not None
588 - and self._background_loop_unshielded.done())
589 -
590 - @property
591 - def errored(self) -> bool:
592 - return self._errored_with is not None
593 -
594 - @property
595 - def dead_error(self) -> BaseException:
596 - return AsyncEngineDeadError(
597 - "Background loop is not running. If it was running, "
598 - "inspect the output to find the stacktrace of the "
599 - "error that caused the background loop to stop "
600 - "(AsyncEngineDeadError).")
601 -
602 - def set_errored(self, exc: Exception) -> None:
603 - self._errored_with = exc
604 -
605 - def _error_callback(self, exc: Exception) -> None:
606 - self.set_errored(exc)
607 - self._request_tracker.propagate_exception(exc)
608 -
609 - async def get_input_preprocessor(self) -> InputPreprocessor:
610 - return self.engine.input_preprocessor
611 -
612 - async def get_tokenizer(self) -> AnyTokenizer:
613 - return self.engine.get_tokenizer()
614 -
615 - def start_background_loop(self) -> None:
616 - """Start the background loop."""
617 - if self.errored:
618 - raise AsyncEngineDeadError(
619 - "Background loop has errored already.") from self._errored_with
620 - if self.is_running:
621 - raise RuntimeError("Background loop is already running.")
622 - # Initialize the RequestTracker here so it uses the right event loop.
623 - self._request_tracker = RequestTracker()
624 -
625 - self._background_loop_unshielded = asyncio.get_event_loop(
626 - ).create_task(self.run_engine_loop(weakref.ref(self)))
627 - self._background_loop_unshielded.add_done_callback(
628 - partial(_log_task_completion, error_callback=self._error_callback))
629 - self.background_loop = asyncio.shield(self._background_loop_unshielded)
630 -
631 - def shutdown_background_loop(self) -> None:
632 - """
633 - Shut down the background loop.
634 -
635 - This method needs to be called during cleanup to remove
636 - references to `self` and properly GC the resources held
637 - by the async LLM engine (e.g., the executors as well as
638 - their resources).
639 - """
640 - if self._background_loop_unshielded is not None:
641 - self._background_loop_unshielded.cancel()
642 - self._background_loop_unshielded = None
643 - self.background_loop = None
644 -
645 - async def engine_step(self, virtual_engine: int) -> bool:
646 - """Kick the engine to process the waiting requests.
647 -
648 - Returns True if there are in-progress requests."""
649 -
650 - new_requests, aborted_requests = (
651 - self._request_tracker.get_new_and_aborted_requests())
652 -
653 - for new_request in new_requests:
654 - # Add the request into the vLLM engine's waiting queue.
655 - try:
656 - await self.engine.add_request_async(**new_request)
657 - except ValueError as e:
658 - # TODO: use a vLLM specific error for failed validation
659 - self._request_tracker.process_exception(
660 - new_request["request_id"],
661 - e,
662 - verbose=self.log_requests,
663 - )
664 -
665 - if aborted_requests:
666 - await self._engine_abort(aborted_requests)
667 -
668 - request_outputs = await self.engine.step_async(virtual_engine)
669 -
670 - # Put the outputs into the corresponding streams.
671 - # If used as a callback, then already invoked inside
672 - # LLMEngine's _process_model_outputs
673 - if not self.use_process_request_outputs_callback:
674 - all_finished = self.process_request_outputs(request_outputs)
675 - else:
676 - # For callback case, we only need to detect when all
677 - # requests are finished
678 - all_finished = all(request_output.finished
679 - for request_output in request_outputs)
680 -
681 - return not all_finished
682 -
683 - def process_request_outputs(self, request_outputs) -> bool:
684 - # Put the outputs into the corresponding streams.
685 - all_finished = True
686 - for request_output in request_outputs:
687 - self._request_tracker.process_request_output(
688 - request_output, verbose=self.log_requests)
689 - all_finished = all_finished and request_output.finished
690 -
691 - return all_finished
692 -
693 - async def _engine_abort(self, request_ids: Iterable[str]):
694 - self.engine.abort_request(request_ids)
695 -
696 - @staticmethod
697 - async def run_engine_loop(engine_ref: ReferenceType):
698 - """We use a weakref to the engine so that the running loop
699 - doesn't prevent the engine being garbage collected."""
700 - engine: Optional[AsyncLLMEngine] = engine_ref()
701 - if not engine:
702 - return
703 -
704 - pipeline_parallel_size = \
705 - engine.engine.parallel_config.pipeline_parallel_size
706 - has_requests_in_progress = [False] * pipeline_parallel_size
707 - while True:
708 - if not any(has_requests_in_progress):
709 - logger.debug("Waiting for new requests...")
710 - # Stop the execute model loop in parallel workers until there
711 - # are more requests to process. This avoids waiting
712 - # indefinitely in torch.distributed ops which may otherwise
713 - # time out, and unblocks the RPC thread in the workers so that
714 - # they can process any other queued control plane messages,
715 - # such as add/remove lora adapters.
716 - await engine.engine.stop_remote_worker_execution_loop_async()
717 - request_tracker = engine._request_tracker
718 - # Allow engine to be garbage collected while
719 - # waiting for new requests
720 - del engine
721 - await asyncio.sleep(0)
722 - if engine_ref() is None:
723 - return
724 - await request_tracker.wait_for_new_requests()
725 - engine = engine_ref()
726 - if not engine:
727 - return
728 - logger.debug("Got new requests!")
729 - requests_in_progress = [
730 - asyncio.create_task(engine.engine_step(ve))
731 - for ve in range(pipeline_parallel_size)
732 - ]
733 - has_requests_in_progress = [True] * pipeline_parallel_size
734 -
735 - # Abort if iteration takes too long due to unrecoverable errors
736 - # (eg. NCCL timeouts).
737 - try:
738 - async with asyncio_timeout(ENGINE_ITERATION_TIMEOUT_S):
739 - done, _ = await asyncio.wait(
740 - requests_in_progress,
741 - return_when=asyncio.FIRST_COMPLETED)
742 - for _ in range(pipeline_parallel_size):
743 - await asyncio.sleep(0)
744 - for task in done:
745 - result = task.result()
746 - virtual_engine = requests_in_progress.index(task)
747 - has_unfinished_requests = (
748 - engine.engine.
749 - has_unfinished_requests_for_virtual_engine(
750 - virtual_engine))
751 - if result or has_unfinished_requests:
752 - requests_in_progress[virtual_engine] = (
753 - asyncio.create_task(
754 - engine.engine_step(virtual_engine)))
755 - has_requests_in_progress[virtual_engine] = True
756 - else:
757 - has_requests_in_progress[virtual_engine] = False
758 - except asyncio.TimeoutError as exc:
759 - logger.error(
760 - "Engine iteration timed out. This should never happen!")
761 - engine.set_errored(exc)
762 - raise
763 - await asyncio.sleep(0)
764 -
765 - async def add_request(
766 - self,
767 - request_id: str,
768 - prompt: PromptType,
769 - params: SamplingParams,
770 - arrival_time: Optional[float] = None,
771 - lora_request: Optional[LoRARequest] = None,
772 - trace_headers: Optional[Mapping[str, str]] = None,
773 - priority: int = 0,
774 - data_parallel_rank: Optional[int] = None,
775 - tokenization_kwargs: Optional[dict[str, Any]] = None,
776 - ) -> AsyncGenerator[RequestOutput, None]:
777 - if not self.is_running:
778 - if self.start_engine_loop:
779 - self.start_background_loop()
780 - else:
781 - raise AsyncEngineDeadError(
782 - "Background loop is not running. If it was running, "
783 - "inspect the output to find the stacktrace of the "
784 - "error that caused the background loop to stop "
785 - "(AsyncEngineDeadError).")
786 -
787 - if (priority != 0
788 - and not self.engine.scheduler_config.policy == "priority"):
789 - raise ValueError(f"Got priority {priority} but "
790 - "Priority scheduling is not enabled.")
791 -
792 - stream = self._request_tracker.add_request(
793 - request_id,
794 - verbose=self.log_requests,
795 - prompt=prompt,
796 - params=params,
797 - arrival_time=arrival_time or time.time(),
798 - lora_request=lora_request,
799 - trace_headers=trace_headers,
800 - priority=priority,
801 - data_parallel_rank=data_parallel_rank,
802 - tokenization_kwargs=tokenization_kwargs,
803 - )
804 -
805 - return stream.generator()
806 -
807 - async def generate(
808 - self,
809 - prompt: PromptType,
810 - sampling_params: SamplingParams,
811 - request_id: str,
812 - lora_request: Optional[LoRARequest] = None,
813 - trace_headers: Optional[Mapping[str, str]] = None,
814 - priority: int = 0,
815 - data_parallel_rank: Optional[int] = None,
816 - ) -> AsyncGenerator[RequestOutput, None]:
817 - """Generate outputs for a request.
818 -
819 - Generate outputs for a request. This method is a coroutine. It adds the
820 - request into the waiting queue of the LLMEngine and streams the outputs
821 - from the LLMEngine to the caller.
822 -
823 - Args:
824 - prompt: The prompt to the LLM. See
825 - [`PromptType`][vllm.inputs.PromptType] for more details about
826 - the format of each input.
827 - sampling_params: The sampling parameters of the request.
828 - request_id: The unique id of the request.
829 - lora_request: LoRA request to use for generation, if any.
830 - trace_headers: OpenTelemetry trace headers.
831 - priority: The priority of the request.
832 - Only applicable with priority scheduling.
833 - data_parallel_rank: The (global) data parallel rank that must
834 - handle this request. Only applicable if DP is enabled.
835 - Yields:
836 - The output `RequestOutput` objects from the LLMEngine
837 - for the request.
838 -
839 - Details:
840 - - If the engine is not running, start the background loop,
841 - which iteratively invokes
842 - [`engine_step`][vllm.engine.async_llm_engine.AsyncLLMEngine.engine_step]
843 - to process the waiting requests.
844 - - Add the request to the engine's `RequestTracker`.
845 - On the next background loop, this request will be sent to
846 - the underlying engine.
847 - Also, a corresponding `AsyncStream` will be created.
848 - - Wait for the request outputs from `AsyncStream` and yield them.
849 -
850 - Example:
851 - >>> # Please refer to entrypoints/api_server.py for
852 - >>> # the complete example.
853 - >>>
854 - >>> # initialize the engine and the example input
855 - >>> # note that engine_args here is AsyncEngineArgs instance
856 - >>> engine = AsyncLLMEngine.from_engine_args(engine_args)
857 - >>> example_input = {
858 - >>> "prompt": "What is LLM?",
859 - >>> "stream": False, # assume the non-streaming case
860 - >>> "temperature": 0.0,
861 - >>> "request_id": 0,
862 - >>> }
863 - >>>
864 - >>> # start the generation
865 - >>> results_generator = engine.generate(
866 - >>> example_input["prompt"],
867 - >>> SamplingParams(temperature=example_input["temperature"]),
868 - >>> example_input["request_id"])
869 - >>>
870 - >>> # get the results
871 - >>> final_output = None
872 - >>> async for request_output in results_generator:
873 - >>> if await request.is_disconnected():
874 - >>> # Abort the request if the client disconnects.
875 - >>> await engine.abort(request_id)
876 - >>> # Return or raise an error
877 - >>> ...
878 - >>> final_output = request_output
879 - >>>
880 - >>> # Process and return the final output
881 - >>> ...
882 - """
883 - try:
884 - async for output in await self.add_request(
885 - request_id,
886 - prompt,
887 - sampling_params,
888 - lora_request=lora_request,
889 - trace_headers=trace_headers,
890 - priority=priority,
891 - data_parallel_rank=data_parallel_rank,
892 - ):
893 - yield LLMEngine.validate_output(output, RequestOutput)
894 - except asyncio.CancelledError:
895 - await self.abort(request_id)
896 - raise
897 -
898 - def encode(
899 - self,
900 - prompt: PromptType,
901 - pooling_params: PoolingParams,
902 - request_id: str,
903 - lora_request: Optional[LoRARequest] = None,
904 - trace_headers: Optional[Mapping[str, str]] = None,
905 - priority: int = 0,
906 - tokenization_kwargs: Optional[dict[str, Any]] = None,
907 - ) -> AsyncGenerator[PoolingRequestOutput, None]:
908 - raise NotImplementedError(
909 - "Pooling models are not supported in vLLM V0")
910 -
911 - async def abort(self, request_id: Union[str, Iterable[str]]) -> None:
912 - """Abort a request.
913 -
914 - Abort a submitted request. If the request is finished or not found,
915 - this method will be a no-op.
916 -
917 - Args:
918 - request_id: The unique id of the request.
919 - """
920 - if not isinstance(request_id, str):
921 - raise RuntimeError("Only single-request abort supported in"
922 - " deprecated V0")
923 - if not self.is_running:
924 - raise AsyncEngineDeadError(
925 - "Background loop is not running. If it was running, "
926 - "inspect the output to find the stacktrace of the "
927 - "error that caused the background loop to stop "
928 - "(AsyncEngineDeadError).")
929 -
930 - return self._abort(request_id)
931 -
932 - def _abort(self, request_id: str) -> None:
933 - """Abort a request.
934 -
935 - Abort a submitted request. If the request is finished or not found,
936 - this method will be a no-op.
937 -
938 - Args:
939 - request_id: The unique id of the request.
940 - """
941 - self._request_tracker.abort_request(request_id,
942 - exception=asyncio.CancelledError,
943 - verbose=self.log_requests)
944 -
945 - async def get_vllm_config(self) -> VllmConfig:
946 - """Get the vllm configuration of the vLLM engine."""
947 - return self.engine.get_vllm_config()
948 -
949 - async def get_model_config(self) -> ModelConfig:
950 - """Get the model configuration of the vLLM engine."""
951 - return self.engine.get_model_config()
952 -
953 - async def get_parallel_config(self) -> ParallelConfig:
954 - """Get the parallel configuration of the vLLM engine."""
955 - return self.engine.get_parallel_config()
956 -
957 - async def get_scheduler_config(self) -> SchedulerConfig:
958 - """Get the scheduling configuration of the vLLM engine."""
959 - return self.engine.get_scheduler_config()
960 -
961 - async def get_lora_config(self) -> LoRAConfig:
962 - """Get the lora configuration of the vLLM engine."""
963 - return self.engine.get_lora_config()
964 -
965 - async def do_log_stats(
966 - self,
967 - scheduler_outputs: Optional[SchedulerOutputs] = None,
968 - model_output: Optional[List[SamplerOutput]] = None) -> None:
969 - self.engine.do_log_stats()
970 -
971 - async def check_health(self) -> None:
972 - """Raises an error if engine is unhealthy."""
973 - t = time.perf_counter()
974 - logger.debug("Starting health check...")
975 - if self.is_stopped:
976 - raise AsyncEngineDeadError("Background loop is stopped.")
977 -
978 - await self.engine.check_health_async()
979 - logger.debug("Health check took %fs", time.perf_counter() - t)
980 -
981 - async def is_tracing_enabled(self) -> bool:
982 - return self.engine.is_tracing_enabled()
983 -
984 - def add_logger(self, logger_name: str, logger: StatLoggerBase) -> None:
985 - self.engine.add_logger(logger_name=logger_name, logger=logger)
986 -
987 - def remove_logger(self, logger_name: str) -> None:
988 - self.engine.remove_logger(logger_name=logger_name)
989 -
990 - async def start_profile(self) -> None:
991 - self.engine.start_profile()
992 -
993 - async def stop_profile(self) -> None:
994 - self.engine.stop_profile()
995 -
996 - async def reset_mm_cache(self) -> None:
997 - self.engine.reset_mm_cache()
998 -
999 - async def reset_prefix_cache(self,
1000 - device: Optional[Device] = None) -> None:
1001 - self.engine.reset_prefix_cache(device)
1002 -
1003 - async def sleep(self, level: int = 1) -> None:
1004 - await self.reset_prefix_cache()
1005 - self.engine.sleep(level)
1006 -
1007 - async def wake_up(self, tags: Optional[list[str]] = None) -> None:
1008 - self.engine.wake_up(tags)
1009 -
1010 - async def is_sleeping(self) -> bool:
1011 - return self.engine.is_sleeping()
1012 -
1013 - async def add_lora(self, lora_request: LoRARequest) -> bool:
1014 - return self.engine.add_lora(lora_request)
1015 -
1016 - async def collective_rpc(self,
1017 - method: str,
1018 - timeout: Optional[float] = None,
1019 - args: tuple = (),
1020 - kwargs: Optional[dict] = None):
1021 - """
1022 - Perform a collective RPC call to the given path.
1023 - """
1024 - return await self.engine.collective_rpc_async(method, timeout, args,
1025 - kwargs)
1026 -
1027 -
1028 -# TODO(v1): Remove this class proxy when V1 goes default.
1029 -if envs.is_set("VLLM_USE_V1") and envs.VLLM_USE_V1:
1030 - from vllm.v1.engine.async_llm import AsyncLLM
1031 -
1032 - AsyncLLMEngine = AsyncLLM # type: ignore
6 +AsyncLLMEngine = AsyncLLM # type: ignore
vllm/entrypoints/launcher.py
-2
@@ -11,7 +11,6 @@ import uvicorn
11 from fastapi import FastAPI, Request, Response
12
13 from vllm import envs
14 -from vllm.engine.async_llm_engine import AsyncEngineDeadError
14 from vllm.engine.protocol import EngineClient
15 from vllm.entrypoints.constants import (H11_MAX_HEADER_COUNT_DEFAULT,
16 H11_MAX_INCOMPLETE_EVENT_SIZE_DEFAULT)
@@ -154,7 +153,6 @@ def _add_shutdown_handlers(app: FastAPI, server: uvicorn.Server) -> None:
153 """
154
155 @app.exception_handler(RuntimeError)
157 - @app.exception_handler(AsyncEngineDeadError)
156 @app.exception_handler(EngineDeadError)
157 @app.exception_handler(EngineGenerateError)
158 async def runtime_exception_handler(request: Request, __):
vllm/entrypoints/openai/api_server.py
+26 -43
@@ -38,7 +38,6 @@ from typing_extensions import assert_never
38 import vllm.envs as envs
39 from vllm.config import VllmConfig
40 from vllm.engine.arg_utils import AsyncEngineArgs
41 -from vllm.engine.async_llm_engine import AsyncLLMEngine # type: ignore
41 from vllm.engine.protocol import EngineClient
42 from vllm.entrypoints.chat_utils import (load_chat_template,
43 resolve_hf_chat_template,
@@ -201,50 +200,34 @@ async def build_async_engine_client_from_engine_args(
200 vllm_config = engine_args.create_engine_config(usage_context=usage_context)
201
202 # V1 AsyncLLM.
204 - if envs.VLLM_USE_V1:
205 - if disable_frontend_multiprocessing:
206 - logger.warning(
207 - "V1 is enabled, but got --disable-frontend-multiprocessing. "
208 - "To disable frontend multiprocessing, set VLLM_USE_V1=0.")
209 -
210 - from vllm.v1.engine.async_llm import AsyncLLM
211 - async_llm: Optional[AsyncLLM] = None
212 - client_count = client_config.pop(
213 - "client_count") if client_config else 1
214 - client_index = client_config.pop(
215 - "client_index") if client_config else 0
216 - try:
217 - async_llm = AsyncLLM.from_vllm_config(
218 - vllm_config=vllm_config,
219 - usage_context=usage_context,
220 - enable_log_requests=engine_args.enable_log_requests,
221 - disable_log_stats=engine_args.disable_log_stats,
222 - client_addresses=client_config,
223 - client_count=client_count,
224 - client_index=client_index)
225 -
226 - # Don't keep the dummy data in memory
227 - await async_llm.reset_mm_cache()
228 -
229 - yield async_llm
230 - finally:
231 - if async_llm:
232 - async_llm.shutdown()
203 + assert envs.VLLM_USE_V1
204
234 - # V0 AsyncLLM.
235 - else:
205 + if disable_frontend_multiprocessing:
206 + logger.warning(
207 + "V1 is enabled, but got --disable-frontend-multiprocessing. "
208 + "To disable frontend multiprocessing, set VLLM_USE_V1=0.")
209
237 - engine_client: Optional[EngineClient] = None
238 - try:
239 - engine_client = AsyncLLMEngine.from_vllm_config(
240 - vllm_config=vllm_config,
241 - usage_context=usage_context,
242 - enable_log_requests=engine_args.enable_log_requests,
243 - disable_log_stats=engine_args.disable_log_stats)
244 - yield engine_client
245 - finally:
246 - if engine_client and hasattr(engine_client, "shutdown"):
247 - engine_client.shutdown()
210 + from vllm.v1.engine.async_llm import AsyncLLM
211 + async_llm: Optional[AsyncLLM] = None
212 + client_count = client_config.pop("client_count") if client_config else 1
213 + client_index = client_config.pop("client_index") if client_config else 0
214 + try:
215 + async_llm = AsyncLLM.from_vllm_config(
216 + vllm_config=vllm_config,
217 + usage_context=usage_context,
218 + enable_log_requests=engine_args.enable_log_requests,
219 + disable_log_stats=engine_args.disable_log_stats,
220 + client_addresses=client_config,
221 + client_count=client_count,
222 + client_index=client_index)
223 +
224 + # Don't keep the dummy data in memory
225 + await async_llm.reset_mm_cache()
226 +
227 + yield async_llm
228 + finally:
229 + if async_llm:
230 + async_llm.shutdown()
231
232
233 async def validate_json_request(raw_request: Request):