- Cast input to weight dtype in DQN residual, rmsnorm, noisy_layers - Set use_gpu=true in QNetworkConfig defaults and all config sites - Resolve BF16 boundary mismatches in attention, curiosity, branching, distributional_dueling across ml-dqn - GPU-resident regime ops with BF16 boundary casts, eliminate .expect() in CUDA paths - Eliminate all Device::Cpu fallbacks — GPU-only across 10 ML crates - PPO: cast logits to F32 before softmax, cast batch tensors to training dtype - Gradient collapse detection for RegimeConditionalDQN - Wire halt_grad_collapse from CUDA guard kernel to halt training - Dead neuron detection uses active network VarMap + squeeze factored readback - Increment gradient_logging_step in GPU PER path - Gradient collapse warmup guards use original buffer_size - Cap training steps per epoch + tracing migration - Replace Tensor::all() with sum_all() for pinned Candle compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
199 lines
6.6 KiB
Rust
199 lines
6.6 KiB
Rust
//! Prometheus metrics for ensemble hot-swapping
|
|
//!
|
|
//! This module provides comprehensive metrics for monitoring checkpoint
|
|
//! hot-swapping operations, including swap success rates, latencies,
|
|
//! validation results, and canary monitoring.
|
|
|
|
use once_cell::sync::Lazy;
|
|
use prometheus::{register_counter_vec, register_histogram_vec, CounterVec, HistogramVec};
|
|
|
|
// Metric registration is infallible in practice; once_cell::Lazy closures cannot use `?`
|
|
#[allow(clippy::expect_used)]
|
|
/// Counter for checkpoint swaps by status
|
|
pub static CHECKPOINT_SWAPS_TOTAL: Lazy<CounterVec> = Lazy::new(|| {
|
|
register_counter_vec!(
|
|
"checkpoint_swaps_total",
|
|
"Total checkpoint hot-swaps by model and status",
|
|
&["model_id", "status"] // status: success, rollback, failed
|
|
)
|
|
.expect("Failed to register checkpoint_swaps_total")
|
|
});
|
|
|
|
#[allow(clippy::expect_used)]
|
|
/// Histogram for checkpoint swap latency
|
|
pub static CHECKPOINT_SWAP_LATENCY_MICROSECONDS: Lazy<HistogramVec> = Lazy::new(|| {
|
|
register_histogram_vec!(
|
|
"checkpoint_swap_latency_microseconds",
|
|
"Checkpoint swap latency in microseconds (atomic swap operation)",
|
|
&["model_id"],
|
|
vec![0.1, 0.5, 1.0, 5.0, 10.0, 50.0, 100.0] // Sub-microsecond to 100μs
|
|
)
|
|
.expect("Failed to register checkpoint_swap_latency_microseconds")
|
|
});
|
|
|
|
#[allow(clippy::expect_used)]
|
|
/// Counter for checkpoint validations
|
|
pub static CHECKPOINT_VALIDATION_TOTAL: Lazy<CounterVec> = Lazy::new(|| {
|
|
register_counter_vec!(
|
|
"checkpoint_validation_total",
|
|
"Total checkpoint validations by model and result",
|
|
&["model_id", "result"] // result: passed, failed
|
|
)
|
|
.expect("Failed to register checkpoint_validation_total")
|
|
});
|
|
|
|
#[allow(clippy::expect_used)]
|
|
/// Histogram for checkpoint validation latency
|
|
pub static CHECKPOINT_VALIDATION_LATENCY_MILLISECONDS: Lazy<HistogramVec> = Lazy::new(|| {
|
|
register_histogram_vec!(
|
|
"checkpoint_validation_latency_milliseconds",
|
|
"Checkpoint validation latency in milliseconds (1000 predictions)",
|
|
&["model_id"],
|
|
vec![10.0, 50.0, 100.0, 500.0, 1000.0, 5000.0]
|
|
)
|
|
.expect("Failed to register checkpoint_validation_latency_milliseconds")
|
|
});
|
|
|
|
#[allow(clippy::expect_used)]
|
|
/// Histogram for validated checkpoint P99 inference latency
|
|
pub static CHECKPOINT_P99_LATENCY_MICROSECONDS: Lazy<HistogramVec> = Lazy::new(|| {
|
|
register_histogram_vec!(
|
|
"checkpoint_p99_latency_microseconds",
|
|
"P99 inference latency from checkpoint validation",
|
|
&["model_id"],
|
|
vec![1.0, 5.0, 10.0, 25.0, 50.0, 100.0]
|
|
)
|
|
.expect("Failed to register checkpoint_p99_latency_microseconds")
|
|
});
|
|
|
|
#[allow(clippy::expect_used)]
|
|
/// Counter for canary monitoring results
|
|
pub static CANARY_MONITORING_TOTAL: Lazy<CounterVec> = Lazy::new(|| {
|
|
register_counter_vec!(
|
|
"canary_monitoring_total",
|
|
"Total canary monitoring runs by model and result",
|
|
&["model_id", "result"] // result: success, failed
|
|
)
|
|
.expect("Failed to register canary_monitoring_total")
|
|
});
|
|
|
|
#[allow(clippy::expect_used)]
|
|
/// Histogram for canary monitoring duration
|
|
pub static CANARY_MONITORING_DURATION_SECONDS: Lazy<HistogramVec> = Lazy::new(|| {
|
|
register_histogram_vec!(
|
|
"canary_monitoring_duration_seconds",
|
|
"Canary monitoring duration in seconds",
|
|
&["model_id"],
|
|
vec![60.0, 120.0, 300.0, 600.0, 1800.0] // 1 min to 30 min
|
|
)
|
|
.expect("Failed to register canary_monitoring_duration_seconds")
|
|
});
|
|
|
|
#[allow(clippy::expect_used)]
|
|
/// Counter for rollbacks by reason
|
|
pub static CHECKPOINT_ROLLBACKS_TOTAL: Lazy<CounterVec> = Lazy::new(|| {
|
|
register_counter_vec!(
|
|
"checkpoint_rollbacks_total",
|
|
"Total checkpoint rollbacks by model and reason",
|
|
&["model_id", "reason"] // reason: latency, error_rate, accuracy_drop
|
|
)
|
|
.expect("Failed to register checkpoint_rollbacks_total")
|
|
});
|
|
|
|
/// Ensemble metrics collector
|
|
#[derive(Debug)]
|
|
pub struct EnsembleMetrics;
|
|
|
|
impl EnsembleMetrics {
|
|
/// Record successful checkpoint swap
|
|
pub fn record_swap_success(model_id: &str, latency_us: u64) {
|
|
CHECKPOINT_SWAPS_TOTAL
|
|
.with_label_values(&[model_id, "success"])
|
|
.inc();
|
|
|
|
CHECKPOINT_SWAP_LATENCY_MICROSECONDS
|
|
.with_label_values(&[model_id])
|
|
.observe(latency_us as f64);
|
|
}
|
|
|
|
/// Record checkpoint swap rollback
|
|
pub fn record_swap_rollback(model_id: &str) {
|
|
CHECKPOINT_SWAPS_TOTAL
|
|
.with_label_values(&[model_id, "rollback"])
|
|
.inc();
|
|
}
|
|
|
|
/// Record checkpoint swap failure
|
|
pub fn record_swap_failed(model_id: &str) {
|
|
CHECKPOINT_SWAPS_TOTAL
|
|
.with_label_values(&[model_id, "failed"])
|
|
.inc();
|
|
}
|
|
|
|
/// Record checkpoint validation result
|
|
pub fn record_validation(
|
|
model_id: &str,
|
|
passed: bool,
|
|
validation_latency_ms: u64,
|
|
p99_latency_us: u64,
|
|
) {
|
|
let result = if passed { "passed" } else { "failed" };
|
|
|
|
CHECKPOINT_VALIDATION_TOTAL
|
|
.with_label_values(&[model_id, result])
|
|
.inc();
|
|
|
|
CHECKPOINT_VALIDATION_LATENCY_MILLISECONDS
|
|
.with_label_values(&[model_id])
|
|
.observe(validation_latency_ms as f64);
|
|
|
|
CHECKPOINT_P99_LATENCY_MICROSECONDS
|
|
.with_label_values(&[model_id])
|
|
.observe(p99_latency_us as f64);
|
|
}
|
|
|
|
/// Record canary monitoring result
|
|
pub fn record_canary(model_id: &str, success: bool, duration_secs: u64) {
|
|
let result = if success { "success" } else { "failed" };
|
|
|
|
CANARY_MONITORING_TOTAL
|
|
.with_label_values(&[model_id, result])
|
|
.inc();
|
|
|
|
CANARY_MONITORING_DURATION_SECONDS
|
|
.with_label_values(&[model_id])
|
|
.observe(duration_secs as f64);
|
|
}
|
|
|
|
/// Record rollback with reason
|
|
pub fn record_rollback(model_id: &str, reason: &str) {
|
|
CHECKPOINT_ROLLBACKS_TOTAL
|
|
.with_label_values(&[model_id, reason])
|
|
.inc();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tracing::info;
|
|
|
|
#[test]
|
|
fn test_metrics_recording() {
|
|
// Record successful swap
|
|
EnsembleMetrics::record_swap_success("DQN", 500);
|
|
|
|
// Record validation
|
|
EnsembleMetrics::record_validation("DQN", true, 150, 35);
|
|
|
|
// Record canary success
|
|
EnsembleMetrics::record_canary("DQN", true, 300);
|
|
|
|
// Record rollback
|
|
EnsembleMetrics::record_rollback("DQN", "latency");
|
|
|
|
// Verify metrics were recorded (metrics are global, just test they don't panic)
|
|
info!("All metrics recorded successfully");
|
|
}
|
|
}
|