Skip to main content

ts_wire/
handshake.rs

1// Copyright (c) 2026 LightSeek Foundation
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! The startup handshake (`engine/zmq_wire.py`).
22//!
23//! The frontend binds a ROUTER handshake socket, a ROUTER input socket and a
24//! PULL output socket. Each engine (one per attention-DP rank) connects with
25//! its two-byte little-endian engine index as its identity and:
26//!
27//! 1. sends HELLO ([`ReadyMessage`]) on the handshake socket;
28//! 2. receives INIT ([`HandshakeInitMessage`]) with the input and output
29//!    addresses;
30//! 3. sends READY on the handshake socket;
31//! 4. connects its input DEALER and registers on it with
32//!    [`EngineCoreReadyResponse`];
33//! 5. connects its output PUSH.
34//!
35//! These structs use the map layout, matching the frontend's named codec.
36
37use crate::{codec::Dict, value::Value};
38
39/// HELLO status.
40pub const STATUS_HELLO: &str = "HELLO";
41/// READY status.
42pub const STATUS_READY: &str = "READY";
43
44wire_struct! {
45    /// Engine to frontend handshake status (HELLO or READY).
46    #[derive(Debug, Clone, PartialEq, Eq)]
47    pub struct ReadyMessage: map + Default {
48        /// `HELLO` or `READY`.
49        pub status: Option<String> = None,
50        /// The engine runs on the frontend's host.
51        pub local: Option<bool> = None,
52        /// The engine runs without an in-process frontend.
53        pub headless: Option<bool> = None,
54        /// Hash of the engine's parallel configuration.
55        pub parallel_config_hash: Option<String> = None,
56    }
57}
58
59wire_struct! {
60    /// Frontend-owned socket addresses sent in INIT.
61    #[derive(Debug, Clone, PartialEq, Eq)]
62    pub struct HandshakeAddresses: map + Default {
63        /// Input ROUTER addresses; engines use the first.
64        pub inputs: Vec<String> = Vec::new(),
65        /// Output PULL addresses; engines use the first.
66        pub outputs: Vec<String> = Vec::new(),
67        /// DP coordinator input, unused by TokenSpeed.
68        pub coordinator_input: Option<String> = None,
69        /// DP coordinator output, unused by TokenSpeed.
70        pub coordinator_output: Option<String> = None,
71        /// Frontend stats publisher, unused by TokenSpeed.
72        pub frontend_stats_publish_address: Option<String> = None,
73    }
74}
75
76wire_struct! {
77    /// Frontend to engine INIT, the reply to HELLO.
78    #[derive(Debug, Clone, PartialEq)]
79    pub struct HandshakeInitMessage: map {
80        /// Where to connect.
81        pub addresses: HandshakeAddresses,
82        /// Opaque to the engine.
83        pub parallel_config: Dict<Value, Value> = Dict::default(),
84    }
85}
86
87wire_struct! {
88    /// The engine's configuration, sent when it registers on the input socket.
89    ///
90    /// Field names, not order, are the contract; some names are the
91    /// frontend's vocabulary rather than TokenSpeed's.
92    #[derive(Debug, Clone, PartialEq)]
93    pub struct EngineCoreReadyResponse: map + Default {
94        /// Longest supported sequence.
95        pub max_model_len: i64 = 0,
96        /// KV pages on the device.
97        pub num_gpu_blocks: i64 = 0,
98        /// Prefix-cache matching granularity in tokens.
99        pub prefix_granularity: i64 = 0,
100        /// DP stats address, unused by TokenSpeed.
101        pub dp_stats_address: Option<String> = None,
102        /// Model dtype: `bfloat16`, `float16` or `float32`.
103        pub dtype: String = "bfloat16".to_owned(),
104        /// Multimodal encoder dtype, if any.
105        pub multimodal_encoder_dtype: Option<String> = None,
106        /// `tokenspeed-<version>`; the frontend's field name.
107        pub vllm_version: String = String::new(),
108        /// Ranks in the engine.
109        pub world_size: i64 = 1,
110        /// Attention-DP ranks.
111        pub data_parallel_size: i64 = 1,
112        /// Attention-TP ranks.
113        pub tensor_parallel_size: i64 = 1,
114        /// Pipeline stages.
115        pub pipeline_parallel_size: i64 = 1,
116        /// KV parallelism (`--kv-parallel-size`); the frontend's field name.
117        pub decode_context_parallel_size: i64 = 1,
118        /// This engine's attention-DP rank.
119        pub data_parallel_rank: i64 = 0,
120        /// Running-request limit.
121        pub max_num_seqs: i64 = 0,
122        /// Tokens per step limit; `0` is no cap.
123        pub max_num_batched_tokens: i64 = 0,
124        /// Served model name.
125        pub instance_id: String = String::new(),
126        /// KV cache capacity in tokens.
127        pub kv_cache_size_tokens: Option<i64> = None,
128        /// KV cache capacity in maximum-length requests.
129        pub kv_cache_max_concurrency: Option<f64> = None,
130        /// KV event publisher configuration.
131        pub kv_events_config: Option<Dict<Value, Value>> = None,
132    }
133}