aisimulate_core/engine/protocol.rs
1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Commands and effects exchanged with one scheduler rank.
5
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use super::{HandoffId, HandoffTransferTiming};
10
11/// Runtime-neutral request accepted by the rank engine.
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct Request {
14 /// Stable request identity allocated by the caller.
15 pub request_id: Uuid,
16 /// Prompt token IDs.
17 pub tokens: Vec<u32>,
18 /// Requested output length.
19 pub max_output_tokens: usize,
20 /// Optional exact output IDs. Its length overrides `max_output_tokens`.
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub output_token_ids: Option<Vec<u32>>,
23}
24
25/// Commands supported by the standalone scheduler.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum Command {
28 /// Submit one request.
29 Submit(Request),
30 /// Submit a request whose completed prefill KV must remain source-held.
31 SubmitHandoffPrefill {
32 handoff_id: HandoffId,
33 request: Request,
34 },
35 /// Accept a decode request and reserve its destination KV footprint.
36 ReserveDestination {
37 handoff_id: HandoffId,
38 request: Request,
39 },
40 /// Make a reserved destination runnable after transfer completion.
41 ActivateDestination { handoff_id: HandoffId },
42 /// Release a successfully transferred source hold.
43 ReleaseSource { handoff_id: HandoffId },
44 /// Cancel pending or held source ownership.
45 CancelSource { handoff_id: HandoffId },
46 /// Cancel pending, reserved, or active destination ownership.
47 CancelDestination { handoff_id: HandoffId },
48 /// Cancel one request.
49 ///
50 /// A command that removes scheduler-owned state also suppresses output
51 /// retained by an in-flight pass. `discard_pending_output` additionally
52 /// requests suppression when scheduler cancellation is a no-op, which is
53 /// needed after an external driver has already retired the request.
54 CancelRequest {
55 request_id: Uuid,
56 discard_pending_output: bool,
57 },
58}
59
60/// Result of applying a scheduler command.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum CommandResult {
63 /// A new request was accepted.
64 Submitted(Uuid),
65 /// A destination request was accepted; physical reservation may be pending.
66 DestinationAccepted { request_id: Uuid },
67 /// State or retained effects were changed.
68 Applied,
69 /// The command addressed no owned state or retained effect.
70 Noop,
71}
72
73/// Asynchronous scheduler lifecycle fact consumed by the Replayer's handoff
74/// coordinator.
75#[derive(Debug, Clone, Copy, PartialEq)]
76pub enum LifecycleEvent {
77 /// Prefill computation completed and source KV ownership is retained.
78 SourceHeld {
79 handoff_id: HandoffId,
80 request_id: Uuid,
81 transfer_timing: HandoffTransferTiming,
82 },
83 /// Decode-side physical KV capacity has been reserved.
84 DestinationReserved {
85 handoff_id: HandoffId,
86 request_id: Uuid,
87 transferable_prompt_tokens: usize,
88 },
89}
90
91/// One runtime-neutral KV block identity.
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93pub struct KvBlock {
94 /// Sequence-aware block hash.
95 pub block_hash: u64,
96 /// Token-only local block hash.
97 pub tokens_hash: u64,
98 /// Token IDs retained only when explicitly configured.
99 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub token_ids: Option<Vec<u32>>,
101}
102
103/// A consecutive set of newly visible blocks.
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
105pub struct StoredBlocks {
106 /// Parent sequence hash immediately preceding the batch.
107 pub parent_hash: Option<u64>,
108 /// Optional absolute zero-based position of the first block.
109 ///
110 /// `None` preserves the parent-linked stream emitted by the native
111 /// schedulers; adapters must not invent an absolute position because that
112 /// changes how downstream radix indexes reconcile stores and removals.
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub start_position: Option<usize>,
115 /// Blocks in sequence order.
116 pub blocks: Vec<KvBlock>,
117}
118
119/// Runtime-neutral KV event payload.
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(rename_all = "snake_case")]
122pub enum KvEventData {
123 /// Blocks became prefix-cache visible.
124 Stored(StoredBlocks),
125 /// The final physical copy of these hashes was evicted.
126 Removed { block_hashes: Vec<u64> },
127}
128
129/// Ordered runtime-neutral KV event emitted by one rank.
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub struct KvEvent {
132 /// Monotonic rank-local event sequence.
133 pub event_id: u64,
134 /// Attention-DP rank.
135 pub dp_rank: u32,
136 /// Event payload.
137 pub data: KvEventData,
138}
139
140/// First-admission provenance for prompt tokens reused across cache tiers.
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142#[non_exhaustive]
143pub struct CacheTierAttribution {
144 /// Reused prompt tokens already resident in device KV before any H2D.
145 pub g1_reused_input_tokens: usize,
146 /// Additional reused prompt tokens restored from the native host tier.
147 pub host_reused_input_tokens: usize,
148}
149
150/// Request admission exposed at pass start.
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub struct Admission {
153 /// Admitted request.
154 pub request_id: Uuid,
155 /// Prompt tokens reused from all KV-cache tiers.
156 pub reused_input_tokens: usize,
157 /// Tier provenance captured before a host-loaded prefix becomes G1.
158 pub cache_tier_attribution: Option<CacheTierAttribution>,
159}
160
161/// Scheduler action taken to relieve KV pressure.
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
163#[serde(rename_all = "snake_case")]
164pub enum PressureKind {
165 /// vLLM evicted a running request and returned it to the waiting queue.
166 VllmPreemption,
167 /// SGLang retracted a running decode request for later readmission.
168 SglangRetraction,
169}
170
171/// Runtime-neutral scheduler and KV occupancy around one pressure action.
172#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
173pub struct PressureState {
174 /// Requests runnable before or after the action.
175 pub running_requests: usize,
176 /// Requests waiting for admission, when the scheduler exposes that count.
177 pub waiting_requests: Option<usize>,
178 /// Physically active native-G1 blocks.
179 pub active_blocks: usize,
180}
181
182/// One scheduler-owned KV pressure action emitted with pass-start effects.
183///
184/// The Replayer may attach topology/pool identity and correlate a later
185/// [`Admission`] for the same request. The engine always produces this
186/// lightweight fact; capture policy belongs to the consuming runtime.
187#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188pub struct PressureEvent {
189 /// Modeled timestamp at which the scheduler took the action.
190 pub at_ms: f64,
191 /// Scheduler-specific pressure action.
192 pub kind: PressureKind,
193 /// Request removed from the running set.
194 pub request_id: Uuid,
195 /// Scheduler/KV state immediately before the action.
196 pub state_before: PressureState,
197 /// Scheduler/KV state immediately after the action.
198 pub state_after: PressureState,
199 /// Blocks owned by this request before it was preempted or retracted.
200 pub request_active_blocks_before: usize,
201 /// Logically available blocks used by the SGLang retraction decision.
202 pub logical_available_blocks_before: Option<usize>,
203 /// Blocks required by the SGLang retraction decision.
204 pub required_blocks_before: Option<usize>,
205}
206
207/// One client-visible output.
208#[derive(Debug, Clone, PartialEq)]
209pub struct Output {
210 /// Request producing the output.
211 pub request_id: Uuid,
212 /// Generated token, or `None` for a terminal-without-token signal.
213 pub token_id: Option<u32>,
214 /// Whether request ownership ended with this output.
215 pub completed: bool,
216 /// Whether admission rejected the request as physically impossible.
217 pub rejected: bool,
218 /// Prompt tokens served from KV cache at first admission, reported once
219 /// on the request's first output.
220 pub cached_tokens: Option<usize>,
221}
222
223/// Rank-local scheduler and G1 metrics.
224#[derive(Debug, Clone, Default, PartialEq)]
225pub struct Metrics {
226 pub dp_rank: u32,
227 /// Backend-native legacy occupied-block count. vLLM reports blocks
228 /// referenced by active requests; SGLang reports occupied page-pool blocks,
229 /// including evictable radix-resident pages.
230 pub active_blocks: u64,
231 /// Reusable resident blocks not included in `active_blocks`. This is
232 /// currently populated by vLLM; SGLang reports zero because its legacy
233 /// occupied count already includes radix-resident pages.
234 pub inactive_blocks: u64,
235 pub total_blocks: u64,
236 /// `active_blocks / total_blocks`, with backend-native semantics above.
237 pub cache_usage: f64,
238 /// Physical resident fraction. This includes inactive reusable vLLM blocks
239 /// and equals `cache_usage` for SGLang's legacy occupied-page metric.
240 pub physical_cache_usage: f64,
241 pub running_requests: u64,
242 pub waiting_requests: u64,
243 pub preemptions_total: u64,
244 /// SGLang radix-cache tokens reused by the most recently completed pass.
245 ///
246 /// Backends without an equivalent pass-local metric report zero.
247 pub sglang_cache_hit_tokens: u64,
248 /// Total SGLang prefill tokens considered by the most recently completed
249 /// pass. Backends without an equivalent pass-local metric report zero.
250 pub sglang_cache_total_tokens: u64,
251}
252
253/// Per-pass scheduling statistics.
254#[derive(Debug, Clone, Default, PartialEq)]
255pub struct ForwardPassMetrics {
256 pub num_prefill_requests: u32,
257 pub sum_prefill_tokens: u64,
258 pub var_prefill_length: f64,
259 pub sum_prefill_kv_tokens: u64,
260 pub num_decode_requests: u32,
261 pub sum_decode_kv_tokens: u64,
262 pub var_decode_kv_tokens: f64,
263 pub num_queued_prefill: u32,
264 pub sum_queued_prefill_tokens: u64,
265 pub var_queued_prefill_length: f64,
266 pub num_queued_decode: u32,
267 pub sum_queued_decode_kv_tokens: u64,
268 pub var_queued_decode_kv_tokens: f64,
269 pub duration_ms: f64,
270}
271
272/// Effects of a scheduler command.
273#[derive(Debug, Clone, PartialEq)]
274pub struct CommandEffects {
275 pub result: CommandResult,
276 pub lifecycle_events: Vec<LifecycleEvent>,
277 pub kv_events: Vec<KvEvent>,
278 /// Requests whose scheduler/KV ownership ended while applying this command.
279 ///
280 /// Replayers use this authoritative delta for cancellation and handoff
281 /// cleanup instead of inferring ownership from command ordering.
282 pub retired_requests: Vec<Uuid>,
283 /// Scheduler state after the command and all immediately admitted work.
284 ///
285 /// Live drivers use this to acknowledge commands while the engine is idle
286 /// without waiting for an otherwise unrelated forward pass.
287 pub metrics: Metrics,
288 /// Whether an output already computed by an in-flight pass was suppressed.
289 pub suppressed_pending_output: bool,
290}
291
292/// Effects visible as soon as an engine pass starts.
293#[derive(Debug, Clone, Default, PartialEq)]
294pub struct PassStartEffects {
295 pub admissions: Vec<Admission>,
296 pub pressure_events: Vec<PressureEvent>,
297 pub kv_events: Vec<KvEvent>,
298}
299
300/// Effects released at the modeled pass completion boundary.
301#[derive(Debug, Clone, Default, PartialEq)]
302pub struct PassCompletionEffects {
303 pub outputs: Vec<Output>,
304 pub lifecycle_events: Vec<LifecycleEvent>,
305 /// KV events whose scheduler visibility boundary is pass completion.
306 pub kv_events: Vec<KvEvent>,
307 pub metrics: Metrics,
308 pub forward_pass_metrics: ForwardPassMetrics,
309}
310
311/// Retained completion effects of an eagerly executed engine pass.
312#[doc(hidden)]
313pub struct PendingPass {
314 pub(crate) started_at_ms: f64,
315 pub(crate) effects: PassCompletionEffects,
316}