From 4d40332de6c21cc668bfa7970313272f678a0553 Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Thu, 24 Jul 2025 13:35:07 -0400 Subject: [PATCH 01/12] draft Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- .../services/queue_service/queue.py | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/vllm_router/services/queue_service/queue.py diff --git a/src/vllm_router/services/queue_service/queue.py b/src/vllm_router/services/queue_service/queue.py new file mode 100644 index 000000000..a2db2cda1 --- /dev/null +++ b/src/vllm_router/services/queue_service/queue.py @@ -0,0 +1,77 @@ +# services/queue_manager.py + +import asyncio +from typing import Dict, Any, Tuple +from vllm_router.stats.engine_stats import EngineStatsScraper + +class EndpointQueueManager: + def __init__(self): + self.endpoint_queues: Dict[str, asyncio.PriorityQueue] = {} + self.queue_tasks: Dict[str, asyncio.Task] = {} + self.scraper = EngineStatsScraper(scrape_interval=5) + + def get_queue(self, endpoint_url: str) -> asyncio.PriorityQueue: + if endpoint_url not in self.endpoint_queues: + self.endpoint_queues[endpoint_url] = asyncio.PriorityQueue() + return self.endpoint_queues[endpoint_url] + + async def enqueue( + self, endpoint_url: str, request: Dict[str, Any], priority: int = 0 + ): + queue = self.get_queue(endpoint_url) + await queue.put((priority, request)) + + def start_scheduler_for(self, endpoint_url: str): + if endpoint_url not in self.queue_tasks: + queue = self.get_queue(endpoint_url) + self.queue_tasks[endpoint_url] = asyncio.create_task( + self._scheduler_loop(endpoint_url, queue) + ) + + async def _scheduler_loop(self, endpoint_url: str, queue: asyncio.PriorityQueue): + while True: + await self._wait_for_endpoint_to_be_free(endpoint_url) + + try: + # Only dequeue if a request is waiting + priority, request = queue.get_nowait() + except asyncio.QueueEmpty: + await asyncio.sleep(0.1) + continue + + await self.dispatch_request(endpoint_url, request) + + async def _wait_for_endpoint_to_be_free(self, endpoint_url: str): + """ + Wait until endpoint has capacity to handle a new request. + """ + while True: + stats = self.scraper.get_engine_stats().get(endpoint_url) + if stats and stats.num_running_requests < 4 and stats.gpu_cache_usage_perc < 90: + return + await asyncio.sleep(0.2) + + async def dispatch_request(self, endpoint_url: str, req: Dict[str, Any]): + from vllm_router.services.request_service.request import process_request + + try: + await process_request( + req["request"], req["body"], endpoint_url, + req["request_id"], req["endpoint"], req["background_tasks"] + ) + except Exception as e: + print(f"[Queue Dispatch Error] {e}") + + def get_queue_length(self, endpoint_url: str) -> int: + return self.get_queue(endpoint_url).qsize() + + def stop_scheduler(self, endpoint_url: str): + if endpoint_url in self.queue_tasks: + self.queue_tasks[endpoint_url].cancel() + del self.queue_tasks[endpoint_url] + + @staticmethod + def calculate_request_priority(request) -> int: + return 0 + +queue_manager = EndpointQueueManager() \ No newline at end of file From 96da20e484ba66d56ea5b5983d17435030602d72 Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Thu, 24 Jul 2025 21:09:33 -0400 Subject: [PATCH 02/12] edited Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- .../services/queue_service/queue.py | 168 +++++++++++++----- .../services/request_service/request.py | 69 ++++--- 2 files changed, 168 insertions(+), 69 deletions(-) diff --git a/src/vllm_router/services/queue_service/queue.py b/src/vllm_router/services/queue_service/queue.py index a2db2cda1..25bfa5b64 100644 --- a/src/vllm_router/services/queue_service/queue.py +++ b/src/vllm_router/services/queue_service/queue.py @@ -1,77 +1,149 @@ # services/queue_manager.py import asyncio +import time from typing import Dict, Any, Tuple from vllm_router.stats.engine_stats import EngineStatsScraper class EndpointQueueManager: - def __init__(self): + def __init__(self, max_queue_wait_time = 10): self.endpoint_queues: Dict[str, asyncio.PriorityQueue] = {} - self.queue_tasks: Dict[str, asyncio.Task] = {} + self.conditions: Dict[str, asyncio.Condition] = {} + self.scraper = EngineStatsScraper(scrape_interval=5) + self.max_queue_wait_time = max_queue_wait_time + #kept for shutdown + self.endpoint_tasks: Dict[str, asyncio.Task] = {} + self._shutdown_event = asyncio.Event() - def get_queue(self, endpoint_url: str) -> asyncio.PriorityQueue: - if endpoint_url not in self.endpoint_queues: - self.endpoint_queues[endpoint_url] = asyncio.PriorityQueue() - return self.endpoint_queues[endpoint_url] + def register_endpoint(self, endpoint_url: str): + if endpoint_url in self.endpoint_queues: + return #already registered + + self.endpoint_queues[endpoint_url] = asyncio.PriorityQueue() + self.conditions[endpoint_url] = asyncio.Condition() + task = asyncio.create_task(self._scheduler_loop(endpoint_url)) + self.endpoint_tasks[endpoint_url] = task async def enqueue( self, endpoint_url: str, request: Dict[str, Any], priority: int = 0 ): - queue = self.get_queue(endpoint_url) - await queue.put((priority, request)) - - def start_scheduler_for(self, endpoint_url: str): - if endpoint_url not in self.queue_tasks: - queue = self.get_queue(endpoint_url) - self.queue_tasks[endpoint_url] = asyncio.create_task( - self._scheduler_loop(endpoint_url, queue) - ) + if self._shutdown_event.is_set(): + raise RuntimeError("Scheduler is shutting down, can't enqueue new requests.") - async def _scheduler_loop(self, endpoint_url: str, queue: asyncio.PriorityQueue): - while True: - await self._wait_for_endpoint_to_be_free(endpoint_url) - - try: - # Only dequeue if a request is waiting - priority, request = queue.get_nowait() - except asyncio.QueueEmpty: - await asyncio.sleep(0.1) - continue - - await self.dispatch_request(endpoint_url, request) - - async def _wait_for_endpoint_to_be_free(self, endpoint_url: str): - """ - Wait until endpoint has capacity to handle a new request. - """ - while True: - stats = self.scraper.get_engine_stats().get(endpoint_url) - if stats and stats.num_running_requests < 4 and stats.gpu_cache_usage_perc < 90: - return - await asyncio.sleep(0.2) + await self.endpoint_queues[endpoint_url].put((priority, time.time(). request)) + async with self.conditions[endpoint_url]: + self.conditions[endpoint_url].notify() #request available in the queue + + async def _scheduler_loop(self, endpoint_url: str): + queue = self.queues[endpoint_url] + condition = self.conditions[endpoint_url] + + try: + while not self._shutdown_event.is_set(): + async with condition: + await condition.wait_for(lambda: not queue.empty()) + + try: + priority, enqueue_time, request = queue.get_nowait() + except asyncio.QueueEmpty: + continue - async def dispatch_request(self, endpoint_url: str, req: Dict[str, Any]): + wait_duration = time.time() - enqueue_time #stale request logic + if wait_duration > self.max_queue_wait_time: + await self._reroute_or_dispatch_stale_request(request, endpoint_url) + continue + + if self._endpoint_is_free(endpoint_url): + asyncio.create_task(self._dispatch_and_signal(endpoint_url, request)) + else: + await queue.put((priority - 1, enqueue_time, request)) #requeue w higher prio + async with condition: + await condition.wait() # only wake up when notified + + except asyncio.CancelledError: + print(f"Scheduler loop for {endpoint_url} cancelled.") + except Exception as e: + print(f"Error in scheduler loop ({endpoint_url}): {e}") + + def _endpoint_is_free(self, endpoint_url: str) -> bool: #TODO: what stats? + """queue waits for endpoint load to decrease before dequeing waiting request""" + + stats = self.scraper.get_engine_stats().get(endpoint_url) + return stats and stats.num_running_requests < 4 and stats.gpu_cache_usage_perc < 90 #TODO: configurable? + + async def _dispatch_and_signal(self, endpoint_url: str, req: Dict[str, Any]): from vllm_router.services.request_service.request import process_request try: - await process_request( + + stream_generator = process_request( req["request"], req["body"], endpoint_url, req["request_id"], req["endpoint"], req["background_tasks"] ) + headers, status_code = await anext(stream_generator) + headers_dict = {key: value for key, value in headers.items()} + headers_dict["X-Request-Id"] = request_id + return StreamingResponse( #TODO + stream_generator, + status_code=status_code, + headers=headers_dict, + media_type="text/event-stream", + ) + except Exception as e: print(f"[Queue Dispatch Error] {e}") + finally: + async with self.conditions[endpoint_url]: + self.conditions[endpoint_url].notify() - def get_queue_length(self, endpoint_url: str) -> int: - return self.get_queue(endpoint_url).qsize() - def stop_scheduler(self, endpoint_url: str): - if endpoint_url in self.queue_tasks: - self.queue_tasks[endpoint_url].cancel() - del self.queue_tasks[endpoint_url] + async def _reroute_or_dispatch_stale_request(self, request: dict, original_endpoint: str): + request_id = request.get("request_id") + session_id = request.get("session_id") + model = request.get("model_name") - @staticmethod - def calculate_request_priority(request) -> int: + # TODO: Use KV cache hit estimation in future + has_session_affinity = session_id and self._session_matches_endpoint(session_id, original_endpoint) + + if not has_session_affinity: + new_endpoint = self.find_best_endpoint(model_name=model, exclude=[original_endpoint]) + if new_endpoint and new_endpoint != original_endpoint: + print(f"[Rerouting] Request {request_id} → {new_endpoint} (was {original_endpoint})") + + if self._endpoint_is_free(new_endpoint): + asyncio.create_task(self._dispatch_and_signal(new_endpoint, request)) + else: + queue = self.endpoint_queues[new_endpoint] + async with self.conditions[new_endpoint]: + await queue.put((self.calculate_request_priority(request), time.time(), request)) + self.conditions[new_endpoint].notify() + return + + # Session matches → keep on original endpoint + print(f"[Requeue] Request {request_id} stays at {original_endpoint}") + queue = self.endpoint_queues[original_endpoint] + async with self.conditions[original_endpoint]: + await queue.put((self.calculate_request_priority(request) - 1, time.time(), request)) + self.conditions[original_endpoint].notify() + + + + def calculate_request_priority(self, request) -> int: return 0 -queue_manager = EndpointQueueManager() \ No newline at end of file + + async def shutdown(self): + print("Shutting down scheduler...") + + self._shutdown_event.set() + + for task in self.endpoint_tasks.values(): + task.cancel() + + # wait for all tasks to cancel + await asyncio.gather(*self.endpoint_tasks.values(), return_exceptions=True) + + print("Scheduler shutdown complete.") + +queue_manager = EndpointQueueManager(max_queue_wait_time = 10) \ No newline at end of file diff --git a/src/vllm_router/services/request_service/request.py b/src/vllm_router/services/request_service/request.py index 5161f7747..1c7378a44 100644 --- a/src/vllm_router/services/request_service/request.py +++ b/src/vllm_router/services/request_service/request.py @@ -17,6 +17,7 @@ import os import time import uuid +import asyncio import httpx from fastapi import BackgroundTasks, HTTPException, Request @@ -35,6 +36,7 @@ is_request_rewriter_initialized, ) from vllm_router.utils import replace_model_in_request_body, update_content_length +from vllm_router.services.queue_service.queue import queue_manager try: # Semantic cache integration @@ -59,6 +61,7 @@ async def process_request( endpoint, background_tasks: BackgroundTasks, debug_request=None, + condition: asyncio.Condition = None ): """ Process a request by sending it to the chosen backend. @@ -122,7 +125,9 @@ async def process_request( request.app.state.request_stats_monitor.on_request_complete( backend_url, request_id, time.time() ) - + if condition: #lets scheduler know that an endpoint-specific request has completed, can perhaps dispatch new + async with condition: + condition.notify() # if debug_request: # logger.debug(f"Finished the request with request id: {debug_request.headers.get('x-request-id', None)} at {time.time()}") # Store in semantic cache if applicable @@ -282,26 +287,48 @@ async def route_general_request( logger.debug(f"Debug session extraction - Request headers: {dict(request.headers)}") logger.debug(f"Debug session extraction - Extracted session ID: {session_id}") - logger.info( - f"Routing request {request_id} with session id {session_id_display} to {server_url} at {curr_time}, process time = {curr_time - in_router_time:.4f}" - ) - stream_generator = process_request( - request, - request_body, - server_url, - request_id, - endpoint, - background_tasks, - ) - headers, status_code = await anext(stream_generator) - headers_dict = {key: value for key, value in headers.items()} - headers_dict["X-Request-Id"] = request_id - return StreamingResponse( - stream_generator, - status_code=status_code, - headers=headers_dict, - media_type="text/event-stream", - ) + # enqueue if endpoint load is too high + if not queue_manager._endpoint_is_free(server_url): + queue_manager.register_endpoint(server_url) #if queue does not already exist + + await queue_manager.enqueue( + server_url, + { + "request": request, + "request_id": request_id, + "body": request_body, + "endpoint": endpoint, + "background_tasks": background_tasks + }, + priority=queue_manager.calculate_request_priority(request) + ) + + return JSONResponse( + status_code=202, + content={"status": "queued", "endpoint": server_url} + ) + else: + logger.info( + f"Routing request {request_id} with session id {session_id_display} to {server_url} at {curr_time}, process time = {curr_time - in_router_time:.4f}" + ) + stream_generator = process_request( + request, + request_body, + server_url, + request_id, + endpoint, + background_tasks, + condition=queue_manager.conditions[server_url] + ) + headers, status_code = await anext(stream_generator) + headers_dict = {key: value for key, value in headers.items()} + headers_dict["X-Request-Id"] = request_id + return StreamingResponse( + stream_generator, + status_code=status_code, + headers=headers_dict, + media_type="text/event-stream", + ) async def send_request_to_prefiller( From 5a59d6cc14e9d1c21f72ac85db106557545e49f0 Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Mon, 28 Jul 2025 16:43:04 -0400 Subject: [PATCH 03/12] readme, edits Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- proposals/imgs/flowchart.png | Bin 0 -> 72895 bytes proposals/queue_manager_README.md | 149 ++++++++++++++++++ src/vllm_router/app.py | 12 ++ src/vllm_router/parsers/parser.py | 6 + .../services/queue_service/queue.py | 22 ++- .../services/request_service/request.py | 5 +- 6 files changed, 187 insertions(+), 7 deletions(-) create mode 100644 proposals/imgs/flowchart.png create mode 100644 proposals/queue_manager_README.md diff --git a/proposals/imgs/flowchart.png b/proposals/imgs/flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..684bb586e6455196e9fd8d593f94da711b02f971 GIT binary patch literal 72895 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ⅇ!4CDR2|J8)4f6KeBP#S- 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 IpP0S-Ut_GJMF0Q* literal 0 HcmV?d00001 diff --git a/proposals/queue_manager_README.md b/proposals/queue_manager_README.md new file mode 100644 index 000000000..266173c0f --- /dev/null +++ b/proposals/queue_manager_README.md @@ -0,0 +1,149 @@ +# Queue Manager for LLM Endpoint Routing + +This module implements an asynchronous queue manager for dispatching of LLM inference requests to backend endpoints. It directly dispatches or queues requests based on endpoint load and endpoint metrics, improving overall Quality of Experience (QoE). + +--- + +## Features + +- Per-endpoint asynchronous request queues +- Condition-based dispatching using `asyncio.Condition` +- Request rerouting if an endpoint remains overloaded too long +- Session affinity preservation (stubbed for future KV cache usage) +- Graceful shutdown of all schedulers + +--- + +## Flow Chart + +![Logic flow for incoming request.](imgs/flowchart.png) + +--- + + +## File: `services/queue_manager.py` + +### Class: `EndpointQueueManager` + +This class manages: + +- `endpoint_queues`: A `PriorityQueue` per endpoint holding pending requests. +- `conditions`: An `asyncio.Condition` per endpoint used to notify the scheduler loop. +- `endpoint_tasks`: Background async tasks for each endpoint’s queue loop. +- `EngineStatsScraper`: Periodically scrapes GPU & model stats per endpoint. + +--- + +## Request Lifecycle + +### 1. Check Endpoint Availability + +```python +if not queue_manager._endpoint_is_free(server_url): +``` + +- If the endpoint is overloaded (e.g. high GPU usage or too many active requests), the request is queued. +- If it's free, the request is dispatched immediately. + +--- + +### 2. Enqueue Logic + +```python +queue_manager.register_endpoint(server_url) + +await queue_manager.enqueue( + server_url, + { + "request": request, + "request_id": request_id, + "body": request_body, + "endpoint": endpoint, + "background_tasks": background_tasks + }, + priority=queue_manager.calculate_request_priority(request) +) +``` + +- Registers the endpoint queue and scheduler if not already present. +- Adds the request to a `PriorityQueue`. +- Notifies the condition variable to wake the scheduler. + +If queued, returns a `202 Accepted`: + +```json +{ + "status": "queued", + "endpoint": "http://..." +} +``` + +--- + +### 3. Scheduler Loop + +```python +async def _scheduler_loop(self, endpoint_url: str): +``` + +Runs a background task for each endpoint: + +- Waits for new requests in the queue. +- If a request has waited longer than max_queue_wait_time, the scheduler calls `_reroute_or_dispatch_stale_request` to determine next actions. +- If the endpoint is free, dispatches the request. + + +--- + +### 4. Dispatch Logic + +```python +async def _dispatch_and_signal(...) +``` + +- Sends the request to the backend via `process_request(...)`. +- Returns a streaming response with appropriate headers. + +--- + +### 5. Rerouting Stale Requests + +If a request exceeds the `max_queue_wait_time` threshold: + +```python +await self._reroute_or_dispatch_stale_request(request, original_endpoint) +``` + +- Attempts to reroute the request to a different free endpoint. +- If session affinity (based on session ID) applies, keeps it on the original endpoint. +- If the new endpoint is also busy, queues the request there. + +--- + +## Configuration + +```python +queue_manager = EndpointQueueManager(max_queue_wait_time=10) +``` + +- `max_queue_wait_time`: Max seconds a request can wait in queue before being rerouted or retried. + +--- + + +## Dependencies + +- `asyncio` +- `EngineStatsScraper` from `vllm_router.stats.engine_stats` +- `process_request()` from `vllm_router.services.request_service.request` + +--- + +## TODOs + +- [ ] Implement KV cache-aware session affinity logic +- [ ] Improve request priority classification +- [ ] Make queue limits and load thresholds configurable +- [ ] Retry policies and smarter rerouting heuristics + +--- diff --git a/src/vllm_router/app.py b/src/vllm_router/app.py index dd1ae5a12..a74fb5b95 100644 --- a/src/vllm_router/app.py +++ b/src/vllm_router/app.py @@ -62,6 +62,11 @@ set_ulimit, ) +from vllm_router.services.queue_service.queue import ( + initialize_queue_manager, + get_queue_manager +) + try: # Semantic cache integration from vllm_router.experimental.semantic_cache import ( @@ -103,6 +108,11 @@ async def lifespan(app: FastAPI): logger.info("Closing dynamic config watcher") dyn_cfg_watcher.close() + # close the queue manager + queue_manager = get_queue_manager() + if queue_manager is not None: + logger.info("Closing per endpoint queues and tasks") + queue_manager.close() def initialize_all(app: FastAPI, args): """ @@ -160,6 +170,8 @@ def initialize_all(app: FastAPI, args): # Initialize singletons via custom functions. initialize_engine_stats_scraper(args.engine_stats_interval) initialize_request_stats_monitor(args.request_stats_window) + # queue + initialize_queue_manager(args.max_wait_time) if args.enable_batch_api: logger.info("Initializing batch API") diff --git a/src/vllm_router/parsers/parser.py b/src/vllm_router/parsers/parser.py index 942231656..7a778ec39 100644 --- a/src/vllm_router/parsers/parser.py +++ b/src/vllm_router/parsers/parser.py @@ -313,6 +313,12 @@ def parse_args(): help="The threshold for kv-aware routing.", ) + parser.add_argument( + "--max-wait-time", + type=int, + default=10, + help="The maximum amount of time a request waits in a queue before it gets rerouted. E.g., 10s" , + ) args = parser.parse_args() args = load_initial_config_from_config_json_if_required(parser, args) diff --git a/src/vllm_router/services/queue_service/queue.py b/src/vllm_router/services/queue_service/queue.py index 25bfa5b64..2ce45ed83 100644 --- a/src/vllm_router/services/queue_service/queue.py +++ b/src/vllm_router/services/queue_service/queue.py @@ -3,14 +3,15 @@ import asyncio import time from typing import Dict, Any, Tuple -from vllm_router.stats.engine_stats import EngineStatsScraper +from vllm_router.stats.engine_stats import get_engine_stats_scraper +_global_queue_manager = None class EndpointQueueManager: def __init__(self, max_queue_wait_time = 10): self.endpoint_queues: Dict[str, asyncio.PriorityQueue] = {} self.conditions: Dict[str, asyncio.Condition] = {} - self.scraper = EngineStatsScraper(scrape_interval=5) + self.scraper = get_engine_stats_scraper() self.max_queue_wait_time = max_queue_wait_time #kept for shutdown self.endpoint_tasks: Dict[str, asyncio.Task] = {} @@ -93,9 +94,9 @@ async def _dispatch_and_signal(self, endpoint_url: str, req: Dict[str, Any]): except Exception as e: print(f"[Queue Dispatch Error] {e}") - finally: + """finally: async with self.conditions[endpoint_url]: - self.conditions[endpoint_url].notify() + self.conditions[endpoint_url].notify()""" async def _reroute_or_dispatch_stale_request(self, request: dict, original_endpoint: str): @@ -133,7 +134,7 @@ def calculate_request_priority(self, request) -> int: return 0 - async def shutdown(self): + async def close(self): print("Shutting down scheduler...") self._shutdown_event.set() @@ -146,4 +147,13 @@ async def shutdown(self): print("Scheduler shutdown complete.") -queue_manager = EndpointQueueManager(max_queue_wait_time = 10) \ No newline at end of file + + +def initialize_queue_manager(max_queue_wait_time=10): + global _global_queue_manager + _global_queue_manager = EndpointQueueManager(max_queue_wait_time=max_queue_wait_time) + +def get_queue_manager() -> "EndpointQueueManager": + if _global_queue_manager is None: + raise ValueError("Queue manager not initialized") + return _global_queue_manager diff --git a/src/vllm_router/services/request_service/request.py b/src/vllm_router/services/request_service/request.py index 1c7378a44..8bfdcbbc3 100644 --- a/src/vllm_router/services/request_service/request.py +++ b/src/vllm_router/services/request_service/request.py @@ -36,7 +36,7 @@ is_request_rewriter_initialized, ) from vllm_router.utils import replace_model_in_request_body, update_content_length -from vllm_router.services.queue_service.queue import queue_manager +from vllm_router.services.queue_service.queue import get_queue_manager try: # Semantic cache integration @@ -161,6 +161,9 @@ async def route_general_request( Returns: StreamingResponse: A response object that streams data from the backend server to the client. """ + #if queue enabled? + queue_manager = get_queue_manager() + if isinstance(request.app.state.router, DisaggregatedPrefillRouter): response = await route_disaggregated_prefill_request( request, endpoint, background_tasks From efd0c58007053cfb0baff3cbb564465bc09ee883 Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:31:55 -0400 Subject: [PATCH 04/12] more edits, test case, not fully working Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- src/tests/test_queue.py | 181 ++++++++++++++++++ src/vllm_router/app.py | 6 +- src/vllm_router/parsers/parser.py | 14 ++ .../services/queue_service/queue.py | 81 +++++--- .../services/request_service/request.py | 10 +- 5 files changed, 257 insertions(+), 35 deletions(-) create mode 100644 src/tests/test_queue.py diff --git a/src/tests/test_queue.py b/src/tests/test_queue.py new file mode 100644 index 000000000..df9169ae2 --- /dev/null +++ b/src/tests/test_queue.py @@ -0,0 +1,181 @@ +import pytest +import asyncio +import time +from unittest.mock import AsyncMock, MagicMock, patch +from vllm_router.services.queue_service.queue import ( + initialize_queue_manager, + get_queue_manager, +) +from fastapi.responses import StreamingResponse +import pytest_asyncio + + +@pytest.fixture +def mock_scraper(): + scraper = MagicMock() + scraper.get_engine_stats.return_value = { + "endpoint1": MagicMock(num_running_requests=0, gpu_cache_usage_perc=0), + "endpoint2": MagicMock(num_running_requests=5, gpu_cache_usage_perc=50), + } + return scraper + + +@pytest_asyncio.fixture +async def queue_manager(mock_scraper): + initialize_queue_manager( + max_queue_wait_time=10, + max_running_requests=10, + max_gpu_perc=95, + scraper=mock_scraper + ) + manager = get_queue_manager() + await manager.register_endpoint("endpoint1") + await manager.register_endpoint("endpoint2") + yield manager + await manager.close() + + +@pytest.mark.asyncio +async def test_queue_manager_initialization(mock_scraper): + initialize_queue_manager( + max_queue_wait_time=10, + max_running_requests=10, + max_gpu_perc=95, + scraper=mock_scraper + ) + manager = get_queue_manager() + assert manager.max_queue_wait_time == 10 + assert manager.max_running_requests == 10 + assert manager.max_gpu_perc == 95 + assert manager.scraper == mock_scraper + + +@pytest.mark.asyncio +async def test_register_endpoint(queue_manager): + # Already registered by fixture; just test existence + for endpoint in ["endpoint1", "endpoint2"]: + assert endpoint in queue_manager.endpoint_queues + assert endpoint in queue_manager.conditions + assert endpoint in queue_manager.endpoint_tasks + + +@pytest.mark.asyncio +async def test_enqueue_request(queue_manager): + test_request = {"request_id": "test123", "body": "test"} + future = asyncio.Future() + await queue_manager.enqueue("endpoint1", test_request, priority=1, result_future=future) + assert queue_manager.endpoint_queues["endpoint1"]._queue + assert not future.done() + + +@pytest.mark.asyncio +async def test_endpoint_is_free(queue_manager, mock_scraper): + assert queue_manager._endpoint_is_free("endpoint1") is True + assert queue_manager._endpoint_is_free("endpoint2") is True + + mock_scraper.get_engine_stats.return_value["endpoint2"].num_running_requests = 15 + assert queue_manager._endpoint_is_free("endpoint2") is False + + mock_scraper.get_engine_stats.return_value["endpoint2"].num_running_requests = 5 + mock_scraper.get_engine_stats.return_value["endpoint2"].gpu_cache_usage_perc = 96 + assert queue_manager._endpoint_is_free("endpoint2") is False + + +@pytest.mark.asyncio +async def test_dispatch_and_signal(queue_manager): + test_request = { + "request_id": "test123", + "body": "test", + "request": MagicMock(), + "endpoint": "endpoint1", + "background_tasks": MagicMock(), + "_result_future": asyncio.Future() + } + + mock_response = StreamingResponse(content=MagicMock()) + + with patch("vllm_router.services.request_service.request.process_request", new_callable=AsyncMock) as mock_process: + # Simulate response async generator + async def mock_stream(): + yield ({"content-type": "application/json"}, 200) + yield StreamingResponse(content=MagicMock()) + + mock_process.return_value = mock_stream() + await queue_manager._dispatch_and_signal("endpoint1", test_request) + + assert test_request["_result_future"].done() + assert isinstance(test_request["_result_future"].result(), StreamingResponse) + + +@pytest.mark.asyncio +async def test_scheduler_loop(queue_manager): + test_request = { + "request_id": "test123", + "body": "test", + "request": MagicMock(), # ← Required by `process_request(...)` + "endpoint": "endpoint1", # ← Required + "background_tasks": MagicMock(), + "_result_future": asyncio.Future() + } + + await queue_manager.enqueue("endpoint1", test_request) + await asyncio.sleep(1) + assert not queue_manager.endpoint_queues["endpoint1"]._queue + assert test_request["_result_future"].done() + + +@pytest.mark.asyncio +async def test_stale_request_rerouting(queue_manager): + stale_request = { + "request_id": "stale123", + "body": "test", + "_result_future": asyncio.Future(), + "enqueue_time": time.time() - 20 + } + + with patch.object(queue_manager, "_reroute_or_dispatch_stale_request", new_callable=AsyncMock) as mock_reroute: + await queue_manager.enqueue("endpoint1", stale_request) + for _ in range(10): + if mock_reroute.call_count: + break + await asyncio.sleep(0.1) + mock_reroute.assert_called_once() + + mock_reroute.assert_called_once() + + +@pytest.mark.asyncio +async def test_shutdown(queue_manager): + assert not queue_manager._shutdown_event.is_set() + await queue_manager.close() + assert queue_manager._shutdown_event.is_set() + for task in queue_manager.endpoint_tasks.values(): + assert task.done() + + +@pytest.mark.asyncio +async def test_singleton_pattern(): + from vllm_router.services.queue_service import queue as queue_module + + queue_module._global_queue_manager = None # Reset locally + + scraper = MagicMock() + scraper.get_engine_stats.return_value = { + "endpoint1": MagicMock(num_running_requests=0, gpu_cache_usage_perc=0), + } + + queue_module.initialize_queue_manager( + max_queue_wait_time=10, + max_running_requests=10, + max_gpu_perc=95, + scraper=scraper + ) + manager1 = queue_module.get_queue_manager() + manager2 = queue_module.get_queue_manager() + assert manager1 is manager2 + + await manager1.close() + queue_module._global_queue_manager = None + + with pytest.raises(ValueError, match="Queue manager not initialized"): + queue_module.get_queue_manager() diff --git a/src/vllm_router/app.py b/src/vllm_router/app.py index a74fb5b95..b5bf32f6d 100644 --- a/src/vllm_router/app.py +++ b/src/vllm_router/app.py @@ -170,8 +170,10 @@ def initialize_all(app: FastAPI, args): # Initialize singletons via custom functions. initialize_engine_stats_scraper(args.engine_stats_interval) initialize_request_stats_monitor(args.request_stats_window) - # queue - initialize_queue_manager(args.max_wait_time) + # Initialize queue + initialize_queue_manager(args.max_wait_time, + args.max_running_requests, + args.max_gpu_perc) if args.enable_batch_api: logger.info("Initializing batch API") diff --git a/src/vllm_router/parsers/parser.py b/src/vllm_router/parsers/parser.py index 7a778ec39..7d32b9e0e 100644 --- a/src/vllm_router/parsers/parser.py +++ b/src/vllm_router/parsers/parser.py @@ -319,6 +319,20 @@ def parse_args(): default=10, help="The maximum amount of time a request waits in a queue before it gets rerouted. E.g., 10s" , ) + + parser.add_argument( + "--max-running-requests", + type=int, + default=10, + help="The maximum number of running requests in an endpoint before the router enqueues an incoming request", + ) + + parser.add_argument( + "--max_gpu_perc", + type=int, + default=95, + help="The maximum GPU use percentage of an endpoint before the router enqueues an incoming request", + ) args = parser.parse_args() args = load_initial_config_from_config_json_if_required(parser, args) diff --git a/src/vllm_router/services/queue_service/queue.py b/src/vllm_router/services/queue_service/queue.py index 2ce45ed83..2e4a557a6 100644 --- a/src/vllm_router/services/queue_service/queue.py +++ b/src/vllm_router/services/queue_service/queue.py @@ -2,22 +2,29 @@ import asyncio import time -from typing import Dict, Any, Tuple +from typing import Dict, Any, Optional +from fastapi.responses import StreamingResponse from vllm_router.stats.engine_stats import get_engine_stats_scraper _global_queue_manager = None class EndpointQueueManager: - def __init__(self, max_queue_wait_time = 10): + def __init__(self, max_queue_wait_time, max_running_requests, max_gpu_perc, scraper=None): self.endpoint_queues: Dict[str, asyncio.PriorityQueue] = {} self.conditions: Dict[str, asyncio.Condition] = {} - self.scraper = get_engine_stats_scraper() + self.scraper = scraper or get_engine_stats_scraper() + if self.scraper is None: + raise RuntimeError("Engine stats scraper not initialized.") + #user configurable + self.max_running_requests = max_running_requests + self.max_gpu_perc = max_gpu_perc self.max_queue_wait_time = max_queue_wait_time + #kept for shutdown self.endpoint_tasks: Dict[str, asyncio.Task] = {} self._shutdown_event = asyncio.Event() - def register_endpoint(self, endpoint_url: str): + async def register_endpoint(self, endpoint_url: str): if endpoint_url in self.endpoint_queues: return #already registered @@ -27,17 +34,24 @@ def register_endpoint(self, endpoint_url: str): self.endpoint_tasks[endpoint_url] = task async def enqueue( - self, endpoint_url: str, request: Dict[str, Any], priority: int = 0 + self, endpoint_url: str, request: Dict[str, Any], + priority: int = 0, + result_future: Optional[asyncio.Future] = None ): if self._shutdown_event.is_set(): raise RuntimeError("Scheduler is shutting down, can't enqueue new requests.") - await self.endpoint_queues[endpoint_url].put((priority, time.time(). request)) + if result_future: + request["_result_future"] = result_future + + await self.endpoint_queues[endpoint_url].put((priority, time.time(), request)) async with self.conditions[endpoint_url]: self.conditions[endpoint_url].notify() #request available in the queue async def _scheduler_loop(self, endpoint_url: str): - queue = self.queues[endpoint_url] + print(f"Scheduler started for {endpoint_url}") + + queue = self.endpoint_queues[endpoint_url] condition = self.conditions[endpoint_url] try: @@ -60,45 +74,52 @@ async def _scheduler_loop(self, endpoint_url: str): else: await queue.put((priority - 1, enqueue_time, request)) #requeue w higher prio async with condition: - await condition.wait() # only wake up when notified + await asyncio.wait_for(condition.wait(), timeout=1.0) # 1 second timeout except asyncio.CancelledError: print(f"Scheduler loop for {endpoint_url} cancelled.") except Exception as e: print(f"Error in scheduler loop ({endpoint_url}): {e}") - def _endpoint_is_free(self, endpoint_url: str) -> bool: #TODO: what stats? + def _endpoint_is_free(self, endpoint_url: str) -> bool: #TODO: what stats could be relevant """queue waits for endpoint load to decrease before dequeing waiting request""" stats = self.scraper.get_engine_stats().get(endpoint_url) - return stats and stats.num_running_requests < 4 and stats.gpu_cache_usage_perc < 90 #TODO: configurable? + return (stats + and stats.num_running_requests < self.max_running_requests + and stats.gpu_cache_usage_perc < self.max_gpu_perc) - async def _dispatch_and_signal(self, endpoint_url: str, req: Dict[str, Any]): + async def _dispatch_and_signal(self, endpoint_url: str, request: Dict[str, Any]): from vllm_router.services.request_service.request import process_request + result_future = request.get("_result_future") try: - - stream_generator = process_request( - req["request"], req["body"], endpoint_url, - req["request_id"], req["endpoint"], req["background_tasks"] - ) + stream_generator = process_request(request["request"], request["body"], endpoint_url, + request["request_id"], request["endpoint"], + request["background_tasks"], self.conditions[endpoint_url]) headers, status_code = await anext(stream_generator) - headers_dict = {key: value for key, value in headers.items()} - headers_dict["X-Request-Id"] = request_id - return StreamingResponse( #TODO + headers_dict = dict(headers) + headers_dict["X-Request-Id"] = request["request_id"] + + response = StreamingResponse( stream_generator, status_code=status_code, headers=headers_dict, - media_type="text/event-stream", + media_type="text/event-stream" ) - - except Exception as e: - print(f"[Queue Dispatch Error] {e}") - """finally: - async with self.conditions[endpoint_url]: - self.conditions[endpoint_url].notify()""" + # If this request came from the queue, fulfill the future + + if result_future and not result_future.done(): + result_future.set_result(response) + + except Exception as e: + if result_future and not result_future.done(): + result_future.set_exception(e) + else: + print(f"[Queue Dispatch Error] {e}") + return async def _reroute_or_dispatch_stale_request(self, request: dict, original_endpoint: str): request_id = request.get("request_id") session_id = request.get("session_id") @@ -149,9 +170,13 @@ async def close(self): -def initialize_queue_manager(max_queue_wait_time=10): +def initialize_queue_manager(max_queue_wait_time=10, max_running_requests = 10, max_gpu_perc = 95, + scraper=None): global _global_queue_manager - _global_queue_manager = EndpointQueueManager(max_queue_wait_time=max_queue_wait_time) + _global_queue_manager = EndpointQueueManager(max_queue_wait_time=max_queue_wait_time, + max_running_requests=max_running_requests, + max_gpu_perc=max_gpu_perc, + scraper=scraper) def get_queue_manager() -> "EndpointQueueManager": if _global_queue_manager is None: diff --git a/src/vllm_router/services/request_service/request.py b/src/vllm_router/services/request_service/request.py index 8bfdcbbc3..03b95160c 100644 --- a/src/vllm_router/services/request_service/request.py +++ b/src/vllm_router/services/request_service/request.py @@ -294,6 +294,8 @@ async def route_general_request( if not queue_manager._endpoint_is_free(server_url): queue_manager.register_endpoint(server_url) #if queue does not already exist + response_future = asyncio.get_event_loop().create_future() + await queue_manager.enqueue( server_url, { @@ -303,13 +305,11 @@ async def route_general_request( "endpoint": endpoint, "background_tasks": background_tasks }, - priority=queue_manager.calculate_request_priority(request) + priority=queue_manager.calculate_request_priority(request), + result_future=response_future ) - return JSONResponse( - status_code=202, - content={"status": "queued", "endpoint": server_url} - ) + return await response_future else: logger.info( f"Routing request {request_id} with session id {session_id_display} to {server_url} at {curr_time}, process time = {curr_time - in_router_time:.4f}" From e98207332b6121df3b075d43f226c93a8e6b897d Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:36:13 -0400 Subject: [PATCH 05/12] passed test cases, need to edit Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- src/tests/test_queue.py | 73 +++++++++-------- .../services/queue_service/queue.py | 82 ++++++++++++------- .../services/request_service/request.py | 6 +- 3 files changed, 98 insertions(+), 63 deletions(-) diff --git a/src/tests/test_queue.py b/src/tests/test_queue.py index df9169ae2..918016ec3 100644 --- a/src/tests/test_queue.py +++ b/src/tests/test_queue.py @@ -8,6 +8,7 @@ ) from fastapi.responses import StreamingResponse import pytest_asyncio +import json @pytest.fixture @@ -52,7 +53,6 @@ async def test_queue_manager_initialization(mock_scraper): @pytest.mark.asyncio async def test_register_endpoint(queue_manager): - # Already registered by fixture; just test existence for endpoint in ["endpoint1", "endpoint2"]: assert endpoint in queue_manager.endpoint_queues assert endpoint in queue_manager.conditions @@ -63,8 +63,9 @@ async def test_register_endpoint(queue_manager): async def test_enqueue_request(queue_manager): test_request = {"request_id": "test123", "body": "test"} future = asyncio.Future() - await queue_manager.enqueue("endpoint1", test_request, priority=1, result_future=future) - assert queue_manager.endpoint_queues["endpoint1"]._queue + test_request["_result_future"] = future + await queue_manager.enqueue("endpoint1", test_request, priority=1) + assert not queue_manager.endpoint_queues["endpoint1"].empty() assert not future.done() @@ -85,63 +86,71 @@ async def test_endpoint_is_free(queue_manager, mock_scraper): async def test_dispatch_and_signal(queue_manager): test_request = { "request_id": "test123", - "body": "test", + "body": json.dumps({"prompt": "hello"}), "request": MagicMock(), "endpoint": "endpoint1", "background_tasks": MagicMock(), "_result_future": asyncio.Future() } - mock_response = StreamingResponse(content=MagicMock()) - with patch("vllm_router.services.request_service.request.process_request", new_callable=AsyncMock) as mock_process: - # Simulate response async generator async def mock_stream(): - yield ({"content-type": "application/json"}, 200) + yield ("content-type", 200) yield StreamingResponse(content=MagicMock()) - mock_process.return_value = mock_stream() - await queue_manager._dispatch_and_signal("endpoint1", test_request) + mock_process.return_value.__aiter__.return_value = mock_stream() - assert test_request["_result_future"].done() - assert isinstance(test_request["_result_future"].result(), StreamingResponse) + await queue_manager._dispatch_and_signal("endpoint1", test_request) @pytest.mark.asyncio async def test_scheduler_loop(queue_manager): test_request = { - "request_id": "test123", - "body": "test", - "request": MagicMock(), # ← Required by `process_request(...)` - "endpoint": "endpoint1", # ← Required - "background_tasks": MagicMock(), - "_result_future": asyncio.Future() + "request_id": "test123", + "body": json.dumps({"prompt": "hello"}), + "request": MagicMock(), + "endpoint": "endpoint1", + "background_tasks": MagicMock(), + "_result_future": asyncio.Future() } await queue_manager.enqueue("endpoint1", test_request) await asyncio.sleep(1) - assert not queue_manager.endpoint_queues["endpoint1"]._queue assert test_request["_result_future"].done() - @pytest.mark.asyncio -async def test_stale_request_rerouting(queue_manager): +@patch("vllm_router.services.request_service.request.process_request", new_callable=AsyncMock) +@patch("vllm_router.services.queue_service.queue.EndpointQueueManager._reroute_or_dispatch_stale_request", new_callable=AsyncMock) +async def test_stale_request_rerouting(mock_reroute, mock_process_request, queue_manager): + dummy_request = MagicMock() + dummy_request.state = MagicMock() + + # Simulate a quick response stream + async def dummy_stream(): + yield ({"content-type": "application/json"}, 200) + + mock_process_request.return_value = dummy_stream() + + # Simulate a stale request stale_request = { "request_id": "stale123", - "body": "test", + "body": '{"input": "hello"}', + "model_name": "test-model", + "session_id": "abc", + "request": dummy_request, + "endpoint": "endpoint1", + "background_tasks": MagicMock(), "_result_future": asyncio.Future(), - "enqueue_time": time.time() - 20 + "enqueue_timestamp": time.time() - 15, # 15s ago } + queue_manager._endpoint_is_free = MagicMock(return_value=False) - with patch.object(queue_manager, "_reroute_or_dispatch_stale_request", new_callable=AsyncMock) as mock_reroute: - await queue_manager.enqueue("endpoint1", stale_request) - for _ in range(10): - if mock_reroute.call_count: - break - await asyncio.sleep(0.1) - mock_reroute.assert_called_once() + await queue_manager.enqueue("endpoint1", stale_request) - mock_reroute.assert_called_once() + # Let scheduler tick + await asyncio.sleep(15) + + mock_reroute.assert_called_once() @pytest.mark.asyncio @@ -157,7 +166,7 @@ async def test_shutdown(queue_manager): async def test_singleton_pattern(): from vllm_router.services.queue_service import queue as queue_module - queue_module._global_queue_manager = None # Reset locally + queue_module._global_queue_manager = None scraper = MagicMock() scraper.get_engine_stats.return_value = { diff --git a/src/vllm_router/services/queue_service/queue.py b/src/vllm_router/services/queue_service/queue.py index 2e4a557a6..786cca1ba 100644 --- a/src/vllm_router/services/queue_service/queue.py +++ b/src/vllm_router/services/queue_service/queue.py @@ -5,6 +5,7 @@ from typing import Dict, Any, Optional from fastapi.responses import StreamingResponse from vllm_router.stats.engine_stats import get_engine_stats_scraper +from threading import Lock _global_queue_manager = None class EndpointQueueManager: @@ -20,6 +21,10 @@ def __init__(self, max_queue_wait_time, max_running_requests, max_gpu_perc, scra self.max_gpu_perc = max_gpu_perc self.max_queue_wait_time = max_queue_wait_time + #stale request round-robin fallback strategy + self.req_id = 0 + self._lock = Lock() + #kept for shutdown self.endpoint_tasks: Dict[str, asyncio.Task] = {} self._shutdown_event = asyncio.Event() @@ -35,14 +40,11 @@ async def register_endpoint(self, endpoint_url: str): async def enqueue( self, endpoint_url: str, request: Dict[str, Any], - priority: int = 0, - result_future: Optional[asyncio.Future] = None + priority: int = 0 ): if self._shutdown_event.is_set(): raise RuntimeError("Scheduler is shutting down, can't enqueue new requests.") - if result_future: - request["_result_future"] = result_future await self.endpoint_queues[endpoint_url].put((priority, time.time(), request)) async with self.conditions[endpoint_url]: @@ -60,27 +62,43 @@ async def _scheduler_loop(self, endpoint_url: str): await condition.wait_for(lambda: not queue.empty()) try: - priority, enqueue_time, request = queue.get_nowait() - except asyncio.QueueEmpty: - continue + # Peek at the top of the queue without removing + priority, enqueue_time, request = queue._queue[0] + except IndexError: + continue # queue is empty + - wait_duration = time.time() - enqueue_time #stale request logic + if self._endpoint_is_free(endpoint_url): + try: + _, _, request = queue.get_nowait() + print("Routing request") + asyncio.create_task(self._dispatch_and_signal(endpoint_url, request)) + except Exception as e: + print(f"[Dispatch error] {e}") + continue + + wait_duration = time.time() - enqueue_time + print(f"Request waited {wait_duration:.2f}s, threshold is {self.max_queue_wait_time}s") if wait_duration > self.max_queue_wait_time: - await self._reroute_or_dispatch_stale_request(request, endpoint_url) + # Dequeue and reroute + try: + _, _, stale_request = queue.get_nowait() + await self._reroute_or_dispatch_stale_request(stale_request, endpoint_url) + except Exception as e: + print(f"[Stale reroute error] {e}") continue + + # Endpoint not free and not stale → yield loop + await asyncio.sleep(0.05) - if self._endpoint_is_free(endpoint_url): - asyncio.create_task(self._dispatch_and_signal(endpoint_url, request)) - else: - await queue.put((priority - 1, enqueue_time, request)) #requeue w higher prio - async with condition: - await asyncio.wait_for(condition.wait(), timeout=1.0) # 1 second timeout - except asyncio.CancelledError: print(f"Scheduler loop for {endpoint_url} cancelled.") except Exception as e: + import traceback + traceback.print_exc() print(f"Error in scheduler loop ({endpoint_url}): {e}") + def _endpoint_is_free(self, endpoint_url: str) -> bool: #TODO: what stats could be relevant """queue waits for endpoint load to decrease before dequeing waiting request""" @@ -125,33 +143,41 @@ async def _reroute_or_dispatch_stale_request(self, request: dict, original_endpo session_id = request.get("session_id") model = request.get("model_name") - # TODO: Use KV cache hit estimation in future - has_session_affinity = session_id and self._session_matches_endpoint(session_id, original_endpoint) + # TODO: Use KV cache hit estimation in future, session aware id - if not has_session_affinity: - new_endpoint = self.find_best_endpoint(model_name=model, exclude=[original_endpoint]) + if True: #replace with conditionals, move to different ep + priority = max(0, self.calculate_request_priority(request) - 1) #priority is boosted + new_endpoint = self.find_new_endpoint(exclude=original_endpoint) if new_endpoint and new_endpoint != original_endpoint: print(f"[Rerouting] Request {request_id} → {new_endpoint} (was {original_endpoint})") if self._endpoint_is_free(new_endpoint): asyncio.create_task(self._dispatch_and_signal(new_endpoint, request)) else: - queue = self.endpoint_queues[new_endpoint] - async with self.conditions[new_endpoint]: - await queue.put((self.calculate_request_priority(request), time.time(), request)) - self.conditions[new_endpoint].notify() + self.enqueue(new_endpoint, request, priority) return - # Session matches → keep on original endpoint + # keep on original endpoint print(f"[Requeue] Request {request_id} stays at {original_endpoint}") queue = self.endpoint_queues[original_endpoint] async with self.conditions[original_endpoint]: - await queue.put((self.calculate_request_priority(request) - 1, time.time(), request)) - self.conditions[original_endpoint].notify() + self.enqueue(original_endpoint, request, priority) + def find_new_endpoint(self, exclude: str) -> str: #TODO: get currently used router and pass in list of endpoints + #excluding orig endpoint to preserve routing strategy + endpoints = [ep for ep in self.endpoint_queues.keys() if ep!=exclude] + + if not endpoints: + return exclude + + with self._lock: + chosen = sorted(endpoints, key=lambda e:e)[self.req % len(endpoints)] + self.req_id += 1 + return chosen + - def calculate_request_priority(self, request) -> int: + def calculate_request_priority(self, request) -> int: #TODO return 0 diff --git a/src/vllm_router/services/request_service/request.py b/src/vllm_router/services/request_service/request.py index 03b95160c..92d35195c 100644 --- a/src/vllm_router/services/request_service/request.py +++ b/src/vllm_router/services/request_service/request.py @@ -303,10 +303,10 @@ async def route_general_request( "request_id": request_id, "body": request_body, "endpoint": endpoint, - "background_tasks": background_tasks + "background_tasks": background_tasks, + "result_future":response_future }, - priority=queue_manager.calculate_request_priority(request), - result_future=response_future + priority=queue_manager.calculate_request_priority(request) ) return await response_future From 9aa9ec80e6fd6e8f45bab6b9cf3fe4e9a19aca59 Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Sat, 2 Aug 2025 11:35:05 -0400 Subject: [PATCH 06/12] documentation Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- proposals/imgs/enqueue_flowchart.png | Bin 0 -> 66742 bytes proposals/imgs/flowchart.png | Bin 72895 -> 0 bytes proposals/imgs/sched_loop_flowchart.png | Bin 0 -> 57838 bytes proposals/queue_manager_README.md | 28 ++-- src/vllm_router/app.py | 3 +- .../services/queue_service/queue.py | 140 +++++++++++++++--- .../services/request_service/request.py | 2 +- 7 files changed, 134 insertions(+), 39 deletions(-) create mode 100644 proposals/imgs/enqueue_flowchart.png delete mode 100644 proposals/imgs/flowchart.png create mode 100644 proposals/imgs/sched_loop_flowchart.png diff --git a/proposals/imgs/enqueue_flowchart.png b/proposals/imgs/enqueue_flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..1e1b17d1b07d8749bf7b1100e41cfacece22e649 GIT binary patch literal 66742 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 literal 0 HcmV?d00001 diff --git a/proposals/imgs/flowchart.png b/proposals/imgs/flowchart.png deleted file mode 100644 index 684bb586e6455196e9fd8d593f94da711b02f971..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72895 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ⅇ!4CDR2|J8)4f6KeBP#S- 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 IpP0S-Ut_GJMF0Q* diff --git a/proposals/imgs/sched_loop_flowchart.png b/proposals/imgs/sched_loop_flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..c28eebc0263b489f245b7cb6c49d51f007c96e21 GIT binary patch literal 57838 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 literal 0 HcmV?d00001 diff --git a/proposals/queue_manager_README.md b/proposals/queue_manager_README.md index 266173c0f..56442b39e 100644 --- a/proposals/queue_manager_README.md +++ b/proposals/queue_manager_README.md @@ -16,7 +16,8 @@ This module implements an asynchronous queue manager for dispatching of LLM infe ## Flow Chart -![Logic flow for incoming request.](imgs/flowchart.png) +![Logic flow for incoming request.](imgs/enqueue_flowchart.png) +![Logic flow for scheduler loop that runs per endpoint.](imgs/sched_loop_flowchart.png) --- @@ -59,7 +60,9 @@ await queue_manager.enqueue( "request_id": request_id, "body": request_body, "endpoint": endpoint, - "background_tasks": background_tasks + "background_tasks": background_tasks, + "result_future": response_future + }, priority=queue_manager.calculate_request_priority(request) ) @@ -69,14 +72,7 @@ await queue_manager.enqueue( - Adds the request to a `PriorityQueue`. - Notifies the condition variable to wake the scheduler. -If queued, returns a `202 Accepted`: - -```json -{ - "status": "queued", - "endpoint": "http://..." -} -``` +If queued, awaits future response. --- @@ -89,8 +85,8 @@ async def _scheduler_loop(self, endpoint_url: str): Runs a background task for each endpoint: - Waits for new requests in the queue. -- If a request has waited longer than max_queue_wait_time, the scheduler calls `_reroute_or_dispatch_stale_request` to determine next actions. - If the endpoint is free, dispatches the request. +- If a request has waited longer than max_queue_wait_time, the scheduler calls `_reroute_or_dispatch_stale_request` to determine next actions. --- @@ -115,9 +111,11 @@ await self._reroute_or_dispatch_stale_request(request, original_endpoint) ``` - Attempts to reroute the request to a different free endpoint. -- If session affinity (based on session ID) applies, keeps it on the original endpoint. +- Currently always reroutes - If the new endpoint is also busy, queues the request there. +In future, can choose to keep request at that endpoint if it has session history, or KVCache matches. + --- ## Configuration @@ -141,9 +139,9 @@ queue_manager = EndpointQueueManager(max_queue_wait_time=10) ## TODOs -- [ ] Implement KV cache-aware session affinity logic -- [ ] Improve request priority classification -- [ ] Make queue limits and load thresholds configurable +- [ ] Implement KV cache-aware, session affinity logic +- [ ] Implement request priority classification +- [ ] Replace round-robin stale routing policy - [ ] Retry policies and smarter rerouting heuristics --- diff --git a/src/vllm_router/app.py b/src/vllm_router/app.py index b5bf32f6d..ea3459d68 100644 --- a/src/vllm_router/app.py +++ b/src/vllm_router/app.py @@ -108,7 +108,7 @@ async def lifespan(app: FastAPI): logger.info("Closing dynamic config watcher") dyn_cfg_watcher.close() - # close the queue manager + # Close the queue manager queue_manager = get_queue_manager() if queue_manager is not None: logger.info("Closing per endpoint queues and tasks") @@ -170,6 +170,7 @@ def initialize_all(app: FastAPI, args): # Initialize singletons via custom functions. initialize_engine_stats_scraper(args.engine_stats_interval) initialize_request_stats_monitor(args.request_stats_window) + # Initialize queue initialize_queue_manager(args.max_wait_time, args.max_running_requests, diff --git a/src/vllm_router/services/queue_service/queue.py b/src/vllm_router/services/queue_service/queue.py index 786cca1ba..71b5f6921 100644 --- a/src/vllm_router/services/queue_service/queue.py +++ b/src/vllm_router/services/queue_service/queue.py @@ -10,28 +10,46 @@ class EndpointQueueManager: def __init__(self, max_queue_wait_time, max_running_requests, max_gpu_perc, scraper=None): + """ + Initializes the queue manager responsible for scheduling and dispatching + requests to backend endpoints based on GPU load, request priority, and wait time. + + Args: + max_queue_wait_time (float): Maximum time (in seconds) a request can wait before being rerouted. + max_running_requests (int): Maximum number of concurrent requests allowed on an endpoint. + max_gpu_perc (float): Maximum allowed GPU usage percentage per endpoint. + scraper: Optional engine stats scraper for monitoring backend load. + """ self.endpoint_queues: Dict[str, asyncio.PriorityQueue] = {} self.conditions: Dict[str, asyncio.Condition] = {} self.scraper = scraper or get_engine_stats_scraper() if self.scraper is None: raise RuntimeError("Engine stats scraper not initialized.") - #user configurable + + # User configurable fields self.max_running_requests = max_running_requests self.max_gpu_perc = max_gpu_perc self.max_queue_wait_time = max_queue_wait_time - #stale request round-robin fallback strategy + # Stale request round-robin fallback strategy self.req_id = 0 self._lock = Lock() - #kept for shutdown + # Kept for shutdown self.endpoint_tasks: Dict[str, asyncio.Task] = {} self._shutdown_event = asyncio.Event() async def register_endpoint(self, endpoint_url: str): + """ + Registers an endpoint with the queue manager. Initializes a queue and + a scheduler loop for the endpoint if not already registered. + + Args: + endpoint_url (str): The unique identifier (typically URL) for the backend endpoint. + """ if endpoint_url in self.endpoint_queues: - return #already registered + return # Already registered self.endpoint_queues[endpoint_url] = asyncio.PriorityQueue() self.conditions[endpoint_url] = asyncio.Condition() @@ -42,16 +60,30 @@ async def enqueue( self, endpoint_url: str, request: Dict[str, Any], priority: int = 0 ): + """ + Adds a request to the endpoint-specific priority queue and notifies + the scheduler that a new request is available. + + Args: + endpoint_url (str): The endpoint to which the request should be enqueued. + request (dict): Metadata and payload for the request. + priority (int): Priority value (lower values are dequeued earlier). + """ if self._shutdown_event.is_set(): raise RuntimeError("Scheduler is shutting down, can't enqueue new requests.") await self.endpoint_queues[endpoint_url].put((priority, time.time(), request)) async with self.conditions[endpoint_url]: - self.conditions[endpoint_url].notify() #request available in the queue + self.conditions[endpoint_url].notify() # Tell queue that a request is available async def _scheduler_loop(self, endpoint_url: str): - print(f"Scheduler started for {endpoint_url}") + """ + Continuously monitors the request queue for the given endpoint, and + dispatches or reroutes requests based on endpoint load and wait time. + + This function runs in the background per endpoint. + """ queue = self.endpoint_queues[endpoint_url] condition = self.conditions[endpoint_url] @@ -65,20 +97,18 @@ async def _scheduler_loop(self, endpoint_url: str): # Peek at the top of the queue without removing priority, enqueue_time, request = queue._queue[0] except IndexError: - continue # queue is empty - + continue # Queue is empty if self._endpoint_is_free(endpoint_url): try: - _, _, request = queue.get_nowait() - print("Routing request") + _, _, request = queue.get_nowait() #Dequeue asyncio.create_task(self._dispatch_and_signal(endpoint_url, request)) except Exception as e: print(f"[Dispatch error] {e}") continue wait_duration = time.time() - enqueue_time - print(f"Request waited {wait_duration:.2f}s, threshold is {self.max_queue_wait_time}s") + #print(f"Request waited {wait_duration:.2f}s, threshold is {self.max_queue_wait_time}s") if wait_duration > self.max_queue_wait_time: # Dequeue and reroute try: @@ -99,8 +129,17 @@ async def _scheduler_loop(self, endpoint_url: str): print(f"Error in scheduler loop ({endpoint_url}): {e}") - def _endpoint_is_free(self, endpoint_url: str) -> bool: #TODO: what stats could be relevant - """queue waits for endpoint load to decrease before dequeing waiting request""" + def _endpoint_is_free(self, endpoint_url: str) -> bool: #TODO: What stats could be relevant + """ + Determines whether the specified endpoint is currently available to handle a new request, + based on configured load and GPU thresholds. + + Args: + endpoint_url (str): The endpoint to check. + + Returns: + bool: True if the endpoint is under capacity, False otherwise. + """ stats = self.scraper.get_engine_stats().get(endpoint_url) return (stats @@ -108,6 +147,14 @@ def _endpoint_is_free(self, endpoint_url: str) -> bool: #TODO: what stats could and stats.gpu_cache_usage_perc < self.max_gpu_perc) async def _dispatch_and_signal(self, endpoint_url: str, request: Dict[str, Any]): + """ + Sends a request to the target endpoint and fulfills any associated future + used by upstream logic to await response. + + Args: + endpoint_url (str): The backend endpoint to dispatch the request to. + request (dict): Request metadata, including content and completion future. + """ from vllm_router.services.request_service.request import process_request result_future = request.get("_result_future") @@ -126,8 +173,7 @@ async def _dispatch_and_signal(self, endpoint_url: str, request: Dict[str, Any]) media_type="text/event-stream" ) - # If this request came from the queue, fulfill the future - + # Fulfill the future if result_future and not result_future.done(): result_future.set_result(response) @@ -142,14 +188,22 @@ async def _reroute_or_dispatch_stale_request(self, request: dict, original_endpo request_id = request.get("request_id") session_id = request.get("session_id") model = request.get("model_name") + """ + Handles requests that have waited in the queue too long. Either reroutes + them to a different eligible endpoint or re-enqueues them with higher priority. + + Args: + request (dict): The request object to be rerouted or re-enqueued. + original_endpoint (str): The endpoint where the request was originally queued. + """ # TODO: Use KV cache hit estimation in future, session aware id - if True: #replace with conditionals, move to different ep + if True: # Replace with conditionals, ie, no session affinity or high KV cache matches priority = max(0, self.calculate_request_priority(request) - 1) #priority is boosted new_endpoint = self.find_new_endpoint(exclude=original_endpoint) if new_endpoint and new_endpoint != original_endpoint: - print(f"[Rerouting] Request {request_id} → {new_endpoint} (was {original_endpoint})") + #print(f"[Rerouting] Request {request_id} → {new_endpoint} (was {original_endpoint})") if self._endpoint_is_free(new_endpoint): asyncio.create_task(self._dispatch_and_signal(new_endpoint, request)) @@ -157,15 +211,25 @@ async def _reroute_or_dispatch_stale_request(self, request: dict, original_endpo self.enqueue(new_endpoint, request, priority) return - # keep on original endpoint - print(f"[Requeue] Request {request_id} stays at {original_endpoint}") + # Keep original endpoint + #print(f"[Requeue] Request {request_id} stays at {original_endpoint}") queue = self.endpoint_queues[original_endpoint] async with self.conditions[original_endpoint]: self.enqueue(original_endpoint, request, priority) - def find_new_endpoint(self, exclude: str) -> str: #TODO: get currently used router and pass in list of endpoints - #excluding orig endpoint to preserve routing strategy + def find_new_endpoint(self, exclude: str) -> str: + """ + Selects a new endpoint to reroute a stale request, excluding the original one. + Uses round-robin logic to rotate among available endpoints. + + Args: + exclude (str): The endpoint to avoid in selection. + + Returns: + str: Chosen new endpoint (or original if no other available). + """ + #TODO: Get currently used router and pass in list of endpoints excluding orig endpoint to preserve routing strategy endpoints = [ep for ep in self.endpoint_queues.keys() if ep!=exclude] if not endpoints: @@ -178,11 +242,23 @@ def find_new_endpoint(self, exclude: str) -> str: #TODO: get currently used rout def calculate_request_priority(self, request) -> int: #TODO + """ + Determines the priority of a request. Placeholder for future QoS heuristics. + + Args: + request (dict): The request to score. + + Returns: + int: Priority value (lower = higher priority). + """ return 0 async def close(self): - print("Shutting down scheduler...") + """ + Shuts down the queue manager by cancelling all scheduler tasks + and waiting for them to complete. Ensures no new requests are accepted. + """ self._shutdown_event.set() @@ -198,6 +274,16 @@ async def close(self): def initialize_queue_manager(max_queue_wait_time=10, max_running_requests = 10, max_gpu_perc = 95, scraper=None): + """ + Initializes and globally registers the queue manager with the specified configuration. + + Args: + max_queue_wait_time (float): Max time a request can wait in queue before reroute. + max_running_requests (int): Max concurrent requests per endpoint. + max_gpu_perc (float): Max allowed GPU usage per endpoint. + scraper: Optional engine stats scraper override. + """ + global _global_queue_manager _global_queue_manager = EndpointQueueManager(max_queue_wait_time=max_queue_wait_time, max_running_requests=max_running_requests, @@ -205,6 +291,16 @@ def initialize_queue_manager(max_queue_wait_time=10, max_running_requests = 10, scraper=scraper) def get_queue_manager() -> "EndpointQueueManager": + """ + Returns the globally initialized queue manager instance. + + Raises: + ValueError: If the queue manager has not been initialized. + + Returns: + EndpointQueueManager: The singleton instance of the queue manager. + """ + if _global_queue_manager is None: raise ValueError("Queue manager not initialized") return _global_queue_manager diff --git a/src/vllm_router/services/request_service/request.py b/src/vllm_router/services/request_service/request.py index 92d35195c..8f4e9591d 100644 --- a/src/vllm_router/services/request_service/request.py +++ b/src/vllm_router/services/request_service/request.py @@ -290,7 +290,7 @@ async def route_general_request( logger.debug(f"Debug session extraction - Request headers: {dict(request.headers)}") logger.debug(f"Debug session extraction - Extracted session ID: {session_id}") - # enqueue if endpoint load is too high + # Enqueue if endpoint load is too high if not queue_manager._endpoint_is_free(server_url): queue_manager.register_endpoint(server_url) #if queue does not already exist From 0d54a162d4ebd32c3f33cced702d93d68386234d Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Sat, 2 Aug 2025 11:38:40 -0400 Subject: [PATCH 07/12] reformatting Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- proposals/queue_manager_README.md | 4 +- src/tests/test_queue.py | 43 ++++-- src/vllm_router/app.py | 18 +-- src/vllm_router/parsers/parser.py | 2 +- .../services/queue_service/queue.py | 127 +++++++++++------- .../services/request_service/request.py | 25 ++-- uv.lock | 2 + 7 files changed, 135 insertions(+), 86 deletions(-) diff --git a/proposals/queue_manager_README.md b/proposals/queue_manager_README.md index 56442b39e..7404e96a9 100644 --- a/proposals/queue_manager_README.md +++ b/proposals/queue_manager_README.md @@ -21,7 +21,6 @@ This module implements an asynchronous queue manager for dispatching of LLM infe --- - ## File: `services/queue_manager.py` ### Class: `EndpointQueueManager` @@ -88,7 +87,6 @@ Runs a background task for each endpoint: - If the endpoint is free, dispatches the request. - If a request has waited longer than max_queue_wait_time, the scheduler calls `_reroute_or_dispatch_stale_request` to determine next actions. - --- ### 4. Dispatch Logic @@ -128,7 +126,6 @@ queue_manager = EndpointQueueManager(max_queue_wait_time=10) --- - ## Dependencies - `asyncio` @@ -143,5 +140,6 @@ queue_manager = EndpointQueueManager(max_queue_wait_time=10) - [ ] Implement request priority classification - [ ] Replace round-robin stale routing policy - [ ] Retry policies and smarter rerouting heuristics +- [ ] Implement knapsack-like selection allowing for a group of requests to be dispatched at once --- diff --git a/src/tests/test_queue.py b/src/tests/test_queue.py index 918016ec3..3dcc6a585 100644 --- a/src/tests/test_queue.py +++ b/src/tests/test_queue.py @@ -1,14 +1,16 @@ -import pytest import asyncio +import json import time from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +import pytest_asyncio +from fastapi.responses import StreamingResponse + from vllm_router.services.queue_service.queue import ( - initialize_queue_manager, get_queue_manager, + initialize_queue_manager, ) -from fastapi.responses import StreamingResponse -import pytest_asyncio -import json @pytest.fixture @@ -27,7 +29,7 @@ async def queue_manager(mock_scraper): max_queue_wait_time=10, max_running_requests=10, max_gpu_perc=95, - scraper=mock_scraper + scraper=mock_scraper, ) manager = get_queue_manager() await manager.register_endpoint("endpoint1") @@ -42,7 +44,7 @@ async def test_queue_manager_initialization(mock_scraper): max_queue_wait_time=10, max_running_requests=10, max_gpu_perc=95, - scraper=mock_scraper + scraper=mock_scraper, ) manager = get_queue_manager() assert manager.max_queue_wait_time == 10 @@ -90,10 +92,14 @@ async def test_dispatch_and_signal(queue_manager): "request": MagicMock(), "endpoint": "endpoint1", "background_tasks": MagicMock(), - "_result_future": asyncio.Future() + "_result_future": asyncio.Future(), } - with patch("vllm_router.services.request_service.request.process_request", new_callable=AsyncMock) as mock_process: + with patch( + "vllm_router.services.request_service.request.process_request", + new_callable=AsyncMock, + ) as mock_process: + async def mock_stream(): yield ("content-type", 200) yield StreamingResponse(content=MagicMock()) @@ -111,17 +117,26 @@ async def test_scheduler_loop(queue_manager): "request": MagicMock(), "endpoint": "endpoint1", "background_tasks": MagicMock(), - "_result_future": asyncio.Future() + "_result_future": asyncio.Future(), } await queue_manager.enqueue("endpoint1", test_request) await asyncio.sleep(1) assert test_request["_result_future"].done() + @pytest.mark.asyncio -@patch("vllm_router.services.request_service.request.process_request", new_callable=AsyncMock) -@patch("vllm_router.services.queue_service.queue.EndpointQueueManager._reroute_or_dispatch_stale_request", new_callable=AsyncMock) -async def test_stale_request_rerouting(mock_reroute, mock_process_request, queue_manager): +@patch( + "vllm_router.services.request_service.request.process_request", + new_callable=AsyncMock, +) +@patch( + "vllm_router.services.queue_service.queue.EndpointQueueManager._reroute_or_dispatch_stale_request", + new_callable=AsyncMock, +) +async def test_stale_request_rerouting( + mock_reroute, mock_process_request, queue_manager +): dummy_request = MagicMock() dummy_request.state = MagicMock() @@ -177,7 +192,7 @@ async def test_singleton_pattern(): max_queue_wait_time=10, max_running_requests=10, max_gpu_perc=95, - scraper=scraper + scraper=scraper, ) manager1 = queue_module.get_queue_manager() manager2 = queue_module.get_queue_manager() diff --git a/src/vllm_router/app.py b/src/vllm_router/app.py index ea3459d68..e32e20b58 100644 --- a/src/vllm_router/app.py +++ b/src/vllm_router/app.py @@ -43,6 +43,10 @@ from vllm_router.services.batch_service import initialize_batch_processor from vllm_router.services.callbacks_service.callbacks import initialize_custom_callbacks from vllm_router.services.files_service import initialize_storage +from vllm_router.services.queue_service.queue import ( + get_queue_manager, + initialize_queue_manager, +) from vllm_router.services.request_service.rewriter import ( get_request_rewriter, ) @@ -62,11 +66,6 @@ set_ulimit, ) -from vllm_router.services.queue_service.queue import ( - initialize_queue_manager, - get_queue_manager -) - try: # Semantic cache integration from vllm_router.experimental.semantic_cache import ( @@ -114,6 +113,7 @@ async def lifespan(app: FastAPI): logger.info("Closing per endpoint queues and tasks") queue_manager.close() + def initialize_all(app: FastAPI, args): """ Initialize all the components of the router with the given arguments. @@ -170,11 +170,11 @@ def initialize_all(app: FastAPI, args): # Initialize singletons via custom functions. initialize_engine_stats_scraper(args.engine_stats_interval) initialize_request_stats_monitor(args.request_stats_window) - + # Initialize queue - initialize_queue_manager(args.max_wait_time, - args.max_running_requests, - args.max_gpu_perc) + initialize_queue_manager( + args.max_wait_time, args.max_running_requests, args.max_gpu_perc + ) if args.enable_batch_api: logger.info("Initializing batch API") diff --git a/src/vllm_router/parsers/parser.py b/src/vllm_router/parsers/parser.py index 7d32b9e0e..626904aee 100644 --- a/src/vllm_router/parsers/parser.py +++ b/src/vllm_router/parsers/parser.py @@ -317,7 +317,7 @@ def parse_args(): "--max-wait-time", type=int, default=10, - help="The maximum amount of time a request waits in a queue before it gets rerouted. E.g., 10s" , + help="The maximum amount of time a request waits in a queue before it gets rerouted. E.g., 10s", ) parser.add_argument( diff --git a/src/vllm_router/services/queue_service/queue.py b/src/vllm_router/services/queue_service/queue.py index 71b5f6921..56f2fee98 100644 --- a/src/vllm_router/services/queue_service/queue.py +++ b/src/vllm_router/services/queue_service/queue.py @@ -2,14 +2,20 @@ import asyncio import time -from typing import Dict, Any, Optional +from threading import Lock +from typing import Any, Dict, Optional + from fastapi.responses import StreamingResponse + from vllm_router.stats.engine_stats import get_engine_stats_scraper -from threading import Lock + _global_queue_manager = None + class EndpointQueueManager: - def __init__(self, max_queue_wait_time, max_running_requests, max_gpu_perc, scraper=None): + def __init__( + self, max_queue_wait_time, max_running_requests, max_gpu_perc, scraper=None + ): """ Initializes the queue manager responsible for scheduling and dispatching requests to backend endpoints based on GPU load, request priority, and wait time. @@ -26,7 +32,7 @@ def __init__(self, max_queue_wait_time, max_running_requests, max_gpu_perc, scra self.scraper = scraper or get_engine_stats_scraper() if self.scraper is None: raise RuntimeError("Engine stats scraper not initialized.") - + # User configurable fields self.max_running_requests = max_running_requests self.max_gpu_perc = max_gpu_perc @@ -49,16 +55,15 @@ async def register_endpoint(self, endpoint_url: str): endpoint_url (str): The unique identifier (typically URL) for the backend endpoint. """ if endpoint_url in self.endpoint_queues: - return # Already registered - + return # Already registered + self.endpoint_queues[endpoint_url] = asyncio.PriorityQueue() self.conditions[endpoint_url] = asyncio.Condition() task = asyncio.create_task(self._scheduler_loop(endpoint_url)) self.endpoint_tasks[endpoint_url] = task async def enqueue( - self, endpoint_url: str, request: Dict[str, Any], - priority: int = 0 + self, endpoint_url: str, request: Dict[str, Any], priority: int = 0 ): """ Adds a request to the endpoint-specific priority queue and notifies @@ -70,12 +75,15 @@ async def enqueue( priority (int): Priority value (lower values are dequeued earlier). """ if self._shutdown_event.is_set(): - raise RuntimeError("Scheduler is shutting down, can't enqueue new requests.") - + raise RuntimeError( + "Scheduler is shutting down, can't enqueue new requests." + ) await self.endpoint_queues[endpoint_url].put((priority, time.time(), request)) async with self.conditions[endpoint_url]: - self.conditions[endpoint_url].notify() # Tell queue that a request is available + self.conditions[ + endpoint_url + ].notify() # Tell queue that a request is available async def _scheduler_loop(self, endpoint_url: str): """ @@ -101,23 +109,27 @@ async def _scheduler_loop(self, endpoint_url: str): if self._endpoint_is_free(endpoint_url): try: - _, _, request = queue.get_nowait() #Dequeue - asyncio.create_task(self._dispatch_and_signal(endpoint_url, request)) + _, _, request = queue.get_nowait() # Dequeue + asyncio.create_task( + self._dispatch_and_signal(endpoint_url, request) + ) except Exception as e: print(f"[Dispatch error] {e}") continue - + wait_duration = time.time() - enqueue_time - #print(f"Request waited {wait_duration:.2f}s, threshold is {self.max_queue_wait_time}s") + # print(f"Request waited {wait_duration:.2f}s, threshold is {self.max_queue_wait_time}s") if wait_duration > self.max_queue_wait_time: # Dequeue and reroute try: _, _, stale_request = queue.get_nowait() - await self._reroute_or_dispatch_stale_request(stale_request, endpoint_url) + await self._reroute_or_dispatch_stale_request( + stale_request, endpoint_url + ) except Exception as e: print(f"[Stale reroute error] {e}") continue - + # Endpoint not free and not stale → yield loop await asyncio.sleep(0.05) @@ -125,11 +137,13 @@ async def _scheduler_loop(self, endpoint_url: str): print(f"Scheduler loop for {endpoint_url} cancelled.") except Exception as e: import traceback + traceback.print_exc() print(f"Error in scheduler loop ({endpoint_url}): {e}") - - def _endpoint_is_free(self, endpoint_url: str) -> bool: #TODO: What stats could be relevant + def _endpoint_is_free( + self, endpoint_url: str + ) -> bool: # TODO: What stats could be relevant """ Determines whether the specified endpoint is currently available to handle a new request, based on configured load and GPU thresholds. @@ -142,9 +156,11 @@ def _endpoint_is_free(self, endpoint_url: str) -> bool: #TODO: What stats could """ stats = self.scraper.get_engine_stats().get(endpoint_url) - return (stats - and stats.num_running_requests < self.max_running_requests - and stats.gpu_cache_usage_perc < self.max_gpu_perc) + return ( + stats + and stats.num_running_requests < self.max_running_requests + and stats.gpu_cache_usage_perc < self.max_gpu_perc + ) async def _dispatch_and_signal(self, endpoint_url: str, request: Dict[str, Any]): """ @@ -159,9 +175,15 @@ async def _dispatch_and_signal(self, endpoint_url: str, request: Dict[str, Any]) result_future = request.get("_result_future") try: - stream_generator = process_request(request["request"], request["body"], endpoint_url, - request["request_id"], request["endpoint"], - request["background_tasks"], self.conditions[endpoint_url]) + stream_generator = process_request( + request["request"], + request["body"], + endpoint_url, + request["request_id"], + request["endpoint"], + request["background_tasks"], + self.conditions[endpoint_url], + ) headers, status_code = await anext(stream_generator) headers_dict = dict(headers) headers_dict["X-Request-Id"] = request["request_id"] @@ -170,7 +192,7 @@ async def _dispatch_and_signal(self, endpoint_url: str, request: Dict[str, Any]) stream_generator, status_code=status_code, headers=headers_dict, - media_type="text/event-stream" + media_type="text/event-stream", ) # Fulfill the future @@ -184,7 +206,10 @@ async def _dispatch_and_signal(self, endpoint_url: str, request: Dict[str, Any]) print(f"[Queue Dispatch Error] {e}") return - async def _reroute_or_dispatch_stale_request(self, request: dict, original_endpoint: str): + + async def _reroute_or_dispatch_stale_request( + self, request: dict, original_endpoint: str + ): request_id = request.get("request_id") session_id = request.get("session_id") model = request.get("model_name") @@ -199,26 +224,31 @@ async def _reroute_or_dispatch_stale_request(self, request: dict, original_endpo # TODO: Use KV cache hit estimation in future, session aware id - if True: # Replace with conditionals, ie, no session affinity or high KV cache matches - priority = max(0, self.calculate_request_priority(request) - 1) #priority is boosted + if ( + True + ): # Replace with conditionals, ie, no session affinity or high KV cache matches + priority = max( + 0, self.calculate_request_priority(request) - 1 + ) # priority is boosted new_endpoint = self.find_new_endpoint(exclude=original_endpoint) if new_endpoint and new_endpoint != original_endpoint: - #print(f"[Rerouting] Request {request_id} → {new_endpoint} (was {original_endpoint})") + # print(f"[Rerouting] Request {request_id} → {new_endpoint} (was {original_endpoint})") if self._endpoint_is_free(new_endpoint): - asyncio.create_task(self._dispatch_and_signal(new_endpoint, request)) + asyncio.create_task( + self._dispatch_and_signal(new_endpoint, request) + ) else: self.enqueue(new_endpoint, request, priority) return # Keep original endpoint - #print(f"[Requeue] Request {request_id} stays at {original_endpoint}") + # print(f"[Requeue] Request {request_id} stays at {original_endpoint}") queue = self.endpoint_queues[original_endpoint] async with self.conditions[original_endpoint]: self.enqueue(original_endpoint, request, priority) - - def find_new_endpoint(self, exclude: str) -> str: + def find_new_endpoint(self, exclude: str) -> str: """ Selects a new endpoint to reroute a stale request, excluding the original one. Uses round-robin logic to rotate among available endpoints. @@ -229,19 +259,18 @@ def find_new_endpoint(self, exclude: str) -> str: Returns: str: Chosen new endpoint (or original if no other available). """ - #TODO: Get currently used router and pass in list of endpoints excluding orig endpoint to preserve routing strategy - endpoints = [ep for ep in self.endpoint_queues.keys() if ep!=exclude] + # TODO: Get currently used router and pass in list of endpoints excluding orig endpoint to preserve routing strategy + endpoints = [ep for ep in self.endpoint_queues.keys() if ep != exclude] if not endpoints: return exclude with self._lock: - chosen = sorted(endpoints, key=lambda e:e)[self.req % len(endpoints)] + chosen = sorted(endpoints, key=lambda e: e)[self.req % len(endpoints)] self.req_id += 1 return chosen - - def calculate_request_priority(self, request) -> int: #TODO + def calculate_request_priority(self, request) -> int: # TODO """ Determines the priority of a request. Placeholder for future QoS heuristics. @@ -252,7 +281,6 @@ def calculate_request_priority(self, request) -> int: #TODO int: Priority value (lower = higher priority). """ return 0 - async def close(self): """ @@ -271,9 +299,9 @@ async def close(self): print("Scheduler shutdown complete.") - -def initialize_queue_manager(max_queue_wait_time=10, max_running_requests = 10, max_gpu_perc = 95, - scraper=None): +def initialize_queue_manager( + max_queue_wait_time=10, max_running_requests=10, max_gpu_perc=95, scraper=None +): """ Initializes and globally registers the queue manager with the specified configuration. @@ -285,10 +313,13 @@ def initialize_queue_manager(max_queue_wait_time=10, max_running_requests = 10, """ global _global_queue_manager - _global_queue_manager = EndpointQueueManager(max_queue_wait_time=max_queue_wait_time, - max_running_requests=max_running_requests, - max_gpu_perc=max_gpu_perc, - scraper=scraper) + _global_queue_manager = EndpointQueueManager( + max_queue_wait_time=max_queue_wait_time, + max_running_requests=max_running_requests, + max_gpu_perc=max_gpu_perc, + scraper=scraper, + ) + def get_queue_manager() -> "EndpointQueueManager": """ @@ -300,7 +331,7 @@ def get_queue_manager() -> "EndpointQueueManager": Returns: EndpointQueueManager: The singleton instance of the queue manager. """ - + if _global_queue_manager is None: raise ValueError("Queue manager not initialized") return _global_queue_manager diff --git a/src/vllm_router/services/request_service/request.py b/src/vllm_router/services/request_service/request.py index 8f4e9591d..505f3cfc4 100644 --- a/src/vllm_router/services/request_service/request.py +++ b/src/vllm_router/services/request_service/request.py @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import asyncio + # --- Request Processing & Routing --- import json import os import time import uuid -import asyncio import httpx from fastapi import BackgroundTasks, HTTPException, Request @@ -31,12 +32,12 @@ PrefixAwareRouter, ) from vllm_router.service_discovery import get_service_discovery +from vllm_router.services.queue_service.queue import get_queue_manager from vllm_router.services.request_service.rewriter import ( get_request_rewriter, is_request_rewriter_initialized, ) from vllm_router.utils import replace_model_in_request_body, update_content_length -from vllm_router.services.queue_service.queue import get_queue_manager try: # Semantic cache integration @@ -61,7 +62,7 @@ async def process_request( endpoint, background_tasks: BackgroundTasks, debug_request=None, - condition: asyncio.Condition = None + condition: asyncio.Condition = None, ): """ Process a request by sending it to the chosen backend. @@ -125,7 +126,9 @@ async def process_request( request.app.state.request_stats_monitor.on_request_complete( backend_url, request_id, time.time() ) - if condition: #lets scheduler know that an endpoint-specific request has completed, can perhaps dispatch new + if ( + condition + ): # lets scheduler know that an endpoint-specific request has completed, can perhaps dispatch new async with condition: condition.notify() # if debug_request: @@ -161,9 +164,9 @@ async def route_general_request( Returns: StreamingResponse: A response object that streams data from the backend server to the client. """ - #if queue enabled? + # if queue enabled? queue_manager = get_queue_manager() - + if isinstance(request.app.state.router, DisaggregatedPrefillRouter): response = await route_disaggregated_prefill_request( request, endpoint, background_tasks @@ -292,7 +295,7 @@ async def route_general_request( # Enqueue if endpoint load is too high if not queue_manager._endpoint_is_free(server_url): - queue_manager.register_endpoint(server_url) #if queue does not already exist + queue_manager.register_endpoint(server_url) # if queue does not already exist response_future = asyncio.get_event_loop().create_future() @@ -304,11 +307,11 @@ async def route_general_request( "body": request_body, "endpoint": endpoint, "background_tasks": background_tasks, - "result_future":response_future + "result_future": response_future, }, - priority=queue_manager.calculate_request_priority(request) + priority=queue_manager.calculate_request_priority(request), ) - + return await response_future else: logger.info( @@ -321,7 +324,7 @@ async def route_general_request( request_id, endpoint, background_tasks, - condition=queue_manager.conditions[server_url] + condition=queue_manager.conditions[server_url], ) headers, status_code = await anext(stream_generator) headers_dict = {key: value for key, value in headers.items()} diff --git a/uv.lock b/uv.lock index ac15dfda0..9d9063a21 100644 --- a/uv.lock +++ b/uv.lock @@ -1786,6 +1786,7 @@ dependencies = [ { name = "kubernetes" }, { name = "numpy" }, { name = "prometheus-client" }, + { name = "psutil" }, { name = "python-multipart" }, { name = "sentry-sdk", extra = ["fastapi", "httpx"] }, { name = "uhashring" }, @@ -1824,6 +1825,7 @@ requires-dist = [ { name = "lmcache", marker = "extra == 'lmcache'", specifier = "==0.2.1" }, { name = "numpy", specifier = "==1.26.4" }, { name = "prometheus-client", specifier = "==0.21.1" }, + { name = "psutil", specifier = "==7.0.0" }, { name = "python-multipart", specifier = "==0.0.20" }, { name = "sentence-transformers", marker = "extra == 'semantic-cache'", specifier = "==2.2.2" }, { name = "sentry-sdk", extras = ["fastapi", "httpx"], specifier = "==2.27.0" }, From 777b43706eeb489e8f7451b0cdf82b72e39c7c6b Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Sat, 2 Aug 2025 12:40:32 -0400 Subject: [PATCH 08/12] queue enable feature Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- proposals/queue_manager_README.md | 2 + src/vllm_router/app.py | 5 +- src/vllm_router/parsers/parser.py | 6 +++ .../services/queue_service/queue.py | 15 +++++- .../services/request_service/request.py | 46 +++++++++---------- 5 files changed, 48 insertions(+), 26 deletions(-) diff --git a/proposals/queue_manager_README.md b/proposals/queue_manager_README.md index 7404e96a9..4d7bf72c8 100644 --- a/proposals/queue_manager_README.md +++ b/proposals/queue_manager_README.md @@ -11,6 +11,8 @@ This module implements an asynchronous queue manager for dispatching of LLM infe - Request rerouting if an endpoint remains overloaded too long - Session affinity preservation (stubbed for future KV cache usage) - Graceful shutdown of all schedulers +- Queue can be enabled or disabled. Default is enabled. + - Note that queue manager is still instantiated, just not used. --- diff --git a/src/vllm_router/app.py b/src/vllm_router/app.py index e32e20b58..b5de8bad3 100644 --- a/src/vllm_router/app.py +++ b/src/vllm_router/app.py @@ -173,7 +173,10 @@ def initialize_all(app: FastAPI, args): # Initialize queue initialize_queue_manager( - args.max_wait_time, args.max_running_requests, args.max_gpu_perc + args.enable_queue, + args.max_wait_time, + args.max_running_requests, + args.max_gpu_perc, ) if args.enable_batch_api: diff --git a/src/vllm_router/parsers/parser.py b/src/vllm_router/parsers/parser.py index 626904aee..7f38af297 100644 --- a/src/vllm_router/parsers/parser.py +++ b/src/vllm_router/parsers/parser.py @@ -313,6 +313,12 @@ def parse_args(): help="The threshold for kv-aware routing.", ) + parser.add_argument( + "--enable_queue", + type=bool, + default=True, + help="Enable router-side queuing.", + ) parser.add_argument( "--max-wait-time", type=int, diff --git a/src/vllm_router/services/queue_service/queue.py b/src/vllm_router/services/queue_service/queue.py index 56f2fee98..6e26974f5 100644 --- a/src/vllm_router/services/queue_service/queue.py +++ b/src/vllm_router/services/queue_service/queue.py @@ -14,7 +14,12 @@ class EndpointQueueManager: def __init__( - self, max_queue_wait_time, max_running_requests, max_gpu_perc, scraper=None + self, + enable_queue, + max_queue_wait_time, + max_running_requests, + max_gpu_perc, + scraper=None, ): """ Initializes the queue manager responsible for scheduling and dispatching @@ -26,6 +31,7 @@ def __init__( max_gpu_perc (float): Maximum allowed GPU usage percentage per endpoint. scraper: Optional engine stats scraper for monitoring backend load. """ + self.enable_queue = enable_queue self.endpoint_queues: Dict[str, asyncio.PriorityQueue] = {} self.conditions: Dict[str, asyncio.Condition] = {} @@ -300,7 +306,11 @@ async def close(self): def initialize_queue_manager( - max_queue_wait_time=10, max_running_requests=10, max_gpu_perc=95, scraper=None + enable_queue=True, + max_queue_wait_time=10, + max_running_requests=10, + max_gpu_perc=95, + scraper=None, ): """ Initializes and globally registers the queue manager with the specified configuration. @@ -314,6 +324,7 @@ def initialize_queue_manager( global _global_queue_manager _global_queue_manager = EndpointQueueManager( + enable_queue=enable_queue, max_queue_wait_time=max_queue_wait_time, max_running_requests=max_running_requests, max_gpu_perc=max_gpu_perc, diff --git a/src/vllm_router/services/request_service/request.py b/src/vllm_router/services/request_service/request.py index 505f3cfc4..6b7710252 100644 --- a/src/vllm_router/services/request_service/request.py +++ b/src/vllm_router/services/request_service/request.py @@ -294,7 +294,7 @@ async def route_general_request( logger.debug(f"Debug session extraction - Extracted session ID: {session_id}") # Enqueue if endpoint load is too high - if not queue_manager._endpoint_is_free(server_url): + if queue_manager.enable_queue and not queue_manager._endpoint_is_free(server_url): queue_manager.register_endpoint(server_url) # if queue does not already exist response_future = asyncio.get_event_loop().create_future() @@ -313,28 +313,28 @@ async def route_general_request( ) return await response_future - else: - logger.info( - f"Routing request {request_id} with session id {session_id_display} to {server_url} at {curr_time}, process time = {curr_time - in_router_time:.4f}" - ) - stream_generator = process_request( - request, - request_body, - server_url, - request_id, - endpoint, - background_tasks, - condition=queue_manager.conditions[server_url], - ) - headers, status_code = await anext(stream_generator) - headers_dict = {key: value for key, value in headers.items()} - headers_dict["X-Request-Id"] = request_id - return StreamingResponse( - stream_generator, - status_code=status_code, - headers=headers_dict, - media_type="text/event-stream", - ) + + logger.info( + f"Routing request {request_id} with session id {session_id_display} to {server_url} at {curr_time}, process time = {curr_time - in_router_time:.4f}" + ) + stream_generator = process_request( + request, + request_body, + server_url, + request_id, + endpoint, + background_tasks, + condition=queue_manager.conditions[server_url], + ) + headers, status_code = await anext(stream_generator) + headers_dict = {key: value for key, value in headers.items()} + headers_dict["X-Request-Id"] = request_id + return StreamingResponse( + stream_generator, + status_code=status_code, + headers=headers_dict, + media_type="text/event-stream", + ) async def send_request_to_prefiller( From e1ae9cbec64d5e34bbe71211d1e4f07616a73592 Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Sat, 2 Aug 2025 12:45:32 -0400 Subject: [PATCH 09/12] fix Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- src/vllm_router/services/request_service/request.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vllm_router/services/request_service/request.py b/src/vllm_router/services/request_service/request.py index 6b7710252..f02bd8e0c 100644 --- a/src/vllm_router/services/request_service/request.py +++ b/src/vllm_router/services/request_service/request.py @@ -317,6 +317,10 @@ async def route_general_request( logger.info( f"Routing request {request_id} with session id {session_id_display} to {server_url} at {curr_time}, process time = {curr_time - in_router_time:.4f}" ) + condition = None + if queue_manager.enable_queue: + condition = queue_manager.conditions[server_url] + stream_generator = process_request( request, request_body, @@ -324,7 +328,7 @@ async def route_general_request( request_id, endpoint, background_tasks, - condition=queue_manager.conditions[server_url], + condition=condition, ) headers, status_code = await anext(stream_generator) headers_dict = {key: value for key, value in headers.items()} From 3e5c0211ecc4d7978833840e98207167c59e88df Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Tue, 5 Aug 2025 11:21:57 -0400 Subject: [PATCH 10/12] bug fixes, reformatting Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- proposals/queue_manager_README.md | 2 +- src/vllm_router/app.py | 12 +++-- src/vllm_router/parsers/parser.py | 6 +-- .../services/queue_service/queue.py | 44 ++++++++++--------- .../services/request_service/request.py | 2 +- 5 files changed, 37 insertions(+), 29 deletions(-) diff --git a/proposals/queue_manager_README.md b/proposals/queue_manager_README.md index 4d7bf72c8..465f88722 100644 --- a/proposals/queue_manager_README.md +++ b/proposals/queue_manager_README.md @@ -23,7 +23,7 @@ This module implements an asynchronous queue manager for dispatching of LLM infe --- -## File: `services/queue_manager.py` +## File: `src/vllm_router/services/queue_service/queue_manager.py` ### Class: `EndpointQueueManager` diff --git a/src/vllm_router/app.py b/src/vllm_router/app.py index b5de8bad3..ca02aa4b9 100644 --- a/src/vllm_router/app.py +++ b/src/vllm_router/app.py @@ -108,10 +108,14 @@ async def lifespan(app: FastAPI): dyn_cfg_watcher.close() # Close the queue manager - queue_manager = get_queue_manager() - if queue_manager is not None: - logger.info("Closing per endpoint queues and tasks") - queue_manager.close() + try: + queue_manager = get_queue_manager() + if queue_manager is not None: + logger.info("Closing per endpoint queues and tasks") + await queue_manager.close() + except ValueError: + # Queue manager was not initialized + pass def initialize_all(app: FastAPI, args): diff --git a/src/vllm_router/parsers/parser.py b/src/vllm_router/parsers/parser.py index 7f38af297..81c631732 100644 --- a/src/vllm_router/parsers/parser.py +++ b/src/vllm_router/parsers/parser.py @@ -315,9 +315,9 @@ def parse_args(): parser.add_argument( "--enable_queue", - type=bool, + action=argparse.BooleanOptionalAction, default=True, - help="Enable router-side queuing.", + help="Enable router-side queuing. Note that queue will still be initialized, just not actually enqueued.", ) parser.add_argument( "--max-wait-time", @@ -334,7 +334,7 @@ def parse_args(): ) parser.add_argument( - "--max_gpu_perc", + "--max-gpu-perc", type=int, default=95, help="The maximum GPU use percentage of an endpoint before the router enqueues an incoming request", diff --git a/src/vllm_router/services/queue_service/queue.py b/src/vllm_router/services/queue_service/queue.py index 6e26974f5..cf1b2f8b3 100644 --- a/src/vllm_router/services/queue_service/queue.py +++ b/src/vllm_router/services/queue_service/queue.py @@ -3,7 +3,7 @@ import asyncio import time from threading import Lock -from typing import Any, Dict, Optional +from typing import Any, Dict from fastapi.responses import StreamingResponse @@ -34,6 +34,7 @@ def __init__( self.enable_queue = enable_queue self.endpoint_queues: Dict[str, asyncio.PriorityQueue] = {} self.conditions: Dict[str, asyncio.Condition] = {} + self._register_lock = Lock() self.scraper = scraper or get_engine_stats_scraper() if self.scraper is None: @@ -60,13 +61,14 @@ async def register_endpoint(self, endpoint_url: str): Args: endpoint_url (str): The unique identifier (typically URL) for the backend endpoint. """ - if endpoint_url in self.endpoint_queues: - return # Already registered + async with self._register_lock: + if endpoint_url in self.endpoint_queues: + return # Already registered - self.endpoint_queues[endpoint_url] = asyncio.PriorityQueue() - self.conditions[endpoint_url] = asyncio.Condition() - task = asyncio.create_task(self._scheduler_loop(endpoint_url)) - self.endpoint_tasks[endpoint_url] = task + self.endpoint_queues[endpoint_url] = asyncio.PriorityQueue() + self.conditions[endpoint_url] = asyncio.Condition() + task = asyncio.create_task(self._scheduler_loop(endpoint_url)) + self.endpoint_tasks[endpoint_url] = task async def enqueue( self, endpoint_url: str, request: Dict[str, Any], priority: int = 0 @@ -119,6 +121,8 @@ async def _scheduler_loop(self, endpoint_url: str): asyncio.create_task( self._dispatch_and_signal(endpoint_url, request) ) + except asyncio.QueueEmpty: + pass except Exception as e: print(f"[Dispatch error] {e}") continue @@ -179,7 +183,7 @@ async def _dispatch_and_signal(self, endpoint_url: str, request: Dict[str, Any]) """ from vllm_router.services.request_service.request import process_request - result_future = request.get("_result_future") + result_future = request.get("result_future") try: stream_generator = process_request( request["request"], @@ -216,9 +220,6 @@ async def _dispatch_and_signal(self, endpoint_url: str, request: Dict[str, Any]) async def _reroute_or_dispatch_stale_request( self, request: dict, original_endpoint: str ): - request_id = request.get("request_id") - session_id = request.get("session_id") - model = request.get("model_name") """ Handles requests that have waited in the queue too long. Either reroutes them to a different eligible endpoint or re-enqueues them with higher priority. @@ -230,13 +231,16 @@ async def _reroute_or_dispatch_stale_request( # TODO: Use KV cache hit estimation in future, session aware id + priority = max( + 0, self.calculate_request_priority(request) - 1 + ) # priority is boosted + if ( True ): # Replace with conditionals, ie, no session affinity or high KV cache matches - priority = max( - 0, self.calculate_request_priority(request) - 1 - ) # priority is boosted + new_endpoint = self.find_new_endpoint(exclude=original_endpoint) + await self.register_endpoint(new_endpoint) if new_endpoint and new_endpoint != original_endpoint: # print(f"[Rerouting] Request {request_id} → {new_endpoint} (was {original_endpoint})") @@ -245,14 +249,12 @@ async def _reroute_or_dispatch_stale_request( self._dispatch_and_signal(new_endpoint, request) ) else: - self.enqueue(new_endpoint, request, priority) + await self.enqueue(new_endpoint, request, priority) return # Keep original endpoint # print(f"[Requeue] Request {request_id} stays at {original_endpoint}") - queue = self.endpoint_queues[original_endpoint] - async with self.conditions[original_endpoint]: - self.enqueue(original_endpoint, request, priority) + await self.enqueue(original_endpoint, request, priority) def find_new_endpoint(self, exclude: str) -> str: """ @@ -272,9 +274,11 @@ def find_new_endpoint(self, exclude: str) -> str: return exclude with self._lock: - chosen = sorted(endpoints, key=lambda e: e)[self.req % len(endpoints)] + new_endpoint = sorted(endpoints, key=lambda e: e)[ + self.req_id % len(endpoints) + ] self.req_id += 1 - return chosen + return new_endpoint def calculate_request_priority(self, request) -> int: # TODO """ diff --git a/src/vllm_router/services/request_service/request.py b/src/vllm_router/services/request_service/request.py index f02bd8e0c..8f77064e0 100644 --- a/src/vllm_router/services/request_service/request.py +++ b/src/vllm_router/services/request_service/request.py @@ -293,9 +293,9 @@ async def route_general_request( logger.debug(f"Debug session extraction - Request headers: {dict(request.headers)}") logger.debug(f"Debug session extraction - Extracted session ID: {session_id}") + await queue_manager.register_endpoint(server_url) # if queue does not already exist # Enqueue if endpoint load is too high if queue_manager.enable_queue and not queue_manager._endpoint_is_free(server_url): - queue_manager.register_endpoint(server_url) # if queue does not already exist response_future = asyncio.get_event_loop().create_future() From 7b631a42d7ad8c2456832fdc23f663051bdeb2db Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Tue, 5 Aug 2025 11:57:48 -0400 Subject: [PATCH 11/12] attempt to fix constant scraper polling Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- src/tests/test_queue.py | 21 ++++-- .../services/queue_service/queue.py | 74 +++++++++---------- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/tests/test_queue.py b/src/tests/test_queue.py index 3dcc6a585..b1ea19486 100644 --- a/src/tests/test_queue.py +++ b/src/tests/test_queue.py @@ -92,7 +92,7 @@ async def test_dispatch_and_signal(queue_manager): "request": MagicMock(), "endpoint": "endpoint1", "background_tasks": MagicMock(), - "_result_future": asyncio.Future(), + "result_future": asyncio.Future(), } with patch( @@ -117,12 +117,21 @@ async def test_scheduler_loop(queue_manager): "request": MagicMock(), "endpoint": "endpoint1", "background_tasks": MagicMock(), - "_result_future": asyncio.Future(), + "result_future": asyncio.Future(), } - await queue_manager.enqueue("endpoint1", test_request) - await asyncio.sleep(1) - assert test_request["_result_future"].done() + with patch( + "vllm_router.services.request_service.request.process_request" + ) as mock_process: + mock_headers = {"content-type": "application/json"} + mock_status = 200 + mock_stream = MagicMock() + mock_process.return_value = (mock_headers, mock_status, mock_stream) + + await queue_manager.enqueue("endpoint1", test_request) + await asyncio.sleep(1.5) # Wait enough time for scheduler loop + + assert test_request["result_future"].done() @pytest.mark.asyncio @@ -155,7 +164,7 @@ async def dummy_stream(): "request": dummy_request, "endpoint": "endpoint1", "background_tasks": MagicMock(), - "_result_future": asyncio.Future(), + "result_future": asyncio.Future(), "enqueue_timestamp": time.time() - 15, # 15s ago } queue_manager._endpoint_is_free = MagicMock(return_value=False) diff --git a/src/vllm_router/services/queue_service/queue.py b/src/vllm_router/services/queue_service/queue.py index cf1b2f8b3..9b1e4ca0c 100644 --- a/src/vllm_router/services/queue_service/queue.py +++ b/src/vllm_router/services/queue_service/queue.py @@ -34,7 +34,7 @@ def __init__( self.enable_queue = enable_queue self.endpoint_queues: Dict[str, asyncio.PriorityQueue] = {} self.conditions: Dict[str, asyncio.Condition] = {} - self._register_lock = Lock() + self._register_lock = asyncio.Lock() self.scraper = scraper or get_engine_stats_scraper() if self.scraper is None: @@ -45,6 +45,8 @@ def __init__( self.max_gpu_perc = max_gpu_perc self.max_queue_wait_time = max_queue_wait_time + self.stale_check_interval = 2 + # Stale request round-robin fallback strategy self.req_id = 0 self._lock = Lock() @@ -104,52 +106,48 @@ async def _scheduler_loop(self, endpoint_url: str): queue = self.endpoint_queues[endpoint_url] condition = self.conditions[endpoint_url] - try: - while not self._shutdown_event.is_set(): - async with condition: - await condition.wait_for(lambda: not queue.empty()) + last_stale_check = 0 + + while not self._shutdown_event.is_set(): + async with condition: + # Wait until queue not empty or shutdown + await condition.wait_for( + lambda: (not queue.empty()) or self._shutdown_event.is_set() + ) + if self._shutdown_event.is_set(): + break + + # Dispatch as many requests as endpoint allows + while not queue.empty() and self._endpoint_is_free(endpoint_url): + _, _, request = queue.get_nowait() + asyncio.create_task( + self._dispatch_and_signal(endpoint_url, request) + ) + # After dispatching, periodically check stale requests outside the condition + now = time.time() + if now - last_stale_check > self.stale_check_interval: + last_stale_check = now + + # Check for stale requests without holding the condition lock try: - # Peek at the top of the queue without removing - priority, enqueue_time, request = queue._queue[0] + priority, enqueue_time, stale_request = queue._queue[0] except IndexError: - continue # Queue is empty + continue # queue empty - if self._endpoint_is_free(endpoint_url): - try: - _, _, request = queue.get_nowait() # Dequeue - asyncio.create_task( - self._dispatch_and_signal(endpoint_url, request) - ) - except asyncio.QueueEmpty: - pass - except Exception as e: - print(f"[Dispatch error] {e}") - continue - - wait_duration = time.time() - enqueue_time - # print(f"Request waited {wait_duration:.2f}s, threshold is {self.max_queue_wait_time}s") + wait_duration = now - enqueue_time if wait_duration > self.max_queue_wait_time: - # Dequeue and reroute - try: - _, _, stale_request = queue.get_nowait() + async with condition: + try: + _, _, stale_request = queue.get_nowait() + except asyncio.QueueEmpty: + continue + await self._reroute_or_dispatch_stale_request( stale_request, endpoint_url ) - except Exception as e: - print(f"[Stale reroute error] {e}") - continue - - # Endpoint not free and not stale → yield loop - await asyncio.sleep(0.05) - - except asyncio.CancelledError: - print(f"Scheduler loop for {endpoint_url} cancelled.") - except Exception as e: - import traceback - traceback.print_exc() - print(f"Error in scheduler loop ({endpoint_url}): {e}") + await asyncio.sleep(0.05) # small sleep to avoid tight loop def _endpoint_is_free( self, endpoint_url: str From 633dd2e13436dbafe68952fb3f7786fd2f3297ec Mon Sep 17 00:00:00 2001 From: allytotheson <82621261+allytotheson@users.noreply.github.com> Date: Wed, 6 Aug 2025 19:16:43 -0400 Subject: [PATCH 12/12] set enable queue to default False Signed-off-by: allytotheson <82621261+allytotheson@users.noreply.github.com> --- src/vllm_router/parsers/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vllm_router/parsers/parser.py b/src/vllm_router/parsers/parser.py index 7665c8de5..349159b9a 100644 --- a/src/vllm_router/parsers/parser.py +++ b/src/vllm_router/parsers/parser.py @@ -342,7 +342,7 @@ def parse_args(): parser.add_argument( "--enable_queue", action=argparse.BooleanOptionalAction, - default=True, + default=False, help="Enable router-side queuing. Note that queue will still be initialized, just not actually enqueued.", ) parser.add_argument(