use serde::Serialize;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ReplayTelemetrySampleKind {
Baseline,
Periodic,
Final,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ReplaySchedulerMetricsSnapshot {
pub worker_id: usize,
pub dp_rank: u32,
pub active_blocks: u64,
pub inactive_blocks: u64,
pub total_blocks: u64,
pub active_cache_usage: f64,
pub physical_cache_usage: f64,
pub running_requests: u64,
pub waiting_requests: u64,
}
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize)]
pub struct ReplaySchedulerIntervalMetrics {
pub cache_hit_tokens: u64,
pub cache_total_tokens: u64,
pub preemptions: u64,
}
impl ReplaySchedulerIntervalMetrics {
pub(crate) const fn has_observations(&self) -> bool {
self.cache_hit_tokens != 0 || self.cache_total_tokens != 0 || self.preemptions != 0
}
pub(crate) fn checked_add_assign(&mut self, other: Self) -> anyhow::Result<()> {
self.cache_hit_tokens = self
.cache_hit_tokens
.checked_add(other.cache_hit_tokens)
.ok_or_else(|| anyhow::anyhow!("scheduler cache-hit token counter overflow"))?;
self.cache_total_tokens = self
.cache_total_tokens
.checked_add(other.cache_total_tokens)
.ok_or_else(|| anyhow::anyhow!("scheduler cache-total token counter overflow"))?;
self.preemptions = self
.preemptions
.checked_add(other.preemptions)
.ok_or_else(|| anyhow::anyhow!("scheduler preemption counter overflow"))?;
Ok(())
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
pub struct ReplayTrafficMetricsSnapshot {
pub duration_s: f64,
pub arriving_requests: usize,
pub completed_requests: usize,
pub avg_isl: f64,
pub avg_osl: f64,
pub avg_ttft_ms: f64,
pub avg_itl_ms: f64,
pub ttft_count: usize,
pub itl_count: usize,
pub avg_router_kv_hit_rate: f64,
pub router_kv_hit_rate_count: usize,
pub avg_accept_length: Option<f64>,
pub accept_length_forward_count: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ReplayTelemetrySnapshot {
pub sample_ordinal: u64,
pub kind: ReplayTelemetrySampleKind,
pub interval_start_ms: f64,
pub sampled_at_ms: f64,
pub traffic: ReplayTrafficMetricsSnapshot,
pub prefill_scheduler_metrics: Vec<ReplaySchedulerMetricsSnapshot>,
pub decode_scheduler_metrics: Vec<ReplaySchedulerMetricsSnapshot>,
pub prefill_interval_metrics: ReplaySchedulerIntervalMetrics,
pub decode_interval_metrics: ReplaySchedulerIntervalMetrics,
pub router_pending_prefill_requests: usize,
pub router_pending_decode_requests: usize,
pub active_prefill_ids: Vec<usize>,
pub active_decode_ids: Vec<usize>,
pub starting_prefill_ids: Vec<usize>,
pub starting_decode_ids: Vec<usize>,
pub draining_prefill_ids: Vec<usize>,
pub draining_decode_ids: Vec<usize>,
}
pub trait ReplayTelemetryObserver: Send {
fn on_sample(&mut self, snapshot: ReplayTelemetrySnapshot) -> anyhow::Result<()>;
}
pub(crate) struct ReplayTelemetryRuntime {
observer: Box<dyn ReplayTelemetryObserver>,
sample_interval_ms: f64,
next_sample_ordinal: u64,
sampling_origin_ms: Option<f64>,
interval_start_ms: f64,
}
impl ReplayTelemetryRuntime {
pub(crate) fn new(sample_interval_ms: f64, observer: Box<dyn ReplayTelemetryObserver>) -> Self {
Self {
observer,
sample_interval_ms,
next_sample_ordinal: 0,
sampling_origin_ms: None,
interval_start_ms: 0.0,
}
}
pub(crate) const fn next_sample_ordinal(&self) -> u64 {
self.next_sample_ordinal
}
pub(crate) const fn interval_start_ms(&self) -> f64 {
self.interval_start_ms
}
pub(crate) fn start_at(&mut self, now_ms: f64) {
self.sampling_origin_ms = Some(now_ms);
self.interval_start_ms = now_ms;
}
pub(crate) fn next_periodic_at_ms(&self) -> anyhow::Result<f64> {
let origin_ms = self
.sampling_origin_ms
.ok_or_else(|| anyhow::anyhow!("replay telemetry baseline was not initialized"))?;
let at_ms = origin_ms + self.next_sample_ordinal as f64 * self.sample_interval_ms;
if !at_ms.is_finite() {
return Err(anyhow::anyhow!(
"replay telemetry sample timestamp overflow"
));
}
if at_ms <= self.interval_start_ms {
return Err(anyhow::anyhow!(
"replay telemetry cadence is below virtual-clock precision at {} ms",
self.interval_start_ms
));
}
Ok(at_ms)
}
pub(crate) fn publish(&mut self, snapshot: ReplayTelemetrySnapshot) -> anyhow::Result<()> {
self.observer.on_sample(snapshot)?;
self.next_sample_ordinal = self
.next_sample_ordinal
.checked_add(1)
.ok_or_else(|| anyhow::anyhow!("replay telemetry sample ordinal overflow"))?;
Ok(())
}
pub(crate) fn close_interval(&mut self, sampled_at_ms: f64) {
self.interval_start_ms = sampled_at_ms;
}
}
#[cfg(test)]
mod tests {
use super::*;
struct NoopObserver;
impl ReplayTelemetryObserver for NoopObserver {
fn on_sample(&mut self, _snapshot: ReplayTelemetrySnapshot) -> anyhow::Result<()> {
Ok(())
}
}
fn sample(
sample_ordinal: u64,
kind: ReplayTelemetrySampleKind,
interval_start_ms: f64,
sampled_at_ms: f64,
) -> ReplayTelemetrySnapshot {
ReplayTelemetrySnapshot {
sample_ordinal,
kind,
interval_start_ms,
sampled_at_ms,
traffic: ReplayTrafficMetricsSnapshot::default(),
prefill_scheduler_metrics: Vec::new(),
decode_scheduler_metrics: Vec::new(),
prefill_interval_metrics: ReplaySchedulerIntervalMetrics::default(),
decode_interval_metrics: ReplaySchedulerIntervalMetrics::default(),
router_pending_prefill_requests: 0,
router_pending_decode_requests: 0,
active_prefill_ids: Vec::new(),
active_decode_ids: Vec::new(),
starting_prefill_ids: Vec::new(),
starting_decode_ids: Vec::new(),
draining_prefill_ids: Vec::new(),
draining_decode_ids: Vec::new(),
}
}
#[test]
fn submillisecond_cadence_stays_anchored_to_the_sampling_origin() {
let mut runtime = ReplayTelemetryRuntime::new(0.1, Box::new(NoopObserver));
runtime.start_at(0.0);
runtime
.publish(sample(0, ReplayTelemetrySampleKind::Baseline, 0.0, 0.0))
.unwrap();
for ordinal in 1..=10_000 {
let at_ms = runtime.next_periodic_at_ms().unwrap();
assert_eq!(at_ms, ordinal as f64 * 0.1);
runtime
.publish(sample(
ordinal,
ReplayTelemetrySampleKind::Periodic,
runtime.interval_start_ms(),
at_ms,
))
.unwrap();
runtime.close_interval(at_ms);
}
}
#[test]
fn cadence_rejects_intervals_below_virtual_clock_precision() {
let origin_ms = 1.0e20;
let mut runtime = ReplayTelemetryRuntime::new(0.1, Box::new(NoopObserver));
runtime.start_at(origin_ms);
runtime
.publish(sample(
0,
ReplayTelemetrySampleKind::Baseline,
origin_ms,
origin_ms,
))
.unwrap();
let error = runtime.next_periodic_at_ms().unwrap_err();
assert!(error.to_string().contains("below virtual-clock precision"));
}
}