use serde::{Deserialize, Serialize};
use crate::{digest::bytes_digest, FibQuantError, Result};
use super::quality::KvAttentionQualityReportV1;
pub const KV_RECEIPT_SCHEMA: &str = "fib_quant_kv_receipt_v1";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(rename_all = "snake_case")]
pub enum KvOperationKindV1 {
Compress,
Decode,
Eval,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct KvCompressionReceiptV1 {
pub schema_version: String,
pub operation_kind: KvOperationKindV1,
pub source_digest: String,
pub profile_digest: String,
pub shape_digest: String,
pub page_digests: Vec<String>,
pub codebook_digest: String,
pub rotation_digest: String,
pub encoded_pages: u32,
pub compressed_blocks: u32,
pub raw_fallback_blocks: u32,
pub fallback_reasons: Vec<String>,
pub recorded_unix_seconds: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct KvDecodeReceiptV1 {
pub schema_version: String,
pub operation_kind: KvOperationKindV1,
pub decoded_digest: String,
pub profile_digest: String,
pub shape_digest: String,
pub page_digests: Vec<String>,
pub codebook_digest: String,
pub rotation_digest: String,
pub decoded_pages: u32,
pub raw_fallback_blocks: u32,
pub recorded_unix_seconds: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct KvEvalReceiptV1 {
pub schema_version: String,
pub operation_kind: KvOperationKindV1,
pub source_digest: String,
pub decoded_digest: String,
pub profile_digest: String,
pub shape_digest: String,
pub quality_report: Option<KvAttentionQualityReportV1>,
pub recorded_unix_seconds: i64,
}
impl KvCompressionReceiptV1 {
pub(crate) fn validate(&self) -> Result<()> {
if self.schema_version != KV_RECEIPT_SCHEMA
|| self.operation_kind != KvOperationKindV1::Compress
{
return Err(FibQuantError::CorruptPayload(
"invalid kv compression receipt".into(),
));
}
Ok(())
}
}
pub fn kv_tensor_digest(values: &[f32]) -> Result<String> {
if values.iter().any(|value| !value.is_finite()) {
return Err(FibQuantError::CorruptPayload(
"kv tensor contains non-finite value".into(),
));
}
let mut bytes = Vec::with_capacity(32 + std::mem::size_of_val(values));
bytes.extend_from_slice(b"fib_quant_kv_tensor_f32_v1");
bytes.push(0);
bytes.extend_from_slice(&(values.len() as u64).to_le_bytes());
for value in values {
bytes.extend_from_slice(&value.to_le_bytes());
}
Ok(bytes_digest(&bytes))
}
pub(crate) fn now_unix_seconds() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|duration| duration.as_secs() as i64)
.unwrap_or(0)
}