|
12 | 12 | from collections import deque |
13 | 13 | from contextlib import asynccontextmanager |
14 | 14 | from datetime import datetime |
| 15 | +from functools import partial |
15 | 16 | from http import HTTPStatus |
16 | 17 | from pathlib import Path |
17 | 18 | from typing import (Annotated, Any, AsyncGenerator, AsyncIterator, List, |
@@ -666,6 +667,9 @@ def register_routes(self): |
666 | 667 | self.app.add_api_route("/kv_cache_events", |
667 | 668 | self.get_kv_cache_events, |
668 | 669 | methods=["POST"]) |
| 670 | + self.app.add_api_route("/reset_prefix_cache", |
| 671 | + self.reset_prefix_cache, |
| 672 | + methods=["POST"]) |
669 | 673 | resource_governor_queue = self.generator._executor.resource_governor_queue |
670 | 674 | if resource_governor_queue is not None: |
671 | 675 | from .resource_governor import ResourceGovernor |
@@ -1027,6 +1031,27 @@ async def get_kv_cache_events(self) -> JSONResponse: |
1027 | 1031 | pass |
1028 | 1032 | return JSONResponse(content=events) |
1029 | 1033 |
|
| 1034 | + async def reset_prefix_cache(self) -> Response: |
| 1035 | + """Reset local PyTorch KV prefix-cache reuse state.""" |
| 1036 | + reset_fn = getattr(self.generator, "reset_prefix_cache", None) |
| 1037 | + if reset_fn is None: |
| 1038 | + return self._create_not_supported_error( |
| 1039 | + "reset_prefix_cache() is only supported by the PyTorch backend." |
| 1040 | + ) |
| 1041 | + |
| 1042 | + try: |
| 1043 | + await asyncio.get_running_loop().run_in_executor(None, reset_fn) |
| 1044 | + except NotImplementedError as e: |
| 1045 | + return self._create_not_supported_error(str(e)) |
| 1046 | + except (RuntimeError, ValueError) as e: |
| 1047 | + return self.create_error_response( |
| 1048 | + message=str(e), |
| 1049 | + err_type="InvalidRequestError", |
| 1050 | + status_code=HTTPStatus.CONFLICT, |
| 1051 | + ) |
| 1052 | + |
| 1053 | + return JSONResponse(content={"status": "success"}) |
| 1054 | + |
1030 | 1055 | async def _extract_metrics(self, res: RequestOutput, raw_request: Request): |
1031 | 1056 | if not res.finished: |
1032 | 1057 | return |
@@ -1952,26 +1977,55 @@ async def openai_responses_delete_response( |
1952 | 1977 | "deleted": True |
1953 | 1978 | }) |
1954 | 1979 |
|
1955 | | - async def release_memory(self, |
1956 | | - request: MemoryUpdateRequest) -> JSONResponse: |
1957 | | - assert isinstance( |
1958 | | - self.generator, AsyncLLM |
1959 | | - ), "/release_memory endpoint is only supported with AsyncLLM()" |
1960 | | - await self.generator.collective_rpc('sleep', args=(request.tags, )) |
1961 | | - return JSONResponse(content={"status": "success"}) |
| 1980 | + async def _run_worker_control_rpc(self, method: str, |
| 1981 | + args: tuple[Any, ...]) -> None: |
| 1982 | + """Dispatch a worker control method through direct or collective RPC.""" |
| 1983 | + executor = getattr(self.generator, "_executor", None) |
| 1984 | + direct_method = getattr(executor, method, None) |
| 1985 | + if direct_method is not None: |
| 1986 | + await asyncio.get_running_loop().run_in_executor( |
| 1987 | + None, partial(direct_method, *args)) |
| 1988 | + return |
| 1989 | + |
| 1990 | + if isinstance(self.generator, AsyncLLM): |
| 1991 | + await self.generator.collective_rpc(method, args=args) |
| 1992 | + return |
| 1993 | + |
| 1994 | + collective_rpc = getattr(self.generator, "_collective_rpc", None) |
| 1995 | + if collective_rpc is not None: |
| 1996 | + await asyncio.get_running_loop().run_in_executor( |
| 1997 | + None, partial(collective_rpc, method, args)) |
| 1998 | + return |
| 1999 | + |
| 2000 | + raise NotImplementedError( |
| 2001 | + f"{method}() is only supported by the PyTorch backend.") |
| 2002 | + |
| 2003 | + async def _handle_worker_control_rpc(self, method: str, |
| 2004 | + args: tuple[Any, ...]) -> Response: |
| 2005 | + """Run a worker control method and map failures to HTTP responses.""" |
| 2006 | + try: |
| 2007 | + await self._run_worker_control_rpc(method, args) |
| 2008 | + except NotImplementedError as e: |
| 2009 | + return self._create_not_supported_error(str(e)) |
| 2010 | + except (RuntimeError, ValueError) as e: |
| 2011 | + return self.create_error_response( |
| 2012 | + message=str(e), |
| 2013 | + err_type="InvalidRequestError", |
| 2014 | + status_code=HTTPStatus.CONFLICT, |
| 2015 | + ) |
1962 | 2016 |
|
1963 | | - async def resume_memory(self, request: MemoryUpdateRequest) -> JSONResponse: |
1964 | | - assert isinstance( |
1965 | | - self.generator, AsyncLLM |
1966 | | - ), "/resume_memory endpoint is only supported with AsyncLLM()" |
1967 | | - await self.generator.collective_rpc('wakeup', args=(request.tags, )) |
1968 | 2017 | return JSONResponse(content={"status": "success"}) |
1969 | 2018 |
|
1970 | | - async def update_weights(self, |
1971 | | - request: UpdateWeightsRequest) -> JSONResponse: |
1972 | | - assert isinstance( |
1973 | | - self.generator, AsyncLLM |
1974 | | - ), "/update_weights endpoint is only supported with AsyncLLM()" |
| 2019 | + async def release_memory(self, request: MemoryUpdateRequest) -> Response: |
| 2020 | + return await self._handle_worker_control_rpc('sleep', (request.tags, )) |
| 2021 | + |
| 2022 | + async def resume_memory(self, request: MemoryUpdateRequest) -> Response: |
| 2023 | + return await self._handle_worker_control_rpc('wakeup', (request.tags, )) |
| 2024 | + |
| 2025 | + async def update_weights(self, request: UpdateWeightsRequest) -> Response: |
| 2026 | + if not isinstance(self.generator, AsyncLLM): |
| 2027 | + return self._create_not_supported_error( |
| 2028 | + "/update_weights endpoint is only supported with AsyncLLM()") |
1975 | 2029 | await self.generator.collective_rpc('update_weights', |
1976 | 2030 | args=(request.weights, )) |
1977 | 2031 | return JSONResponse(content={"status": "success"}) |
|
0 commit comments