-
Notifications
You must be signed in to change notification settings - Fork 1.6k
feat: add endpoint to clear all kv blocks in vllm v1 #1384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f1a0fd2
19d59e4
38e51fa
44aa1ee
c5f0f54
cf802b4
15135dc
3465076
a51c646
e7ab327
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,12 +29,14 @@ pub async fn run( | |
| engine_config: EngineConfig, | ||
| template: Option<RequestTemplate>, | ||
| ) -> anyhow::Result<()> { | ||
| let distributed_runtime = DistributedRuntime::from_settings(runtime.clone()).await?; | ||
| let http_service = service_v2::HttpService::builder() | ||
| .port(flags.http_port) | ||
| .enable_chat_endpoints(true) | ||
| .enable_cmpl_endpoints(true) | ||
| .enable_embeddings_endpoints(true) | ||
| .with_request_template(template) | ||
| .runtime(Some(Arc::new(distributed_runtime))) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like a pretty intrusive change if it adds runtime object that wasn't previously necessary for other functionalities. I think we'll probably want some kind of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CC @ryanolson @paulhendricks @grahamking (Graham on PTO, but for future viz)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea Graham had me remove the DRT from this. I think there should be a way to access things via the ModelState. Also see #1037 (comment) |
||
| .build()?; | ||
| match engine_config { | ||
| EngineConfig::Dynamic => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |
|
|
||
| mod openai; | ||
|
|
||
| pub mod clear_kv_blocks; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to put this on the public API? I'm not so sure. |
||
| pub mod error; | ||
| pub mod health; | ||
| pub mod metrics; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,258 @@ | ||||
| // SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||||
| // SPDX-License-Identifier: Apache-2.0 | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|
|
||||
| use super::{service_v2, RouteDoc}; | ||||
| use axum::{http::Method, response::IntoResponse, routing::post, Json, Router}; | ||||
| use serde_json::json; | ||||
| use std::sync::Arc; | ||||
|
|
||||
| use dynamo_runtime::{pipeline::PushRouter, stream::StreamExt}; | ||||
|
|
||||
| pub fn clear_kv_blocks_router( | ||||
| state: Arc<service_v2::State>, | ||||
| path: Option<String>, | ||||
| ) -> (Vec<RouteDoc>, Router) { | ||||
| let path = path.unwrap_or_else(|| "/clear_kv_blocks".to_string()); | ||||
|
|
||||
| let docs: Vec<RouteDoc> = vec![RouteDoc::new(Method::POST, &path)]; | ||||
|
|
||||
| let router = Router::new() | ||||
| .route(&path, post(clear_kv_blocks_handler)) | ||||
| .with_state(state); | ||||
|
|
||||
| (docs, router) | ||||
| } | ||||
|
|
||||
| async fn clear_kv_blocks_handler( | ||||
| axum::extract::State(state): axum::extract::State<Arc<service_v2::State>>, | ||||
| ) -> impl IntoResponse { | ||||
| let model_entries = state.manager().get_model_entries(); | ||||
|
|
||||
| // if there are no active workers | ||||
| if model_entries.is_empty() { | ||||
| return Json(serde_json::json!({ | ||||
| "message": "No active worker groups found" | ||||
| })); | ||||
| } | ||||
|
|
||||
| let distributed = match state.runtime() { | ||||
| Some(runtime) => runtime, | ||||
| None => { | ||||
| return Json(serde_json::json!({ | ||||
| "message": "Failed to create distributed runtime", | ||||
| })); | ||||
| } | ||||
| }; | ||||
|
|
||||
| let mut cleared_workers = Vec::new(); | ||||
| let mut failed_workers = Vec::new(); | ||||
|
|
||||
| // update cleared and failed workers | ||||
| let mut add_worker_result = |success: bool, | ||||
| name: String, | ||||
| status: &str, | ||||
| ns: &str, | ||||
| comp: &str, | ||||
| message: Option<String>| { | ||||
| let mut result = json!({ | ||||
| "name": name, | ||||
| "endpoint": format!("{}/{}/clear_kv_blocks", ns, comp), | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
dynamo/lib/llm/src/kv_router.rs Line 45 in 0e7d4d8
|
||||
| "status": status, | ||||
| }); | ||||
| if success { | ||||
| if let Some(m) = message { | ||||
| result["response"] = json!(m); | ||||
| } | ||||
| cleared_workers.push(result); | ||||
| } else { | ||||
| if let Some(m) = message { | ||||
| result["error"] = json!(m); | ||||
| } | ||||
| failed_workers.push(result); | ||||
| } | ||||
| }; | ||||
|
|
||||
| // create client for each model entry | ||||
| for entry in &model_entries { | ||||
| let namespace = &entry.endpoint.namespace; | ||||
| let component = &entry.endpoint.component; | ||||
| let entry_name = entry.name.to_string(); | ||||
|
|
||||
| tracing::debug!("Processing worker group: {}/{}", namespace, component); | ||||
|
|
||||
| let namespace_obj = match distributed.namespace(namespace) { | ||||
|
jain-ria marked this conversation as resolved.
|
||||
| Ok(ns) => ns, | ||||
| Err(e) => { | ||||
| add_worker_result( | ||||
| false, | ||||
| entry_name, | ||||
| "Failed to get namespace", | ||||
| namespace, | ||||
| component, | ||||
| Some(e.to_string()), | ||||
| ); | ||||
| continue; | ||||
| } | ||||
| }; | ||||
|
|
||||
| let component_obj = match namespace_obj.component(component) { | ||||
| Ok(comp) => comp, | ||||
| Err(e) => { | ||||
| add_worker_result( | ||||
| false, | ||||
| entry_name, | ||||
| "Failed to get component", | ||||
| namespace, | ||||
| component, | ||||
| Some(e.to_string()), | ||||
| ); | ||||
| continue; | ||||
| } | ||||
| }; | ||||
|
|
||||
| let endpoint: dynamo_runtime::component::Endpoint = | ||||
| component_obj.endpoint("clear_kv_blocks"); | ||||
|
|
||||
| let client = match endpoint.client().await { | ||||
| Ok(c) => c, | ||||
| Err(e) => { | ||||
| add_worker_result( | ||||
| false, | ||||
| entry_name, | ||||
| "Failed to get client", | ||||
| namespace, | ||||
| component, | ||||
| Some(e.to_string()), | ||||
| ); | ||||
| continue; | ||||
| } | ||||
| }; | ||||
|
|
||||
| let router = match PushRouter::<(), serde_json::Value>::from_client( | ||||
| client.clone(), | ||||
| Default::default(), | ||||
| ) | ||||
| .await | ||||
| { | ||||
| Ok(r) => r, | ||||
|
jain-ria marked this conversation as resolved.
|
||||
| Err(e) => { | ||||
| add_worker_result( | ||||
| false, | ||||
| entry_name, | ||||
| "Failed to create router", | ||||
| namespace, | ||||
| component, | ||||
| Some(e.to_string()), | ||||
| ); | ||||
| continue; | ||||
| } | ||||
| }; | ||||
|
|
||||
| let instances = match component_obj.list_instances().await { | ||||
| Ok(instances) => instances, | ||||
| Err(e) => { | ||||
| add_worker_result( | ||||
| false, | ||||
| entry_name, | ||||
| "Failed to get instances for worker group", | ||||
| namespace, | ||||
| component, | ||||
| Some(e.to_string()), | ||||
| ); | ||||
| continue; | ||||
| } | ||||
| }; | ||||
|
|
||||
| if instances.is_empty() { | ||||
| add_worker_result( | ||||
| false, | ||||
| entry_name, | ||||
| "No instances found for worker group", | ||||
| namespace, | ||||
| component, | ||||
| None, | ||||
| ); | ||||
| continue; | ||||
| } | ||||
|
|
||||
| let instances_filtered = instances | ||||
| .clone() | ||||
| .into_iter() | ||||
| .filter(|instance| instance.endpoint == "clear_kv_blocks") | ||||
| .collect::<Vec<_>>(); | ||||
|
|
||||
| if instances_filtered.is_empty() { | ||||
| let found_endpoints: Vec<String> = instances | ||||
| .iter() | ||||
| .map(|instance| instance.endpoint.clone()) | ||||
| .collect(); | ||||
| add_worker_result( | ||||
| false, | ||||
| entry_name, | ||||
| &format!( | ||||
| "Worker group doesn't support clear_kv_blocks. Supported endpoints: {}", | ||||
| found_endpoints.join(", ") | ||||
| ), | ||||
| namespace, | ||||
| component, | ||||
| None, | ||||
| ); | ||||
| continue; | ||||
| } | ||||
|
|
||||
| for instance in &instances_filtered { | ||||
| let instance_name = format!("{}-instance-{}", entry.name, instance.id()); | ||||
| match router.round_robin(().into()).await { | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why |
||||
| Ok(mut stream) => match stream.next().await { | ||||
| Some(response) => { | ||||
| add_worker_result( | ||||
| true, | ||||
| instance_name, | ||||
| "Successfully cleared kv blocks for instance", | ||||
| namespace, | ||||
| component, | ||||
| Some(response.to_string()), | ||||
| ); | ||||
| } | ||||
| None => { | ||||
| add_worker_result( | ||||
| false, | ||||
| instance_name, | ||||
| "No response from instance", | ||||
| namespace, | ||||
| component, | ||||
| None, | ||||
| ); | ||||
| } | ||||
|
jain-ria marked this conversation as resolved.
|
||||
| }, | ||||
| Err(e) => { | ||||
| add_worker_result( | ||||
| false, | ||||
| instance_name, | ||||
| "Failed to send request for instance", | ||||
| namespace, | ||||
| component, | ||||
| Some(e.to_string()), | ||||
| ); | ||||
| } | ||||
| } | ||||
| } | ||||
| } | ||||
|
|
||||
| Json(serde_json::json!({ | ||||
| "cleared_workers": cleared_workers, | ||||
| "failed_workers": failed_workers | ||||
| })) | ||||
| } | ||||
Uh oh!
There was an error while loading. Please reload this page.