Skip to main content

ferrum_types/
numerical.rs

1//! User-requested numerical execution policy. Device capabilities and model
2//! family contracts resolve this request before any model execution begins.
3
4use std::{fmt, str::FromStr};
5
6use serde::{Deserialize, Serialize};
7
8/// Versioned storage of causal K/V state, independent of activation arithmetic.
9/// INT8 uses a separate F32 scale for each K/V token and head. Its encoder first
10/// rounds to the existing F16 KV boundary, then uses max-abs / 127 and ties-even
11/// rounding to [-127, 127]. A zero vector has scale 1; non-finite input fails.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum KvStorageFormat {
15    #[default]
16    F16,
17    Int8PerTokenHeadF32ScaleV1,
18}
19
20impl KvStorageFormat {
21    pub const fn as_str(self) -> &'static str {
22        match self {
23            Self::F16 => "f16",
24            Self::Int8PerTokenHeadF32ScaleV1 => "int8-per-token-head-f32-scale-v1",
25        }
26    }
27}
28
29impl std::fmt::Display for KvStorageFormat {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        f.write_str(self.as_str())
32    }
33}
34
35impl TryFrom<crate::KvCacheDtype> for KvStorageFormat {
36    type Error = String;
37
38    fn try_from(dtype: crate::KvCacheDtype) -> Result<Self, Self::Error> {
39        match dtype {
40            crate::KvCacheDtype::Fp16 => Ok(Self::F16),
41            crate::KvCacheDtype::Int8 => Ok(Self::Int8PerTokenHeadF32ScaleV1),
42            crate::KvCacheDtype::Bf16 => Err(
43                "vNext BF16 KV storage is unsupported; use fp16 or a supported int8 plan".into(),
44            ),
45            crate::KvCacheDtype::Fp8 => {
46                Err("vNext FP8 KV storage is unsupported; use fp16 or a supported int8 plan".into())
47            }
48        }
49    }
50}
51
52/// Stable profile identity, interpreted within a model family's declared
53/// catalog. The profile's numerical ABI has a separate contract version.
54#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
55#[serde(try_from = "String", into = "String")]
56pub struct NumericalProfileId(String);
57
58impl NumericalProfileId {
59    pub fn new(value: impl Into<String>) -> Result<Self, String> {
60        let value = value.into();
61        if value.is_empty()
62            || value.len() > 160
63            || !value.bytes().all(|byte| {
64                byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b':' | b'/')
65            })
66        {
67            return Err("numerical profile identity needs 1..=160 portable ASCII bytes".to_owned());
68        }
69        Ok(Self(value))
70    }
71
72    pub fn as_str(&self) -> &str {
73        &self.0
74    }
75}
76
77impl fmt::Display for NumericalProfileId {
78    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79        formatter.write_str(&self.0)
80    }
81}
82
83impl TryFrom<String> for NumericalProfileId {
84    type Error = String;
85
86    fn try_from(value: String) -> Result<Self, Self::Error> {
87        Self::new(value)
88    }
89}
90
91impl FromStr for NumericalProfileId {
92    type Err = String;
93
94    fn from_str(value: &str) -> Result<Self, Self::Err> {
95        Self::new(value)
96    }
97}
98
99impl From<NumericalProfileId> for String {
100    fn from(value: NumericalProfileId) -> Self {
101        value.0
102    }
103}
104
105/// A request, never proof that a numerical profile can execute on a device.
106/// `Require` must fail if its complete declared program cannot be supported.
107#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
108#[serde(rename_all = "snake_case")]
109pub enum NumericalExecutionPolicy {
110    #[default]
111    Auto,
112    Require(NumericalProfileId),
113}
114
115impl FromStr for NumericalExecutionPolicy {
116    type Err = String;
117
118    fn from_str(value: &str) -> Result<Self, Self::Err> {
119        match value {
120            "auto" => Ok(Self::Auto),
121            _ => Ok(Self::Require(value.parse()?)),
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn kv_storage_configuration_preserves_default_and_rejects_unimplemented_formats() {
132        assert_eq!(KvStorageFormat::default(), KvStorageFormat::F16);
133        assert_eq!(
134            KvStorageFormat::try_from(crate::KvCacheDtype::Int8).unwrap(),
135            KvStorageFormat::Int8PerTokenHeadF32ScaleV1
136        );
137        for dtype in [crate::KvCacheDtype::Bf16, crate::KvCacheDtype::Fp8] {
138            assert!(KvStorageFormat::try_from(dtype).is_err());
139        }
140        for storage in [
141            KvStorageFormat::F16,
142            KvStorageFormat::Int8PerTokenHeadF32ScaleV1,
143        ] {
144            assert_eq!(
145                serde_json::from_value::<KvStorageFormat>(serde_json::to_value(storage).unwrap())
146                    .unwrap(),
147                storage
148            );
149        }
150    }
151
152    #[test]
153    fn explicit_policy_preserves_identity_through_config_and_cli_parsing() {
154        let explicit: NumericalExecutionPolicy = "fixture.f32-master".parse().unwrap();
155        assert_eq!(
156            explicit,
157            NumericalExecutionPolicy::Require(
158                NumericalProfileId::new("fixture.f32-master").unwrap()
159            )
160        );
161        let json = serde_json::to_vec(&explicit).unwrap();
162        assert_eq!(
163            serde_json::from_slice::<NumericalExecutionPolicy>(&json).unwrap(),
164            explicit
165        );
166        assert_eq!(
167            "auto".parse::<NumericalExecutionPolicy>().unwrap(),
168            NumericalExecutionPolicy::Auto
169        );
170    }
171
172    #[test]
173    fn invalid_profile_ids_are_rejected_at_the_deserialization_boundary() {
174        for value in ["", "fixture f16", "fixture\nf16", "配置.f16"] {
175            assert!(value.parse::<NumericalExecutionPolicy>().is_err());
176            let json = serde_json::json!({ "require": value });
177            assert!(serde_json::from_value::<NumericalExecutionPolicy>(json).is_err());
178        }
179    }
180}