use crate::tuner::error::TunerError;
use crate::tuner::features::FeatureExtractor;
use crate::tuner::helpers::crc32_hash;
use super::types::TrainingSample;
use super::TunerDataCollector;
impl TunerDataCollector {
#[cfg(feature = "hardware-detect")]
pub fn cache_path() -> std::path::PathBuf {
let hw_id = Self::hardware_id();
dirs::cache_dir()
.unwrap_or_else(|| std::path::PathBuf::from(".cache"))
.join("trueno")
.join(format!("training_data_{}.apr", hw_id))
}
#[cfg(feature = "hardware-detect")]
pub fn hardware_id() -> String {
use crate::hardware::HardwareCapability;
let hw = HardwareCapability::detect();
let fingerprint = format!(
"{}-{:?}-{}-{}",
hw.cpu.cores,
hw.cpu.simd,
hw.gpu.as_ref().map(|g| g.model.as_str()).unwrap_or("none"),
hw.gpu.as_ref().map(|g| g.vram_gb as u32).unwrap_or(0),
);
let hash = crc32_hash(fingerprint.as_bytes());
format!("{:08x}", hash)
}
#[cfg(feature = "hardware-detect")]
pub fn load_or_create() -> Self {
let path = Self::cache_path();
if path.exists() {
if let Ok(collector) = Self::load_apr(&path) {
return collector;
}
}
Self::new()
}
pub fn save_apr<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), TunerError> {
use std::io::Write;
if let Some(parent) = path.as_ref().parent() {
std::fs::create_dir_all(parent)
.map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
}
let json = serde_json::to_string(&self.samples)
.map_err(|e| TunerError::Serialization(e.to_string()))?;
let json_bytes = json.as_bytes();
let mut file = std::fs::File::create(path.as_ref())
.map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
file.write_all(b"APR2").map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
let len = json_bytes.len() as u32;
file.write_all(&len.to_le_bytes())
.map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
file.write_all(json_bytes).map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
let checksum = crc32_hash(json_bytes);
file.write_all(&checksum.to_le_bytes())
.map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
Ok(())
}
pub fn load_apr<P: AsRef<std::path::Path>>(path: P) -> Result<Self, TunerError> {
use std::io::Read;
let mut file = std::fs::File::open(path.as_ref())
.map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
let mut magic = [0u8; 4];
file.read_exact(&mut magic).map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
if &magic != b"APR2" {
return Err(TunerError::InvalidFormat(format!("Expected APR2 magic, got {:?}", magic)));
}
let mut len_bytes = [0u8; 4];
file.read_exact(&mut len_bytes)
.map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
let len = u32::from_le_bytes(len_bytes) as usize;
let mut json_bytes = vec![0u8; len];
file.read_exact(&mut json_bytes)
.map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
let mut crc_bytes = [0u8; 4];
file.read_exact(&mut crc_bytes)
.map_err(|e: std::io::Error| TunerError::Io(e.to_string()))?;
let stored_crc = u32::from_le_bytes(crc_bytes);
let computed_crc = crc32_hash(&json_bytes);
if stored_crc != computed_crc {
return Err(TunerError::InvalidFormat(format!(
"CRC mismatch: stored={:08x}, computed={:08x}",
stored_crc, computed_crc
)));
}
let samples: Vec<TrainingSample> = serde_json::from_slice(&json_bytes)
.map_err(|e| TunerError::Serialization(e.to_string()))?;
Ok(Self {
samples,
extractor: FeatureExtractor::new(),
retrain_threshold: 100,
samples_at_last_train: 0,
feedback: Vec::new(),
online_learning_enabled: false,
error_window: Vec::new(),
error_window_size: Self::DEFAULT_ERROR_WINDOW_SIZE,
})
}
#[cfg(feature = "hardware-detect")]
pub fn record_and_persist(
&mut self,
profiler: &crate::brick::BrickProfiler,
config: &crate::tuner::features::RunConfig,
kernel: crate::tuner::types::KernelType,
) -> Result<(), TunerError> {
self.record(profiler, config, kernel);
let path = Self::cache_path();
self.save_apr(&path)?;
Ok(())
}
pub fn from_json(json: &str) -> Result<Self, TunerError> {
let samples: Vec<TrainingSample> =
serde_json::from_str(json).map_err(|e| TunerError::Serialization(e.to_string()))?;
Ok(Self {
samples,
extractor: FeatureExtractor::new(),
retrain_threshold: 100,
samples_at_last_train: 0,
feedback: Vec::new(),
online_learning_enabled: false,
error_window: Vec::new(),
error_window_size: Self::DEFAULT_ERROR_WINDOW_SIZE,
})
}
pub fn bootstrap_from_five_whys() -> Self {
Self::new()
}
}