use std::any::Any;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub enum BrickAssertion {
MinWidth(u16),
MinHeight(u16),
MaxWidth(u16),
MaxHeight(u16),
MaxRenderTimeMs(u32),
MaxLatencyMs(u32),
ValueInRange { min: f64, max: f64 },
DataNonEmpty,
Custom {
name: &'static str,
description: &'static str,
},
ChecksumMatch {
expected: u64,
actual: u64,
kernel_name: String,
position: u32,
},
}
impl BrickAssertion {
pub fn name(&self) -> &str {
match self {
Self::MinWidth(_) => "min_width",
Self::MinHeight(_) => "min_height",
Self::MaxWidth(_) => "max_width",
Self::MaxHeight(_) => "max_height",
Self::MaxRenderTimeMs(_) => "max_render_time_ms",
Self::MaxLatencyMs(_) => "max_latency_ms",
Self::ValueInRange { .. } => "value_in_range",
Self::DataNonEmpty => "data_non_empty",
Self::Custom { name, .. } => name,
Self::ChecksumMatch { .. } => "checksum_match",
}
}
pub fn custom<F>(_name: &'static str, _validator: F) -> Self
where
F: Fn(&dyn Any) -> bool,
{
Self::Custom {
name: _name,
description: "",
}
}
pub const fn max_latency_ms(ms: u32) -> Self {
Self::MaxLatencyMs(ms)
}
pub fn checksum_match(expected: u64, actual: u64, kernel_name: &str, position: u32) -> Self {
Self::ChecksumMatch {
expected,
actual,
kernel_name: kernel_name.to_string(),
position,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KernelTrace {
pub kernel_name: String,
pub layer_idx: usize,
pub position: u32,
pub input_checksum: u64,
pub output_checksum: u64,
pub params: String,
pub time_us: f64,
pub backend: String,
}
impl KernelTrace {
pub fn new(kernel_name: &str, layer_idx: usize, position: u32, backend: &str) -> Self {
Self {
kernel_name: kernel_name.to_string(),
layer_idx,
position,
input_checksum: 0,
output_checksum: 0,
params: String::new(),
time_us: 0.0,
backend: backend.to_string(),
}
}
pub fn with_input_checksum(mut self, data: &[f32]) -> Self {
self.input_checksum = fnv1a_f32(data);
self
}
pub fn with_output_checksum(mut self, data: &[f32]) -> Self {
self.output_checksum = fnv1a_f32(data);
self
}
pub fn with_params(mut self, params: &str) -> Self {
self.params = params.to_string();
self
}
pub fn with_time_us(mut self, time_us: f64) -> Self {
self.time_us = time_us;
self
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DivergenceReport {
pub matched: bool,
pub first_divergent_kernel: Option<KernelTrace>,
pub expected_trace: Option<KernelTrace>,
pub actual_trace: Option<KernelTrace>,
pub kernels_compared: usize,
pub diagnosis: String,
}
impl DivergenceReport {
pub fn matched(kernels_compared: usize) -> Self {
Self {
matched: true,
first_divergent_kernel: None,
expected_trace: None,
actual_trace: None,
kernels_compared,
diagnosis: format!(
"All {} kernels matched between CPU and GPU",
kernels_compared
),
}
}
pub fn diverged(expected: KernelTrace, actual: KernelTrace, kernels_compared: usize) -> Self {
let diagnosis = format!(
"DIVERGENCE at kernel '{}' (layer {}, position {}): \
CPU checksum 0x{:016X} != GPU checksum 0x{:016X}. \
Params: {}",
actual.kernel_name,
actual.layer_idx,
actual.position,
expected.output_checksum,
actual.output_checksum,
actual.params,
);
Self {
matched: false,
first_divergent_kernel: Some(actual.clone()),
expected_trace: Some(expected),
actual_trace: Some(actual),
kernels_compared,
diagnosis,
}
}
}
pub fn fnv1a_f32(data: &[f32]) -> u64 {
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
let mut hash = FNV_OFFSET;
let len = data.len().min(64);
for &val in &data[..len] {
let bytes = val.to_le_bytes();
for byte in bytes {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(FNV_PRIME);
}
}
hash
}
#[derive(Debug, Clone, Copy, Default)]
pub struct BrickBudget {
pub collect_ms: u32,
pub layout_ms: u32,
pub render_ms: u32,
}
impl BrickBudget {
pub const fn uniform(ms: u32) -> Self {
Self {
collect_ms: ms,
layout_ms: ms,
render_ms: ms,
}
}
pub const FRAME_60FPS: Self = Self {
collect_ms: 5,
layout_ms: 3,
render_ms: 8,
};
pub const FRAME_30FPS: Self = Self {
collect_ms: 10,
layout_ms: 6,
render_ms: 17,
};
pub const fn total_ms(&self) -> u32 {
self.collect_ms + self.layout_ms + self.render_ms
}
}
#[derive(Debug, Clone)]
pub struct BrickVerification {
pub passed: Vec<BrickAssertion>,
pub failed: Vec<(BrickAssertion, String)>,
pub verification_time: Duration,
pub timestamp: Instant,
}
impl BrickVerification {
pub fn new() -> Self {
Self {
passed: Vec::new(),
failed: Vec::new(),
verification_time: Duration::ZERO,
timestamp: Instant::now(),
}
}
pub fn pass() -> Self {
Self::new()
}
pub fn add_pass(&mut self, assertion: BrickAssertion) {
self.passed.push(assertion);
}
pub fn add_fail(&mut self, assertion: BrickAssertion, reason: impl Into<String>) {
self.failed.push((assertion, reason.into()));
}
pub fn check(&mut self, assertion: &BrickAssertion) {
self.passed.push(assertion.clone());
}
pub fn is_valid(&self) -> bool {
self.failed.is_empty()
}
pub fn score(&self) -> f64 {
let total = self.passed.len() + self.failed.len();
if total == 0 {
1.0
} else {
self.passed.len() as f64 / total as f64
}
}
pub fn failure_count(&self) -> usize {
self.failed.len()
}
}
impl Default for BrickVerification {
fn default() -> Self {
Self::new()
}
}