use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use super::error::io_error;
use super::{
ANSWERS_FILE, CompareError, EvalQuestion, HARDWARE_FILE, MODEL_FILE, SERVER_LOG,
write_child_questions,
};
use crate::export::{PYTHON, TRAMPOLINE, link_file, llama_cpp_env, train_io};
use crate::train::{Artifacts, JobStage, METRICS_ENV, METRICS_FILE, TrainError, Trainer};
pub const SCRIPT: &str = concat!(
include_str!("../export/llama_cpp.sh"),
include_str!("compare.sh")
);
pub const SCRIPT_FILE: &str = "compare.sh";
pub const CLIENT: &str = include_str!("compare_client.py");
pub const CLIENT_FILE: &str = "compare_client.py";
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ChildSettings {
pub max_tokens: u32,
pub temperature: f64,
pub server_start_secs: u64,
pub context: u32,
}
impl ChildSettings {
#[must_use]
pub fn from_config(compare: &crate::config::Compare, context: u32) -> Self {
Self {
max_tokens: compare.max_tokens,
temperature: compare.temperature,
server_start_secs: compare.server_start_secs,
context,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ModelSource {
Upload(PathBuf),
OnTarget(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct CompareJob {
model: ModelSource,
script: String,
questions: Vec<EvalQuestion>,
settings: ChildSettings,
}
impl CompareJob {
pub fn new(
model: ModelSource,
questions: Vec<EvalQuestion>,
settings: ChildSettings,
) -> Result<Self, CompareError> {
if let ModelSource::OnTarget(path) = &model
&& !path.starts_with('/')
{
return Err(CompareError::RelativeModel(path.clone()));
}
Ok(Self {
model,
script: SCRIPT_FILE.to_string(),
questions,
settings,
})
}
#[must_use]
pub fn with_script(mut self, script: String) -> Self {
self.script = script;
self
}
fn model_path(&self) -> &str {
match &self.model {
ModelSource::Upload(_) => MODEL_FILE,
ModelSource::OnTarget(path) => path,
}
}
}
fn questions_error(path: &Path, error: CompareError) -> TrainError {
match error {
CompareError::Io { path, source } => TrainError::Io { path, source },
other => TrainError::Io {
path: path.to_path_buf(),
source: io::Error::other(other),
},
}
}
impl Trainer for CompareJob {
fn prepare(&self, run_dir: &Path, _root: &str) -> Result<(), TrainError> {
fs::create_dir_all(run_dir).map_err(train_io(run_dir))?;
for (name, content) in [(SCRIPT_FILE, SCRIPT), (CLIENT_FILE, CLIENT)] {
let path = run_dir.join(name);
fs::write(&path, content).map_err(train_io(&path))?;
}
write_child_questions(run_dir, &self.questions)
.map_err(|error| questions_error(&run_dir.join(super::QUESTIONS_FILE), error))?;
match &self.model {
ModelSource::Upload(gguf) => link_file(gguf, &run_dir.join(MODEL_FILE)),
ModelSource::OnTarget(_) => Ok(()),
}
}
fn commands(&self) -> Vec<Vec<String>> {
vec![vec![
PYTHON.to_string(),
"-c".to_string(),
TRAMPOLINE.to_string(),
self.script.clone(),
]]
}
fn env(&self, root: &str) -> Vec<(String, String)> {
let mut env = vec![
(METRICS_ENV.to_string(), format!("{root}/{METRICS_FILE}")),
(
"OVERBRAINER_COMPARE_MODEL".to_string(),
self.model_path().to_string(),
),
(
"OVERBRAINER_COMPARE_MAX_TOKENS".to_string(),
self.settings.max_tokens.to_string(),
),
(
"OVERBRAINER_COMPARE_TEMPERATURE".to_string(),
self.settings.temperature.to_string(),
),
(
"OVERBRAINER_COMPARE_START_SECS".to_string(),
self.settings.server_start_secs.to_string(),
),
(
"OVERBRAINER_COMPARE_CTX".to_string(),
self.settings.context.to_string(),
),
];
if matches!(self.model, ModelSource::Upload(_)) {
env.push((
"OVERBRAINER_COMPARE_DISCARD_MODEL".to_string(),
"1".to_string(),
));
}
env.extend(llama_cpp_env());
env
}
fn metrics_file(&self) -> &'static str {
METRICS_FILE
}
fn artifacts(&self) -> Artifacts {
Artifacts {
entries: vec![
ANSWERS_FILE.to_string(),
SERVER_LOG.to_string(),
HARDWARE_FILE.to_string(),
METRICS_FILE.to_string(),
],
exclude: Vec::new(),
required: Some(ANSWERS_FILE.to_string()),
}
}
fn stages(&self) -> Vec<JobStage> {
vec![JobStage::Compare]
}
fn metrics_required(&self) -> bool {
false
}
fn caches_tools(&self) -> bool {
true
}
}
pub fn discard_model(job_dir: &Path) -> Result<(), CompareError> {
let path = job_dir.join(MODEL_FILE);
match fs::remove_file(&path) {
Ok(()) => Ok(()),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(io_error(&path)(error)),
}
}