Skip to main content

fib_quant/kv/
receipt.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{digest::bytes_digest, FibQuantError, Result};
4
5use super::quality::KvAttentionQualityReportV1;
6
7pub const KV_RECEIPT_SCHEMA: &str = "fib_quant_kv_receipt_v1";
8
9/// KV operation kind.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[non_exhaustive]
12#[serde(rename_all = "snake_case")]
13pub enum KvOperationKindV1 {
14    /// Compression/encode.
15    Compress,
16    /// Decode.
17    Decode,
18    /// Quality/evaluation.
19    Eval,
20}
21
22/// Compression receipt.
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub struct KvCompressionReceiptV1 {
25    /// Stable schema marker.
26    pub schema_version: String,
27    /// Operation kind.
28    pub operation_kind: KvOperationKindV1,
29    /// Source tensor digest.
30    pub source_digest: String,
31    /// KV profile digest.
32    pub profile_digest: String,
33    /// Shape digest.
34    pub shape_digest: String,
35    /// Page digests.
36    pub page_digests: Vec<String>,
37    /// Codebook digest.
38    pub codebook_digest: String,
39    /// Rotation digest.
40    pub rotation_digest: String,
41    /// Number of encoded pages.
42    pub encoded_pages: u32,
43    /// Number of compressed blocks.
44    pub compressed_blocks: u32,
45    /// Number of raw fallback blocks.
46    pub raw_fallback_blocks: u32,
47    /// Fallback/degradation reasons.
48    pub fallback_reasons: Vec<String>,
49    /// Recorded Unix time in seconds.
50    pub recorded_unix_seconds: i64,
51}
52
53/// Decode receipt.
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55pub struct KvDecodeReceiptV1 {
56    /// Stable schema marker.
57    pub schema_version: String,
58    /// Operation kind.
59    pub operation_kind: KvOperationKindV1,
60    /// Reconstructed tensor digest.
61    pub decoded_digest: String,
62    /// KV profile digest.
63    pub profile_digest: String,
64    /// Shape digest.
65    pub shape_digest: String,
66    /// Page digests verified during decode.
67    pub page_digests: Vec<String>,
68    /// Codebook digest.
69    pub codebook_digest: String,
70    /// Rotation digest.
71    pub rotation_digest: String,
72    /// Number of pages decoded.
73    pub decoded_pages: u32,
74    /// Number of raw fallback blocks decoded.
75    pub raw_fallback_blocks: u32,
76    /// Recorded Unix time in seconds.
77    pub recorded_unix_seconds: i64,
78}
79
80/// Quality/eval receipt.
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct KvEvalReceiptV1 {
83    /// Stable schema marker.
84    pub schema_version: String,
85    /// Operation kind.
86    pub operation_kind: KvOperationKindV1,
87    /// Source tensor digest.
88    pub source_digest: String,
89    /// Decoded tensor digest.
90    pub decoded_digest: String,
91    /// KV profile digest.
92    pub profile_digest: String,
93    /// Shape digest.
94    pub shape_digest: String,
95    /// Optional quality metrics.
96    pub quality_report: Option<KvAttentionQualityReportV1>,
97    /// Recorded Unix time in seconds.
98    pub recorded_unix_seconds: i64,
99}
100
101impl KvCompressionReceiptV1 {
102    pub(crate) fn validate(&self) -> Result<()> {
103        if self.schema_version != KV_RECEIPT_SCHEMA
104            || self.operation_kind != KvOperationKindV1::Compress
105        {
106            return Err(FibQuantError::CorruptPayload(
107                "invalid kv compression receipt".into(),
108            ));
109        }
110        Ok(())
111    }
112}
113
114/// Stable digest for a canonical f32 tensor.
115pub fn kv_tensor_digest(values: &[f32]) -> Result<String> {
116    if values.iter().any(|value| !value.is_finite()) {
117        return Err(FibQuantError::CorruptPayload(
118            "kv tensor contains non-finite value".into(),
119        ));
120    }
121    let mut bytes = Vec::with_capacity(32 + std::mem::size_of_val(values));
122    bytes.extend_from_slice(b"fib_quant_kv_tensor_f32_v1");
123    bytes.push(0);
124    bytes.extend_from_slice(&(values.len() as u64).to_le_bytes());
125    for value in values {
126        bytes.extend_from_slice(&value.to_le_bytes());
127    }
128    Ok(bytes_digest(&bytes))
129}
130
131pub(crate) fn now_unix_seconds() -> i64 {
132    std::time::SystemTime::now()
133        .duration_since(std::time::UNIX_EPOCH)
134        .map(|duration| duration.as_secs() as i64)
135        .unwrap_or(0)
136}