[Rust Frontend][gRPC] Add server and model discovery (#49491)

Signed-off-by: Connor Carpenter <connorc@nvidia.com> Co-authored-by: Nick Hill <nickhill123@gmail.com>

Connor Carpenter committed Jul 27, 2026 at 09:53 UTC e3c2fc3b3ca3f41466126cd2aa0b228eb8465844
18 files changed +615 -269
rust/proto/control.proto new
+53
@@ -0,0 +1,53 @@
1 +// SPDX-License-Identifier: Apache-2.0
2 +// SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +syntax = "proto3";
5 +package vllm;
6 +
7 +service Control {
8 + rpc GetServerInfo (GetServerInfoRequest) returns (ServerInfo) {}
9 + rpc GetModelInfo (GetModelInfoRequest) returns (ModelInfo) {}
10 + rpc Abort (AbortRequest) returns (AbortResponse) {}
11 +}
12 +
13 +message GetServerInfoRequest {}
14 +
15 +message ServerInfo {
16 + string engine_version = 1;
17 + string api_version = 2;
18 + string instance_id = 3;
19 + ParallelismInfo parallelism = 4;
20 + uint32 max_model_len = 5;
21 + uint32 kv_block_size = 6;
22 + uint64 total_kv_blocks = 7;
23 + uint64 max_running_requests = 8;
24 + uint64 max_batched_tokens = 9;
25 +}
26 +
27 +message ParallelismInfo {
28 + uint32 tensor_parallel_size = 1;
29 + uint32 pipeline_parallel_size = 2;
30 + uint32 data_parallel_size = 3;
31 + uint32 data_parallel_rank = 4;
32 + uint32 decode_context_parallel_size = 5;
33 +}
34 +
35 +message GetModelInfoRequest {}
36 +
37 +message ModelInfo {
38 + string model_id = 1;
39 + string served_model_name = 2;
40 + repeated string served_model_aliases = 3;
41 +
42 + bool supports_text_input = 20;
43 + bool supports_token_ids_input = 21;
44 + bool supports_multimodal = 23;
45 + string reasoning_parser = 24;
46 + string tool_call_parser = 25;
47 +}
48 +
49 +message AbortRequest {
50 + repeated string request_ids = 1;
51 +}
52 +
53 +message AbortResponse {}
rust/proto/inference.proto renamed
+1 -15
@@ -7,17 +7,13 @@ package vllm;
7 import "google/protobuf/struct.proto";
8
9
10 -service Generate {
10 +service Inference {
11 // Generates text given a prompt
12 rpc Generate (GenerateRequest) returns (GenerateResponse) {}
13 // Generates text given a prompt, streaming the outputs
14 rpc GenerateStream (GenerateRequest) returns (stream GenerateResponse) {}
15 }
16
17 -service Control {
18 - rpc Abort (AbortRequest) returns (AbortResponse) {}
19 -}
20 -
17 // ======================================================================================
18 // Generate Request
19 // ======================================================================================
@@ -204,13 +200,3 @@ message CandidateTokenInfo {
200 message TokenIds {
201 repeated uint32 ids = 1;
202 }
207 -
208 -// ======================================================================================
209 -// Control
210 -// ======================================================================================
211 -
212 -message AbortRequest {
213 - repeated string request_ids = 1;
214 -}
215 -
216 -message AbortResponse {}
rust/src/chat/src/lib.rs
+27
@@ -255,6 +255,33 @@ impl ChatLlm {
255 self.text.engine_core_client()
256 }
257
258 + /// Whether the loaded backend has a registered multimodal processor.
259 + pub fn supports_multimodal(&self) -> bool {
260 + self.processor.backend.multimodal_model_info().is_some()
261 + }
262 +
263 + /// Effective tool-call parser name for this model, if parsing is enabled.
264 + pub fn tool_call_parser_name(&self) -> Option<&str> {
265 + match &self.tool_call_parser {
266 + ParserSelection::Auto => {
267 + ToolParserFactory::global().resolve_name_for_model(self.model_id())
268 + }
269 + ParserSelection::None => None,
270 + ParserSelection::Explicit(name) => Some(name),
271 + }
272 + }
273 +
274 + /// Effective reasoning parser name for this model, if parsing is enabled.
275 + pub fn reasoning_parser_name(&self) -> Option<&str> {
276 + match &self.reasoning_parser {
277 + ParserSelection::Auto => {
278 + ReasoningParserFactory::global().resolve_name_for_model(self.model_id())
279 + }
280 + ParserSelection::None => None,
281 + ParserSelection::Explicit(name) => Some(name),
282 + }
283 + }
284 +
285 /// Render, tokenize, and submit one chat request.
286 pub async fn chat(&self, request: ChatRequest) -> Result<ChatEventStream> {
287 let (text_request, output_processor) = self
rust/src/engine-core-client/src/client.rs
+11
@@ -394,6 +394,17 @@ impl EngineCoreClient {
394 self.engines.iter().map(|engine| &engine.ready_response).collect()
395 }
396
397 + /// Return the first engine's ready response.
398 + ///
399 + /// Per-engine fields such as `data_parallel_rank` should be read through
400 + /// [`ready_responses`](Self::ready_responses).
401 + pub fn ready_response(&self) -> &EngineCoreReadyResponse {
402 + &self
403 + .engines
404 + .first()
405 + .expect("engine core client requires at least one engine")
406 + .ready_response
407 + }
408 /// Return the engine-reported effective model dtype.
409 pub fn model_dtype(&self) -> ModelDtype {
410 self.engines
rust/src/engine-core-client/src/mock_engine.rs
+7
@@ -57,6 +57,13 @@ pub fn default_ready_response() -> EngineCoreReadyResponse {
57 vllm_version: "test-vllm-version".to_string(),
58 world_size: 1,
59 data_parallel_size: 1,
60 + tensor_parallel_size: 1,
61 + pipeline_parallel_size: 1,
62 + decode_context_parallel_size: 1,
63 + data_parallel_rank: 0,
64 + max_num_seqs: 256,
65 + max_num_batched_tokens: 8192,
66 + instance_id: "test-instance".to_string(),
67 kv_cache_size_tokens: None,
68 kv_cache_max_concurrency: None,
69 }
rust/src/engine-core-client/src/protocol/handshake.rs
+15
@@ -52,6 +52,21 @@ pub struct EngineCoreReadyResponse {
52 pub world_size: u64,
53 /// Data parallelism size from the parallel config.
54 pub data_parallel_size: u64,
55 + // Required discovery metadata; EngineCore and client versions must match.
56 + /// Tensor-parallel size of this engine.
57 + pub tensor_parallel_size: u32,
58 + /// Pipeline-parallel size of this engine.
59 + pub pipeline_parallel_size: u32,
60 + /// Decode-context-parallel size of this engine.
61 + pub decode_context_parallel_size: u32,
62 + /// This engine's data-parallel rank.
63 + pub data_parallel_rank: u32,
64 + /// Scheduler cap on concurrently running sequences.
65 + pub max_num_seqs: u64,
66 + /// Scheduler cap on batched tokens per step.
67 + pub max_num_batched_tokens: u64,
68 + /// Unique identifier for this server instance.
69 + pub instance_id: String,
70 /// Total KV cache capacity in tokens, if reported.
71 pub kv_cache_size_tokens: Option<u64>,
72 /// Maximum achievable request concurrency given the KV cache, if reported.
rust/src/engine-core-client/src/tests/python_compat.py
+14
@@ -363,6 +363,13 @@ class EngineCoreReadyResponse:
363 vllm_version: str
364 world_size: int
365 data_parallel_size: int
366 + tensor_parallel_size: int
367 + pipeline_parallel_size: int
368 + decode_context_parallel_size: int
369 + data_parallel_rank: int
370 + max_num_seqs: int
371 + max_num_batched_tokens: int
372 + instance_id: str
373 kv_cache_size_tokens: int | None = None
374 kv_cache_max_concurrency: float | None = None
375
@@ -376,6 +383,13 @@ ready_response = EngineCoreReadyResponse(
383 vllm_version="0.0.0",
384 data_parallel_size=1,
385 world_size=1,
386 + tensor_parallel_size=1,
387 + pipeline_parallel_size=1,
388 + decode_context_parallel_size=1,
389 + data_parallel_rank=0,
390 + max_num_seqs=256,
391 + max_num_batched_tokens=8192,
392 + instance_id="test-instance",
393 )
394
395 print(msgspec.msgpack.encode(request).hex())
rust/src/server/build.rs
+7 -1
@@ -9,7 +9,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
9 .build_server(true)
10 .build_client(true)
11 .protoc_arg("--experimental_allow_proto3_optional") // be compatible with old compilers
12 - .compile_protos(&[format!("{proto_dir}/vllm_grpc.proto")], &[proto_dir])?;
12 + .compile_protos(
13 + &[
14 + format!("{proto_dir}/control.proto"),
15 + format!("{proto_dir}/inference.proto"),
16 + ],
17 + &[proto_dir],
18 + )?;
19
20 Ok(())
21 }
rust/src/server/src/grpc/control.rs new
+106
@@ -0,0 +1,106 @@
1 +// SPDX-License-Identifier: Apache-2.0
2 +// SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +use std::sync::Arc;
5 +
6 +use thiserror_ext::AsReport as _;
7 +use tonic::{Request, Response, Status};
8 +use vllm_engine_core_client::protocol::handshake::EngineCoreReadyResponse;
9 +
10 +use super::{ControlServer, pb};
11 +use crate::state::AppState;
12 +
13 +pub(crate) type ControlGrpcService = ControlServer<ControlServiceImpl>;
14 +
15 +/// gRPC control service backed by the shared application state.
16 +pub struct ControlServiceImpl {
17 + state: Arc<AppState>,
18 +}
19 +
20 +impl ControlServiceImpl {
21 + pub fn new(state: Arc<AppState>) -> Self {
22 + Self { state }
23 + }
24 +
25 + fn ready(&self) -> &EngineCoreReadyResponse {
26 + self.state.engine_core_client().ready_response()
27 + }
28 +
29 + fn parallelism_info(&self) -> pb::ParallelismInfo {
30 + let ready = self.ready();
31 + pb::ParallelismInfo {
32 + tensor_parallel_size: ready.tensor_parallel_size,
33 + pipeline_parallel_size: ready.pipeline_parallel_size,
34 + data_parallel_size: ready.data_parallel_size.min(u64::from(u32::MAX)) as u32,
35 + data_parallel_rank: ready.data_parallel_rank,
36 + decode_context_parallel_size: ready.decode_context_parallel_size,
37 + }
38 + }
39 +}
40 +
41 +const GRPC_API_VERSION: &str = "vllm";
42 +
43 +#[tonic::async_trait]
44 +impl pb::control_server::Control for ControlServiceImpl {
45 + async fn get_server_info(
46 + &self,
47 + _request: Request<pb::GetServerInfoRequest>,
48 + ) -> Result<Response<pb::ServerInfo>, Status> {
49 + let ready = self.ready();
50 + Ok(Response::new(pb::ServerInfo {
51 + engine_version: ready.vllm_version.clone(),
52 + api_version: GRPC_API_VERSION.to_string(),
53 + instance_id: ready.instance_id.clone(),
54 + parallelism: Some(self.parallelism_info()),
55 + max_model_len: self.state.engine_core_client().max_model_len(),
56 + kv_block_size: ready.block_size.min(u64::from(u32::MAX)) as u32,
57 + total_kv_blocks: self.state.engine_core_client().total_num_gpu_blocks(),
58 + max_running_requests: ready.max_num_seqs,
59 + max_batched_tokens: ready.max_num_batched_tokens,
60 + }))
61 + }
62 +
63 + async fn get_model_info(
64 + &self,
65 + _request: Request<pb::GetModelInfoRequest>,
66 + ) -> Result<Response<pb::ModelInfo>, Status> {
67 + let served = self.state.served_model_names();
68 + Ok(Response::new(pb::ModelInfo {
69 + model_id: self.state.chat.text().model_id().to_string(),
70 + served_model_name: self.state.primary_model_name().to_string(),
71 + served_model_aliases: served.iter().skip(1).cloned().collect(),
72 + // GenerateRequest accepts both prompt representations.
73 + supports_text_input: true,
74 + supports_token_ids_input: true,
75 + supports_multimodal: self.state.chat.supports_multimodal(),
76 + reasoning_parser: self
77 + .state
78 + .chat
79 + .reasoning_parser_name()
80 + .unwrap_or_default()
81 + .to_string(),
82 + tool_call_parser: self
83 + .state
84 + .chat
85 + .tool_call_parser_name()
86 + .unwrap_or_default()
87 + .to_string(),
88 + }))
89 + }
90 +
91 + async fn abort(
92 + &self,
93 + request: Request<pb::AbortRequest>,
94 + ) -> Result<Response<pb::AbortResponse>, Status> {
95 + let request_ids = request.into_inner().request_ids;
96 + if request_ids.is_empty() {
97 + return Ok(Response::new(pb::AbortResponse {}));
98 + }
99 + self.state
100 + .chat
101 + .abort(&request_ids)
102 + .await
103 + .map_err(|error| Status::internal(error.to_report_string()))?;
104 + Ok(Response::new(pb::AbortResponse {}))
105 + }
106 +}
rust/src/server/src/grpc/health.rs
+4 -4
@@ -8,14 +8,14 @@ use tonic_health::ServingStatus;
8 use tonic_health::server::HealthReporter;
9 use tracing::{info, warn};
10
11 -use super::{ControlGrpcService, GenerateGrpcService};
11 +use super::{ControlGrpcService, InferenceGrpcService};
12
13 pub(crate) async fn monitor_health(
14 mut health_reporter: HealthReporter,
15 mut engine_health: watch::Receiver<bool>,
16 shutdown: CancellationToken,
17 ) {
18 - let generate_service = GenerateGrpcService::NAME;
18 + let inference_service = InferenceGrpcService::NAME;
19 let control_service = ControlGrpcService::NAME;
20 let status = ServingStatus::NotServing;
21 let health_event_first = tokio::select! {
@@ -45,7 +45,7 @@ pub(crate) async fn monitor_health(
45 }
46 };
47
48 - health_reporter.set_not_serving::<GenerateGrpcService>().await;
48 + health_reporter.set_not_serving::<InferenceGrpcService>().await;
49 health_reporter.set_not_serving::<ControlGrpcService>().await;
50 // Both gRPC services use the same engine client, so overall server health
51 // mirrors their shared engine health.
@@ -59,7 +59,7 @@ pub(crate) async fn monitor_health(
59 );
60 }
61
62 - health_reporter.clear_service_status(generate_service).await;
62 + health_reporter.clear_service_status(inference_service).await;
63 health_reporter.clear_service_status(control_service).await;
64 health_reporter.clear_service_status("").await;
65 }
rust/src/server/src/grpc/inference.rs new
+156
@@ -0,0 +1,156 @@
1 +// SPDX-License-Identifier: Apache-2.0
2 +// SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +use std::pin::Pin;
5 +use std::sync::Arc;
6 +
7 +use futures::{Stream, StreamExt as _};
8 +use thiserror_ext::AsReport as _;
9 +use tokio::sync::mpsc;
10 +use tokio_stream::wrappers::ReceiverStream;
11 +use tonic::{Request, Response, Status};
12 +use tracing::info;
13 +use vllm_text::{DecodedTextEvent, TextOutputStreamExt as _};
14 +
15 +use super::convert::{self, ResponseOpts};
16 +use super::{InferenceServer, pb};
17 +use crate::state::AppState;
18 +
19 +pub(crate) type InferenceGrpcService = InferenceServer<InferenceServiceImpl>;
20 +
21 +/// gRPC inference service backed by the shared application state.
22 +pub struct InferenceServiceImpl {
23 + state: Arc<AppState>,
24 +}
25 +
26 +impl InferenceServiceImpl {
27 + pub fn new(state: Arc<AppState>) -> Self {
28 + Self { state }
29 + }
30 +}
31 +
32 +#[tonic::async_trait]
33 +impl pb::inference_server::Inference for InferenceServiceImpl {
34 + type GenerateStreamStream =
35 + Pin<Box<dyn Stream<Item = Result<pb::GenerateResponse, Status>> + Send>>;
36 +
37 + /// Unary generate: collect all output and return a single response.
38 + async fn generate(
39 + &self,
40 + request: Request<pb::GenerateRequest>,
41 + ) -> Result<Response<pb::GenerateResponse>, Status> {
42 + let proto_req = request.into_inner();
43 + let response_opts = ResponseOpts::from_proto(proto_req.response.as_ref());
44 + let text_request =
45 + convert::to_text_request(proto_req, false, self.state.served_model_names())?;
46 +
47 + let request_id = text_request.request_id.clone();
48 + info!(%request_id, "grpc generate (unary)");
49 +
50 + let stream = self.state.chat.text().generate(text_request).await;
51 + let stream = stream.map_err(text_error_to_status)?;
52 +
53 + let collected = stream.collect_output().await.map_err(text_error_to_status)?;
54 +
55 + // Build the single aggregated response.
56 + let prompt_info = convert::to_prompt_info(
57 + &collected.prompt_token_ids,
58 + collected.prompt_logprobs.as_ref(),
59 + &response_opts,
60 + );
61 +
62 + let finish_info = vllm_text::Finished {
63 + usage: collected.usage,
64 + finish_reason: collected.finish_reason,
65 + kv_transfer_params: collected.kv_transfer_params,
66 + ec_transfer_params: collected.ec_transfer_params,
67 + };
68 +
69 + let outputs = convert::to_sequence_output(
70 + &collected.text,
71 + &collected.token_ids,
72 + collected.logprobs.as_ref(),
73 + Some(&finish_info),
74 + &response_opts,
75 + );
76 +
77 + Ok(Response::new(pb::GenerateResponse {
78 + prompt_info: Some(prompt_info),
79 + outputs: Some(outputs),
80 + }))
81 + }
82 +
83 + /// Streaming generate: yield incremental responses as tokens are produced.
84 + async fn generate_stream(
85 + &self,
86 + request: Request<pb::GenerateRequest>,
87 + ) -> Result<Response<Self::GenerateStreamStream>, Status> {
88 + let proto_req = request.into_inner();
89 + let response_opts = ResponseOpts::from_proto(proto_req.response.as_ref());
90 + let text_request =
91 + convert::to_text_request(proto_req, true, self.state.served_model_names())?;
92 +
93 + let request_id = text_request.request_id.clone();
94 + info!(%request_id, "grpc generate (stream)");
95 +
96 + let stream = self.state.chat.text().generate(text_request).await;
97 + let stream = stream.map_err(text_error_to_status)?;
98 +
99 + let (tx, rx) = mpsc::channel(32);
100 +
101 + tokio::spawn(async move {
102 + futures::pin_mut!(stream);
103 + while let Some(event) = stream.next().await {
104 + let response = match event {
105 + Err(e) => Err(text_error_to_status(e)),
106 + Ok(DecodedTextEvent::Start {
107 + prompt_token_ids,
108 + prompt_logprobs,
109 + }) => {
110 + let prompt_info = convert::to_prompt_info(
111 + &prompt_token_ids,
112 + prompt_logprobs.as_ref(),
113 + &response_opts,
114 + );
115 + Ok(pb::GenerateResponse {
116 + prompt_info: Some(prompt_info),
117 + outputs: None,
118 + })
119 + }
120 + Ok(DecodedTextEvent::TextDelta {
121 + delta,
122 + token_ids,
123 + logprobs,
124 + finished,
125 + }) => Ok(pb::GenerateResponse {
126 + prompt_info: None,
127 + outputs: Some(convert::to_sequence_output(
128 + &delta,
129 + &token_ids,
130 + logprobs.as_ref(),
131 + finished.as_ref(),
132 + &response_opts,
133 + )),
134 + }),
135 + };
136 +
137 + if tx.send(response).await.is_err() {
138 + // Client disconnected.
139 + break;
140 + }
141 + }
142 + });
143 +
144 + let response_stream = ReceiverStream::new(rx);
145 + Ok(Response::new(Box::pin(response_stream)))
146 + }
147 +}
148 +
149 +fn text_error_to_status(error: vllm_text::Error) -> Status {
150 + let message = error.to_report_string();
151 + if error.is_request_validation_error() {
152 + Status::invalid_argument(message)
153 + } else {
154 + Status::internal(message)
155 + }
156 +}
rust/src/server/src/grpc/mod.rs
+8 -186
@@ -1,203 +1,25 @@
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 -//! gRPC Generate service backed by the shared [`vllm_text::TextLlm`] facade.
4 +//! gRPC services backed by the shared application state.
5
6 +mod control;
7 mod convert;
8 mod health;
8 -
9 -use std::pin::Pin;
10 -use std::sync::Arc;
11 -
12 -use futures::{Stream, StreamExt as _};
13 -use thiserror_ext::AsReport as _;
14 -use tokio::sync::mpsc;
15 -use tokio_stream::wrappers::ReceiverStream;
16 -use tonic::{Request, Response, Status};
17 -use tracing::info;
18 -use vllm_text::{DecodedTextEvent, TextOutputStreamExt as _};
19 -
20 -use self::convert::ResponseOpts;
21 -use crate::state::AppState;
9 +mod inference;
10
11 /// Generated protobuf/gRPC types for the `vllm` package.
12 pub mod pb {
13 tonic::include_proto!("vllm");
14 }
15
16 +pub(crate) use control::ControlGrpcService;
17 +pub use control::ControlServiceImpl;
18 pub(crate) use health::monitor_health;
19 +pub(crate) use inference::InferenceGrpcService;
20 +pub use inference::InferenceServiceImpl;
21 pub use pb::control_server::ControlServer;
30 -pub use pb::generate_server::GenerateServer;
31 -
32 -pub(crate) type ControlGrpcService = ControlServer<ControlServiceImpl>;
33 -pub(crate) type GenerateGrpcService = GenerateServer<GenerateServiceImpl>;
22 +pub use pb::inference_server::InferenceServer;
23
24 #[cfg(test)]
25 mod tests;
37 -
38 -/// gRPC Generate service implementation backed by the shared application state.
39 -pub struct GenerateServiceImpl {
40 - state: Arc<AppState>,
41 -}
42 -
43 -impl GenerateServiceImpl {
44 - pub fn new(state: Arc<AppState>) -> Self {
45 - Self { state }
46 - }
47 -}
48 -
49 -/// gRPC control service backed by the shared application state.
50 -pub struct ControlServiceImpl {
51 - state: Arc<AppState>,
52 -}
53 -
54 -impl ControlServiceImpl {
55 - pub fn new(state: Arc<AppState>) -> Self {
56 - Self { state }
57 - }
58 -}
59 -
60 -#[tonic::async_trait]
61 -impl pb::control_server::Control for ControlServiceImpl {
62 - async fn abort(
63 - &self,
64 - request: Request<pb::AbortRequest>,
65 - ) -> Result<Response<pb::AbortResponse>, Status> {
66 - let request_ids = request.into_inner().request_ids;
67 - if request_ids.is_empty() {
68 - return Ok(Response::new(pb::AbortResponse {}));
69 - }
70 - self.state
71 - .chat
72 - .abort(&request_ids)
73 - .await
74 - .map_err(|error| Status::internal(error.to_report_string()))?;
75 - Ok(Response::new(pb::AbortResponse {}))
76 - }
77 -}
78 -
79 -#[tonic::async_trait]
80 -impl pb::generate_server::Generate for GenerateServiceImpl {
81 - type GenerateStreamStream =
82 - Pin<Box<dyn Stream<Item = Result<pb::GenerateResponse, Status>> + Send>>;
83 -
84 - /// Unary generate: collect all output and return a single response.
85 - async fn generate(
86 - &self,
87 - request: Request<pb::GenerateRequest>,
88 - ) -> Result<Response<pb::GenerateResponse>, Status> {
89 - let proto_req = request.into_inner();
90 - let response_opts = ResponseOpts::from_proto(proto_req.response.as_ref());
91 - let text_request =
92 - convert::to_text_request(proto_req, false, self.state.served_model_names())?;
93 -
94 - let request_id = text_request.request_id.clone();
95 - info!(%request_id, "grpc generate (unary)");
96 -
97 - let stream = self.state.chat.text().generate(text_request).await;
98 - let stream = stream.map_err(text_error_to_status)?;
99 -
100 - let collected = stream.collect_output().await.map_err(text_error_to_status)?;
101 -
102 - // Build the single aggregated response.
103 - let prompt_info = convert::to_prompt_info(
104 - &collected.prompt_token_ids,
105 - collected.prompt_logprobs.as_ref(),
106 - &response_opts,
107 - );
108 -
109 - let finish_info = vllm_text::Finished {
110 - usage: collected.usage,
111 - finish_reason: collected.finish_reason,
112 - kv_transfer_params: collected.kv_transfer_params,
113 - ec_transfer_params: collected.ec_transfer_params,
114 - };
115 -
116 - let outputs = convert::to_sequence_output(
117 - &collected.text,
118 - &collected.token_ids,
119 - collected.logprobs.as_ref(),
120 - Some(&finish_info),
121 - &response_opts,
122 - );
123 -
124 - Ok(Response::new(pb::GenerateResponse {
125 - prompt_info: Some(prompt_info),
126 - outputs: Some(outputs),
127 - }))
128 - }
129 -
130 - /// Streaming generate: yield incremental responses as tokens are produced.
131 - async fn generate_stream(
132 - &self,
133 - request: Request<pb::GenerateRequest>,
134 - ) -> Result<Response<Self::GenerateStreamStream>, Status> {
135 - let proto_req = request.into_inner();
136 - let response_opts = ResponseOpts::from_proto(proto_req.response.as_ref());
137 - let text_request =
138 - convert::to_text_request(proto_req, true, self.state.served_model_names())?;
139 -
140 - let request_id = text_request.request_id.clone();
141 - info!(%request_id, "grpc generate (stream)");
142 -
143 - let stream = self.state.chat.text().generate(text_request).await;
144 - let stream = stream.map_err(text_error_to_status)?;
145 -
146 - let (tx, rx) = mpsc::channel(32);
147 -
148 - tokio::spawn(async move {
149 - futures::pin_mut!(stream);
150 - while let Some(event) = stream.next().await {
151 - let response = match event {
152 - Err(e) => Err(text_error_to_status(e)),
153 - Ok(DecodedTextEvent::Start {
154 - prompt_token_ids,
155 - prompt_logprobs,
156 - }) => {
157 - let prompt_info = convert::to_prompt_info(
158 - &prompt_token_ids,
159 - prompt_logprobs.as_ref(),
160 - &response_opts,
161 - );
162 - Ok(pb::GenerateResponse {
163 - prompt_info: Some(prompt_info),
164 - outputs: None,
165 - })
166 - }
167 - Ok(DecodedTextEvent::TextDelta {
168 - delta,
169 - token_ids,
170 - logprobs,
171 - finished,
172 - }) => Ok(pb::GenerateResponse {
173 - prompt_info: None,
174 - outputs: Some(convert::to_sequence_output(
175 - &delta,
176 - &token_ids,
177 - logprobs.as_ref(),
178 - finished.as_ref(),
179 - &response_opts,
180 - )),
181 - }),
182 - };
183 -
184 - if tx.send(response).await.is_err() {
185 - // Client disconnected.
186 - break;
187 - }
188 - }
189 - });
190 -
191 - let response_stream = ReceiverStream::new(rx);
192 - Ok(Response::new(Box::pin(response_stream)))
193 - }
194 -}
195 -
196 -fn text_error_to_status(error: vllm_text::Error) -> Status {
197 - let message = error.to_report_string();
198 - if error.is_request_validation_error() {
199 - Status::invalid_argument(message)
200 - } else {
201 - Status::internal(message)
202 - }
203 -}
rust/src/server/src/grpc/tests.rs
+157 -38
@@ -25,12 +25,18 @@ use vllm_chat::{
25 ChatBackend, ChatLlm, ChatRenderer, ChatRequest, ChatTextBackend, DefaultChatOutputProcessor,
26 DynChatOutputProcessor, DynChatRenderer, NewChatOutputProcessorOptions, RenderedPrompt,
27 };
28 +use vllm_engine_core_client::mock_engine::{
29 + DEFAULT_MOCK_BLOCK_SIZE, DEFAULT_MOCK_MAX_MODEL_LEN, DEFAULT_MOCK_NUM_GPU_BLOCKS,
30 + default_ready_response,
31 +};
32 use vllm_engine_core_client::protocol::output::{
33 EngineCoreFinishReason, EngineCoreOutput, EngineCoreOutputs, RequestBatchOutputs,
34 };
35 use vllm_engine_core_client::protocol::request::EngineCoreRequest;
32 -use vllm_engine_core_client::test_utils::{IpcNamespace, spawn_mock_engine_task};
33 -use vllm_engine_core_client::{EngineCoreClient, EngineCoreClientConfig, EngineId};
36 +use vllm_engine_core_client::test_utils::{
37 + IpcNamespace, spawn_mock_engine_task, spawn_mock_engine_task_with_ready,
38 +};
39 +use vllm_engine_core_client::{EngineCoreClient, EngineCoreClientConfig, EngineId, TransportMode};
40 use vllm_llm::Llm;
41 use vllm_text::tokenizer::DynTokenizer;
42 use vllm_text::{Prompt, TextBackend};
@@ -39,8 +45,8 @@ use zeromq::prelude::{SocketRecv, SocketSend};
45 use zeromq::{DealerSocket, PushSocket, ZmqMessage};
46
47 use super::pb::control_client::ControlClient;
42 -use super::pb::generate_client::GenerateClient;
43 -use super::{ControlServer, ControlServiceImpl, GenerateServer, GenerateServiceImpl, pb};
48 +use super::pb::inference_client::InferenceClient;
49 +use super::{ControlServer, ControlServiceImpl, InferenceServer, InferenceServiceImpl, pb};
50 use crate::listener::{Listener, MaybeTlsListener};
51 use crate::state::AppState;
52 use crate::tls;
@@ -202,7 +208,7 @@ async fn setup_grpc_service(
208 engine_id: impl Into<EngineId>,
209 output_specs: Vec<(Vec<u32>, Option<EngineCoreFinishReason>)>,
210 ) -> (
205 - GenerateServer<GenerateServiceImpl>,
211 + InferenceServer<InferenceServiceImpl>,
212 ControlServer<ControlServiceImpl>,
213 tokio::sync::watch::Receiver<bool>,
214 MockEngineTask,
@@ -246,7 +252,7 @@ async fn setup_grpc_service(
252 );
253 let state = Arc::new(AppState::new(vec!["test-model".to_string()], chat));
254 (
249 - GenerateServer::new(GenerateServiceImpl::new(state.clone())),
255 + InferenceServer::new(InferenceServiceImpl::new(state.clone())),
256 ControlServer::new(ControlServiceImpl::new(state)),
257 engine_health,
258 engine_task,
@@ -259,30 +265,30 @@ async fn grpc_test_server(
265 engine_id: impl Into<EngineId>,
266 output_specs: Vec<(Vec<u32>, Option<EngineCoreFinishReason>)>,
267 ) -> (
262 - GenerateClient<tonic::transport::Channel>,
268 + InferenceClient<tonic::transport::Channel>,
269 tokio::task::JoinHandle<()>,
270 MockEngineTask,
271 ) {
266 - let (generate_service, control_service, engine_health, engine_task) =
272 + let (inference_service, control_service, engine_health, engine_task) =
273 setup_grpc_service(engine_id, output_specs).await;
274 let (channel, server_task) = start_grpc_test_server(
269 - generate_service,
275 + inference_service,
276 control_service,
277 engine_health,
278 tokio_util::sync::CancellationToken::new(),
279 )
280 .await;
275 - (GenerateClient::new(channel), server_task, engine_task)
281 + (InferenceClient::new(channel), server_task, engine_task)
282 }
283
284 async fn start_grpc_test_server(
279 - generate_service: GenerateServer<GenerateServiceImpl>,
285 + inference_service: InferenceServer<InferenceServiceImpl>,
286 control_service: ControlServer<ControlServiceImpl>,
287 engine_health: tokio::sync::watch::Receiver<bool>,
288 shutdown: tokio_util::sync::CancellationToken,
289 ) -> (Channel, tokio::task::JoinHandle<()>) {
290 let (health_reporter, health_service) = health_reporter();
285 - health_reporter.set_serving::<GenerateServer<GenerateServiceImpl>>().await;
291 + health_reporter.set_serving::<InferenceServer<InferenceServiceImpl>>().await;
292 health_reporter.set_serving::<ControlServer<ControlServiceImpl>>().await;
293
294 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("bind grpc listener");
@@ -293,7 +299,7 @@ async fn start_grpc_test_server(
299 let server = TonicServer::builder()
300 .add_service(health_service)
301 .add_service(control_service)
296 - .add_service(generate_service)
302 + .add_service(inference_service)
303 .serve_with_incoming_shutdown(incoming, shutdown.clone().cancelled_owned());
304 let health_monitor =
305 super::monitor_health(health_reporter, engine_health, shutdown.clone());
@@ -323,7 +329,7 @@ async fn grpc_tls_test_server(
329 certs: &TestCerts,
330 cert_reqs: i32,
331 ) -> (String, tokio::task::JoinHandle<()>, MockEngineTask) {
326 - let (generate_service, control_service, _engine_health, engine_task) =
332 + let (inference_service, control_service, _engine_health, engine_task) =
333 setup_grpc_service(engine_id, output_specs).await;
334 let context = tls::build_grpc_server_config(&server_tls(certs, cert_reqs))
335 .expect("build grpc tls config");
@@ -335,7 +341,7 @@ async fn grpc_tls_test_server(
341 let incoming = MaybeTlsListener::tls(Listener::Tcp(listener), context);
342 TonicServer::builder()
343 .add_service(control_service)
338 - .add_service(generate_service)
344 + .add_service(inference_service)
345 .serve_with_incoming(incoming)
346 .await
347 .expect("grpc tls server");
@@ -351,7 +357,7 @@ async fn grpc_tls_client(
357 certs: &TestCerts,
358 addr: &str,
359 identity: Option<&str>,
354 -) -> Result<GenerateClient<Channel>, tonic::transport::Error> {
360 +) -> Result<InferenceClient<Channel>, tonic::transport::Error> {
361 let ca = certs.path("ca.pem");
362 let identity = identity.map(|name| {
363 (
@@ -388,7 +394,7 @@ async fn grpc_tls_client(
394 .expect("grpc endpoint")
395 .connect_with_connector(connector)
396 .await?;
391 - Ok(GenerateClient::new(channel))
397 + Ok(InferenceClient::new(channel))
398 }
399
400 /// Complete a raw TLS handshake against the gRPC port (offering ALPN `h2`) for
@@ -415,7 +421,7 @@ async fn grpc_server_with_keepalive(
421 engine_id: impl Into<EngineId>,
422 keepalive: Option<Duration>,
423 ) -> (String, tokio::task::JoinHandle<()>, MockEngineTask) {
418 - let (generate_service, control_service, _engine_health, engine_task) =
424 + let (inference_service, control_service, _engine_health, engine_task) =
425 setup_grpc_service(engine_id, default_stream_output_specs()).await;
426
427 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("bind grpc listener");
@@ -432,7 +438,7 @@ async fn grpc_server_with_keepalive(
438 let incoming = MaybeTlsListener::plain(Listener::Tcp(listener));
439 builder
440 .add_service(control_service)
435 - .add_service(generate_service)
441 + .add_service(inference_service)
442 .serve_with_incoming(incoming)
443 .await
444 .expect("grpc server");
@@ -1083,20 +1089,20 @@ async fn grpc_without_keepalive_keeps_unresponsive_connection_open() {
1089 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1090 #[serial]
1091 async fn control_abort_resolves_external_id_and_empty_is_noop() {
1086 - let (generate_service, control_service, engine_health, engine_task) =
1092 + let (inference_service, control_service, engine_health, engine_task) =
1093 setup_grpc_service(b"engine-grpc-abort-active", vec![(vec![b'h' as u32], None)]).await;
1094 let (channel, server_task) = start_grpc_test_server(
1089 - generate_service,
1095 + inference_service,
1096 control_service,
1097 engine_health,
1098 tokio_util::sync::CancellationToken::new(),
1099 )
1100 .await;
1095 - let mut generate_client = GenerateClient::new(channel.clone());
1101 + let mut inference_client = InferenceClient::new(channel.clone());
1102 let mut control_client = ControlClient::new(channel);
1103 let request_id = "test-abort-active";
1104
1099 - let mut stream = generate_client
1105 + let mut stream = inference_client
1106 .generate_stream(pb::GenerateRequest {
1107 request_id: request_id.to_string(),
1108 model: "test-model".to_string(),
@@ -1171,14 +1177,127 @@ async fn control_abort_resolves_external_id_and_empty_is_noop() {
1177 server_task.abort();
1178 }
1179
1180 +#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1181 +#[serial]
1182 +async fn control_reports_server_and_model_info() {
1183 + let (generate_service, control_service, engine_health, _engine_task) =
1184 + setup_grpc_service(b"engine-grpc-info", default_stream_output_specs()).await;
1185 + let (channel, server_task) = start_grpc_test_server(
1186 + generate_service,
1187 + control_service,
1188 + engine_health,
1189 + tokio_util::sync::CancellationToken::new(),
1190 + )
1191 + .await;
1192 + let mut client = ControlClient::new(channel);
1193 +
1194 + let server = client
1195 + .get_server_info(pb::GetServerInfoRequest {})
1196 + .await
1197 + .expect("get server info")
1198 + .into_inner();
1199 + assert_eq!(server.engine_version, "test-vllm-version");
1200 + assert_eq!(server.api_version, "vllm");
1201 + assert_eq!(server.instance_id, "test-instance");
1202 + assert_eq!(server.max_model_len, DEFAULT_MOCK_MAX_MODEL_LEN as u32);
1203 + assert_eq!(server.kv_block_size, DEFAULT_MOCK_BLOCK_SIZE as u32);
1204 + assert_eq!(server.total_kv_blocks, DEFAULT_MOCK_NUM_GPU_BLOCKS);
1205 + assert_eq!(server.max_running_requests, 256);
1206 + assert_eq!(server.max_batched_tokens, 8_192);
1207 + let parallelism = server.parallelism.expect("parallelism metadata");
1208 + assert_eq!(parallelism.tensor_parallel_size, 1);
1209 + assert_eq!(parallelism.pipeline_parallel_size, 1);
1210 + assert_eq!(parallelism.data_parallel_size, 1);
1211 + assert_eq!(parallelism.data_parallel_rank, 0);
1212 + assert_eq!(parallelism.decode_context_parallel_size, 1);
1213 +
1214 + let model = client
1215 + .get_model_info(pb::GetModelInfoRequest {})
1216 + .await
1217 + .expect("get model info")
1218 + .into_inner();
1219 + assert_eq!(model.model_id, "test-model");
1220 + assert_eq!(model.served_model_name, "test-model");
1221 + assert!(model.served_model_aliases.is_empty());
1222 + assert!(model.supports_text_input);
1223 + assert!(model.supports_token_ids_input);
1224 + assert!(!model.supports_multimodal);
1225 + assert!(model.reasoning_parser.is_empty());
1226 + assert!(model.tool_call_parser.is_empty());
1227 +
1228 + server_task.abort();
1229 +}
1230 +
1231 +#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1232 +async fn control_aggregates_multi_engine_capacity() {
1233 + let ipc = IpcNamespace::new().expect("create ipc namespace");
1234 + let handshake_address = ipc.handshake_endpoint();
1235 +
1236 + let mut ready_0 = default_ready_response();
1237 + ready_0.max_model_len = 8_192;
1238 + ready_0.num_gpu_blocks = 10;
1239 + ready_0.data_parallel_size = 2;
1240 +
1241 + let mut ready_1 = default_ready_response();
1242 + ready_1.max_model_len = 4_096;
1243 + ready_1.num_gpu_blocks = 20;
1244 + ready_1.data_parallel_size = 2;
1245 + ready_1.data_parallel_rank = 1;
1246 +
1247 + let engine_tasks = [ready_0, ready_1].map(|ready| {
1248 + let engine_id = EngineId::from_engine_index(ready.data_parallel_rank);
1249 + MockEngineTask::new(spawn_mock_engine_task_with_ready(
1250 + handshake_address.clone(),
1251 + engine_id,
1252 + ready,
1253 + |_, _| boxed_test_future(async {}),
1254 + ))
1255 + });
1256 +
1257 + let client = EngineCoreClient::connect(EngineCoreClientConfig {
1258 + transport_mode: TransportMode::HandshakeOwner {
1259 + handshake_address,
1260 + advertised_host: "127.0.0.1".to_string(),
1261 + engine_count: 2,
1262 + ready_timeout: Duration::from_secs(2),
1263 + local_input_address: Some(ipc.input_endpoint()),
1264 + local_output_address: Some(ipc.output_endpoint()),
1265 + },
1266 + coordinator_mode: None,
1267 + model_name: "test-model".to_string(),
1268 + client_index: 0,
1269 + })
1270 + .await
1271 + .expect("connect multi-engine client");
1272 + let chat = ChatLlm::from_shared_backend(
1273 + Llm::new(client),
1274 + Arc::new(FakeTextBackend) as Arc<dyn ChatTextBackend>,
1275 + );
1276 + let service = ControlServiceImpl::new(Arc::new(AppState::new(
1277 + vec!["test-model".to_string()],
1278 + chat,
1279 + )));
1280 +
1281 + let server = pb::control_server::Control::get_server_info(
1282 + &service,
1283 + tonic::Request::new(pb::GetServerInfoRequest {}),
1284 + )
1285 + .await
1286 + .expect("get server info")
1287 + .into_inner();
1288 + assert_eq!(server.max_model_len, 4_096);
1289 + assert_eq!(server.total_kv_blocks, 30);
1290 +
1291 + drop(engine_tasks);
1292 +}
1293 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1294 #[serial]
1295 async fn grpc_health_transitions_to_not_serving_when_engine_becomes_unhealthy() {
1177 - let (generate_service, control_service, _connected_engine_health, _engine_task) =
1296 + let (inference_service, control_service, _connected_engine_health, _engine_task) =
1297 setup_grpc_service(b"engine-grpc-health-failure", default_stream_output_specs()).await;
1298 let (engine_health_tx, engine_health) = tokio::sync::watch::channel(true);
1299 let (channel, server_task) = start_grpc_test_server(
1181 - generate_service,
1300 + inference_service,
1301 control_service,
1302 engine_health,
1303 tokio_util::sync::CancellationToken::new(),
@@ -1187,7 +1306,7 @@ async fn grpc_health_transitions_to_not_serving_when_engine_becomes_unhealthy()
1306 let mut health_client = HealthClient::new(channel);
1307
1308 let mut health_streams = Vec::new();
1190 - for service in ["vllm.Generate", "vllm.Control", ""] {
1309 + for service in ["vllm.Inference", "vllm.Control", ""] {
1310 let service_label = if service.is_empty() {
1311 "overall"
1312 } else {
@@ -1242,14 +1361,14 @@ async fn grpc_health_transitions_to_not_serving_when_engine_becomes_unhealthy()
1361 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1362 #[serial]
1363 async fn grpc_health_watch_closes_on_graceful_shutdown() {
1245 - let (generate_service, control_service, engine_health, _engine_task) = setup_grpc_service(
1364 + let (inference_service, control_service, engine_health, _engine_task) = setup_grpc_service(
1365 b"engine-grpc-health-shutdown",
1366 default_stream_output_specs(),
1367 )
1368 .await;
1369 let shutdown = tokio_util::sync::CancellationToken::new();
1370 let (channel, server_task) = start_grpc_test_server(
1252 - generate_service,
1371 + inference_service,
1372 control_service,
1373 engine_health,
1374 shutdown.clone(),
@@ -1258,43 +1377,43 @@ async fn grpc_health_watch_closes_on_graceful_shutdown() {
1377 let mut health_client = HealthClient::new(channel);
1378 let mut stream = health_client
1379 .watch(HealthCheckRequest {
1261 - service: "vllm.Generate".to_string(),
1380 + service: "vllm.Inference".to_string(),
1381 })
1382 .await
1264 - .expect("start health watch for vllm.Generate")
1383 + .expect("start health watch for vllm.Inference")
1384 .into_inner();
1385
1386 let initial = stream
1387 .message()
1388 .await
1270 - .expect("read initial health status for vllm.Generate")
1389 + .expect("read initial health status for vllm.Inference")
1390 .expect("health watch ended before its initial status");
1391 assert_eq!(
1392 initial.status,
1393 HealthServingStatus::Serving as i32,
1275 - "unexpected initial health status for vllm.Generate"
1394 + "unexpected initial health status for vllm.Inference"
1395 );
1396
1397 shutdown.cancel();
1398
1399 let update = tokio::time::timeout(Duration::from_secs(2), stream.message())
1400 .await
1282 - .expect("timed out waiting for shutdown health update for vllm.Generate")
1283 - .expect("failed to read shutdown health update for vllm.Generate")
1401 + .expect("timed out waiting for shutdown health update for vllm.Inference")
1402 + .expect("failed to read shutdown health update for vllm.Inference")
1403 .expect("health watch ended before its shutdown update");
1404 assert_eq!(
1405 update.status,
1406 HealthServingStatus::NotServing as i32,
1288 - "unexpected shutdown health status for vllm.Generate"
1407 + "unexpected shutdown health status for vllm.Inference"
1408 );
1409
1410 let stream_end = tokio::time::timeout(Duration::from_secs(2), stream.message())
1411 .await
1293 - .expect("timed out waiting for vllm.Generate health watch to close")
1294 - .expect("failed while closing vllm.Generate health watch");
1412 + .expect("timed out waiting for vllm.Inference health watch to close")
1413 + .expect("failed while closing vllm.Inference health watch");
1414 assert!(
1415 stream_end.is_none(),
1297 - "vllm.Generate health watch remained open"
1416 + "vllm.Inference health watch remained open"
1417 );
1418
1419 tokio::time::timeout(Duration::from_secs(2), server_task)
rust/src/server/src/lib.rs
+5 -5
@@ -187,7 +187,7 @@ where
187 let model = state.primary_model_name().to_owned();
188 let app = extend_router(build_router(state.clone()));
189
190 - // Optionally bind the gRPC Generate server on a separate port. Bind
190 + // Optionally bind the gRPC Inference server on a separate port. Bind
191 // synchronously here so bind errors (port in use, permission denied, ...)
192 // surface before serving rather than being deferred until shutdown.
193 let grpc_setup = if let Some(grpc_port) = config.grpc_port {
@@ -206,19 +206,19 @@ where
206 .context("invalid gRPC TLS configuration")?;
207 let (health_reporter, health_service) = health_reporter();
208 let engine_health = state.engine_core_client().subscribe_health();
209 - health_reporter.set_serving::<grpc::GenerateGrpcService>().await;
209 + health_reporter.set_serving::<grpc::InferenceGrpcService>().await;
210 health_reporter.set_serving::<grpc::ControlGrpcService>().await;
211 let control_service =
212 grpc::ControlGrpcService::new(grpc::ControlServiceImpl::new(state.clone()));
213 - let generate_service =
214 - grpc::GenerateGrpcService::new(grpc::GenerateServiceImpl::new(state.clone()));
213 + let inference_service =
214 + grpc::InferenceGrpcService::new(grpc::InferenceServiceImpl::new(state.clone()));
215 let svc = TonicServer::builder()
216 .http2_keepalive_interval(Some(GRPC_KEEPALIVE_INTERVAL))
217 .http2_keepalive_timeout(Some(GRPC_KEEPALIVE_TIMEOUT))
218 .layer(middleware::request_runtime_layer(state.clone()))
219 .add_service(health_service)
220 .add_service(control_service)
221 - .add_service(generate_service);
221 + .add_service(inference_service);
222 info!(%addr, tls = grpc_tls.is_some(), "starting gRPC server");
223 Some((grpc_listener, svc, grpc_tls, health_reporter, engine_health))
224 } else {
rust/src/server/src/middleware/offload.rs
+4 -4
@@ -30,8 +30,8 @@ const OFFLOADED_PATHS: &[&str] = &[
30 "/detokenize",
31 "/inference/v1/generate",
32 // gRPC routes:
33 - "/vllm.Generate/Generate",
34 - "/vllm.Generate/GenerateStream",
33 + "/vllm.Inference/Generate",
34 + "/vllm.Inference/GenerateStream",
35 ];
36
37 /// Return a Tower layer that runs selected data-plane requests on the request runtime,
@@ -124,8 +124,8 @@ mod tests {
124 assert!(should_offload("/tokenize"));
125 assert!(should_offload("/detokenize"));
126 assert!(should_offload("/inference/v1/generate"));
127 - assert!(should_offload("/vllm.Generate/Generate"));
128 - assert!(should_offload("/vllm.Generate/GenerateStream"));
127 + assert!(should_offload("/vllm.Inference/Generate"));
128 + assert!(should_offload("/vllm.Inference/GenerateStream"));
129 }
130
131 #[test]
tests/v1/engine/test_engine_core_client.py
+7
@@ -328,6 +328,13 @@ def test_apply_ready_response_syncs_block_size():
328 vllm_version="test",
329 world_size=1,
330 data_parallel_size=1,
331 + tensor_parallel_size=1,
332 + pipeline_parallel_size=1,
333 + decode_context_parallel_size=1,
334 + data_parallel_rank=0,
335 + max_num_seqs=256,
336 + max_num_batched_tokens=8192,
337 + instance_id="test-instance",
338 )
339 )
340 client._apply_ready_response(payload)
vllm/v1/engine/__init__.py
+7
@@ -82,6 +82,13 @@ class EngineCoreReadyResponse:
82 vllm_version: str
83 world_size: int
84 data_parallel_size: int
85 + tensor_parallel_size: int
86 + pipeline_parallel_size: int
87 + decode_context_parallel_size: int
88 + data_parallel_rank: int
89 + max_num_seqs: int
90 + max_num_batched_tokens: int
91 + instance_id: str
92 # KV cache capacity (None for encoder-only/attention-free models).
93 kv_cache_size_tokens: int | None = None
94 kv_cache_max_concurrency: float | None = None
vllm/v1/engine/core.py
+26 -16
@@ -1613,6 +1613,31 @@ class EngineCoreProc(EngineCore):
1613 "to send. Please report this issue."
1614 )
1615
1616 + def _make_ready_response(self) -> EngineCoreReadyResponse:
1617 + parallel_config = self.vllm_config.parallel_config
1618 + scheduler_config = self.vllm_config.scheduler_config
1619 + return EngineCoreReadyResponse(
1620 + max_model_len=self.vllm_config.model_config.max_model_len,
1621 + num_gpu_blocks=self.vllm_config.cache_config.num_gpu_blocks or 0,
1622 + block_size=self.vllm_config.cache_config.block_size,
1623 + dp_stats_address=self.frontend_stats_publish_address,
1624 + dtype=str(self.vllm_config.model_config.dtype).removeprefix("torch."),
1625 + vllm_version=VLLM_VERSION,
1626 + world_size=self.vllm_config.parallel_config.world_size,
1627 + data_parallel_size=parallel_config.data_parallel_size,
1628 + kv_cache_size_tokens=self.vllm_config.cache_config.kv_cache_size_tokens,
1629 + kv_cache_max_concurrency=(
1630 + self.vllm_config.cache_config.kv_cache_max_concurrency
1631 + ),
1632 + tensor_parallel_size=parallel_config.tensor_parallel_size,
1633 + pipeline_parallel_size=parallel_config.pipeline_parallel_size,
1634 + decode_context_parallel_size=parallel_config.decode_context_parallel_size,
1635 + data_parallel_rank=self.engine_index,
1636 + max_num_seqs=scheduler_config.max_num_seqs,
1637 + max_num_batched_tokens=scheduler_config.max_num_batched_tokens,
1638 + instance_id=self.vllm_config.instance_id,
1639 + )
1640 +
1641 def process_input_sockets(
1642 self,
1643 input_addresses: list[str],
@@ -1654,22 +1679,7 @@ class EngineCoreProc(EngineCore):
1679
1680 # Register sockets with poller.
1681 poller = zmq.Poller()
1657 - ready_response = EngineCoreReadyResponse(
1658 - max_model_len=self.vllm_config.model_config.max_model_len,
1659 - num_gpu_blocks=self.vllm_config.cache_config.num_gpu_blocks or 0,
1660 - block_size=self.vllm_config.cache_config.block_size,
1661 - dp_stats_address=self.frontend_stats_publish_address,
1662 - dtype=str(self.vllm_config.model_config.dtype).removeprefix("torch."),
1663 - vllm_version=VLLM_VERSION,
1664 - world_size=self.vllm_config.parallel_config.world_size,
1665 - data_parallel_size=self.vllm_config.parallel_config.data_parallel_size,
1666 - kv_cache_size_tokens=(
1667 - self.vllm_config.cache_config.kv_cache_size_tokens
1668 - ),
1669 - kv_cache_max_concurrency=(
1670 - self.vllm_config.cache_config.kv_cache_max_concurrency
1671 - ),
1672 - )
1682 + ready_response = self._make_ready_response()
1683 ready_payload = msgspec.msgpack.encode(ready_response)
1684 for input_socket in input_sockets:
1685 # Send initial message to each input socket - this is required