1use std::path::Path;
9
10use serde::{Deserialize, Serialize};
11use silicera::compare::{
12 compare_profiles, compare_with_placeholder, CompareReport, MachineBPlaceholder,
13 MeasurementEcho, SanitizedSplitExport, StrategyVector, SILICON_SPLIT_PROTOCOL,
14 SILICON_SPLIT_PROTOCOL_VERSION,
15};
16use silicera::hardware::HardwareInfo;
17use silicera::hnep::HnepProfile;
18use silicera::measure::MeasurementConfig;
19use silicera::Result;
20
21use crate::profile_gen::{generate_profile, ProfileGenConfig};
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct SiliconSplitProtocol {
26 pub protocol: String,
28 pub protocol_version: String,
30 pub measurement: MeasurementEcho,
32}
33
34impl Default for SiliconSplitProtocol {
35 fn default() -> Self {
36 Self {
37 protocol: SILICON_SPLIT_PROTOCOL.into(),
38 protocol_version: SILICON_SPLIT_PROTOCOL_VERSION.into(),
39 measurement: MeasurementEcho {
40 warmup: 3,
41 iterations: 20,
42 min_improvement: 0.03,
43 candidate_set: "baseline|scan_dense|copy|copy_loop|candidate".into(),
44 workload_set: "memory-l2|integer|float|branch|memscan-l1|memscan-l2|memscan-l3|memscan-dram"
45 .into(),
46 },
47 }
48 }
49}
50
51impl SiliconSplitProtocol {
52 pub fn profile_gen_config(&self, role: &str) -> ProfileGenConfig {
54 ProfileGenConfig {
55 measurement: MeasurementConfig {
56 warmup: self.measurement.warmup,
57 iterations: self.measurement.iterations,
58 ..Default::default()
59 },
60 label: format!("silicon-split-{role}"),
61 size_tree: true,
62 size_class_tournaments: true,
63 extended_size_targets: false,
64 min_improvement: self.measurement.min_improvement,
65 only: None,
66 }
67 }
68}
69
70pub fn train_split_profile(
72 info: &HardwareInfo,
73 role: &str,
74 out: &Path,
75 protocol: &SiliconSplitProtocol,
76) -> Result<(HnepProfile, SanitizedSplitExport)> {
77 let cfg = protocol.profile_gen_config(role);
78 let (profile, _results) = generate_profile(info, &cfg, out)?;
79 let export = sanitize_export(&profile, role, &protocol.measurement);
80 Ok((profile, export))
81}
82
83pub fn sanitize_export(
85 profile: &HnepProfile,
86 role: &str,
87 measurement: &MeasurementEcho,
88) -> SanitizedSplitExport {
89 SanitizedSplitExport {
90 protocol: SILICON_SPLIT_PROTOCOL.into(),
91 protocol_version: SILICON_SPLIT_PROTOCOL_VERSION.into(),
92 machine_role: role.into(),
93 strategy_vector: StrategyVector::from_profile(role, profile),
94 measurement_echo: measurement.clone(),
95 profile: profile.clone(),
96 }
97}
98
99pub fn write_export(export: &SanitizedSplitExport, path: &Path) -> Result<()> {
101 if let Some(parent) = path.parent() {
102 std::fs::create_dir_all(parent)?;
103 }
104 let text = serde_json::to_string_pretty(export)?;
105 std::fs::write(path, text)?;
106 Ok(())
107}
108
109pub fn read_export(path: &Path) -> Result<SanitizedSplitExport> {
111 let text = std::fs::read_to_string(path)?;
112 let export: SanitizedSplitExport = serde_json::from_str(&text)?;
113 export.profile.verify_integrity()?;
114 if export.protocol != SILICON_SPLIT_PROTOCOL {
115 return Err(silicera::SiliceraError::Parse(format!(
116 "unexpected silicon-split protocol {}",
117 export.protocol
118 )));
119 }
120 Ok(export)
121}
122
123pub fn write_machine_b_placeholder(a: &HnepProfile, path: &Path) -> Result<MachineBPlaceholder> {
125 let ph = MachineBPlaceholder::from_machine_a(a);
126 if let Some(parent) = path.parent() {
127 std::fs::create_dir_all(parent)?;
128 }
129 std::fs::write(path, serde_json::to_string_pretty(&ph)?)?;
130 Ok(ph)
131}
132
133pub fn split_report_profiles(a: &HnepProfile, b: Option<&HnepProfile>) -> CompareReport {
135 match b {
136 Some(b) => compare_profiles(a, b),
137 None => compare_with_placeholder(a, "second-zen-box"),
138 }
139}
140
141pub fn split_report_paths(a: &Path, b: Option<&Path>) -> Result<CompareReport> {
143 let pa = if a.extension().and_then(|e| e.to_str()) == Some("json") {
144 read_export(a)?.profile
145 } else {
146 HnepProfile::read_from(a)?
147 };
148 let pb = match b {
149 Some(path) => {
150 let p = if path.extension().and_then(|e| e.to_str()) == Some("json") {
151 read_export(path)?.profile
152 } else {
153 HnepProfile::read_from(path)?
154 };
155 Some(p)
156 }
157 None => None,
158 };
159 Ok(split_report_profiles(&pa, pb.as_ref()))
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use silicera::hardware::{HardwareBackend, MockHardware};
166 use silicera::knowledge::KnowledgePack;
167 use silicera::SplitVerdict;
168 use tempfile::tempdir;
169
170 #[test]
171 fn mock_a_placeholder_unknown() {
172 let kp = KnowledgePack::builtin();
173 let info = MockHardware::zen5_dual_ccd().discover(&kp).unwrap();
174 let dir = tempdir().unwrap();
175 let out = dir.path().join("a.hnep");
176 let protocol = SiliconSplitProtocol {
177 measurement: MeasurementEcho {
178 warmup: 1,
179 iterations: 3,
180 min_improvement: 0.03,
181 ..MeasurementEcho::default()
182 },
183 ..Default::default()
184 };
185 let (profile, export) = train_split_profile(&info, "A", &out, &protocol).unwrap();
186 assert!(!export.strategy_vector.workloads.is_empty());
187 let report = split_report_profiles(&profile, None);
188 assert_eq!(report.verdict, SplitVerdict::Unknown);
189 assert!(report.b_is_placeholder);
190 }
191
192 #[test]
193 fn mock_two_machines_compare_no_invented_physics() {
194 let kp = KnowledgePack::builtin();
197 let a_info = MockHardware::zen5_dual_ccd().discover(&kp).unwrap();
198 let b_info = MockHardware::zen4_single_ccd().discover(&kp).unwrap();
199 let dir = tempdir().unwrap();
200 let protocol = SiliconSplitProtocol {
201 measurement: MeasurementEcho {
202 warmup: 1,
203 iterations: 3,
204 min_improvement: 0.03,
205 ..MeasurementEcho::default()
206 },
207 ..Default::default()
208 };
209 let (pa, _) = train_split_profile(&a_info, "A", &dir.path().join("a.hnep"), &protocol).unwrap();
210 let (pb, _) = train_split_profile(&b_info, "B", &dir.path().join("b.hnep"), &protocol).unwrap();
211 let report = compare_profiles(&pa, &pb);
212 assert!(!report.b_is_placeholder);
213 assert!(!report.fingerprint_a.is_empty());
214 assert!(!report.fingerprint_b.is_empty());
215 assert_ne!(report.fingerprint_a, report.fingerprint_b);
216 let _ = report.verdict.label();
218 }
219}
220