|
|
@@ -105,6 +105,10 @@ fn pump<R: Read>(mut reader: R, to_console: bool, file: Arc<Mutex<fs::File>>) {
|
|
|
/// Run the bench tool in `cwd`, teeing stdout+stderr to the console and the
|
|
|
/// log file; returns the exit code.
|
|
|
///
|
|
|
+/// `append_log = false` truncates the log (main campaign phase); `true`
|
|
|
+/// appends (the depth-suite phase shares one log so its rows land in the
|
|
|
+/// report's Throughput table).
|
|
|
+///
|
|
|
/// The child runs under a pty (`script -q /dev/null`) instead of raw pipes:
|
|
|
/// tool-eval-bench's rich Live display deadlocks on a non-tty stdout pipe
|
|
|
/// right after the perf phase (deterministic on the 2x lane; 4x runs got
|
|
|
@@ -121,8 +125,21 @@ fn pump<R: Read>(mut reader: R, to_console: bool, file: Arc<Mutex<fs::File>>) {
|
|
|
/// keeps the deadlock workaround intact. `NO_COLOR=1` covers non-rich
|
|
|
/// output. Verified locally under `script`: 1.3 KB/8 CR of soup → 141
|
|
|
/// bytes/1 static final frame.
|
|
|
-fn run_tool(bin: &str, args: &[String], cwd: &Path, log_path: &Path) -> Result<i32> {
|
|
|
- let file = Arc::new(Mutex::new(fs::File::create(log_path)?));
|
|
|
+fn run_tool(
|
|
|
+ bin: &str,
|
|
|
+ args: &[String],
|
|
|
+ cwd: &Path,
|
|
|
+ log_path: &Path,
|
|
|
+ append_log: bool,
|
|
|
+) -> Result<i32> {
|
|
|
+ let file = Arc::new(Mutex::new(if append_log {
|
|
|
+ fs::OpenOptions::new()
|
|
|
+ .create(true)
|
|
|
+ .append(true)
|
|
|
+ .open(log_path)?
|
|
|
+ } else {
|
|
|
+ fs::File::create(log_path)?
|
|
|
+ }));
|
|
|
let mut cmd = if std::process::Command::new("/usr/bin/script")
|
|
|
.arg("-q")
|
|
|
.arg("/dev/null")
|
|
|
@@ -231,6 +248,9 @@ pub async fn run(config: &Config, a: &Args, dry_run: bool) -> Result<()> {
|
|
|
.join("bench")
|
|
|
.join(format!("{stamp}_{label}"));
|
|
|
let bin = "tool-eval-bench".to_string();
|
|
|
+ let depth_scratch = scratch.with_file_name(format!("{stem}_depth"));
|
|
|
+ let depth_args =
|
|
|
+ crate::bench_depth::depth_args(&base_url, &model, a.seed, a.tokenizer.as_ref());
|
|
|
|
|
|
let mut args = vec![
|
|
|
"--base-url".into(),
|
|
|
@@ -261,6 +281,7 @@ pub async fn run(config: &Config, a: &Args, dry_run: bool) -> Result<()> {
|
|
|
if dry_run {
|
|
|
println!("bench: cwd {}", scratch.display());
|
|
|
println!("bench: {} {}", bin, args.join(" "));
|
|
|
+ println!("bench: depth suite: {} {}", bin, depth_args.join(" "));
|
|
|
println!("bench: reports -> {}", reports_dir.display());
|
|
|
println!(
|
|
|
"bench: run package -> {}.recipe.toml + {}.manifest.json",
|
|
|
@@ -276,7 +297,7 @@ pub async fn run(config: &Config, a: &Args, dry_run: bool) -> Result<()> {
|
|
|
crate::readiness::wait_ready(&base_url, crate::readiness::BENCH_WAIT_SECS).await?;
|
|
|
eprintln!("bench: API ready — running {} (seed {})", bin, a.seed);
|
|
|
|
|
|
- let code = run_tool(&bin, &args, &scratch, &log_path)?;
|
|
|
+ let code = run_tool(&bin, &args, &scratch, &log_path, false)?;
|
|
|
if code != 0 {
|
|
|
anyhow::bail!(
|
|
|
"bench: tool exited with {code} (log: {})",
|
|
|
@@ -284,6 +305,29 @@ pub async fn run(config: &Config, a: &Args, dry_run: bool) -> Result<()> {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ // Phase 2 — context-depth suite (16K..256K @ c2, 2 repeats): appended
|
|
|
+ // to the same log so `parse_perf_rows` picks its rows up too. A depth
|
|
|
+ // failure must not cost the finished quality run — warn, keep going.
|
|
|
+ eprintln!(
|
|
|
+ "bench: depth suite: {} @ c{}, {} repeats{}",
|
|
|
+ crate::bench_depth::DEPTHS,
|
|
|
+ crate::bench_depth::CONCURRENCY,
|
|
|
+ crate::bench_depth::RUNS,
|
|
|
+ if a.tokenizer.is_some() {
|
|
|
+ " (+tokenizer)"
|
|
|
+ } else {
|
|
|
+ ""
|
|
|
+ }
|
|
|
+ );
|
|
|
+ fs::create_dir_all(&depth_scratch)?;
|
|
|
+ let dcode = run_tool(&bin, &depth_args, &depth_scratch, &log_path, true)?;
|
|
|
+ if dcode != 0 {
|
|
|
+ eprintln!(
|
|
|
+ "bench: WARNING depth suite exited with {dcode} — main report unaffected ({})",
|
|
|
+ log_path.display()
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
let md_src = latest_md(&scratch)
|
|
|
.ok_or_else(|| anyhow::anyhow!("no report .md under {}", scratch.join("runs").display()))?;
|
|
|
let md_dest = reports_dir.join(format!("{stamp}_{label}.md"));
|
|
|
@@ -404,6 +448,7 @@ mod tests {
|
|
|
],
|
|
|
&tmp,
|
|
|
&log,
|
|
|
+ false,
|
|
|
)
|
|
|
.unwrap();
|
|
|
let body = fs::read_to_string(&log).unwrap();
|
|
|
@@ -414,6 +459,28 @@ mod tests {
|
|
|
assert!(!body.contains('\r'), "pty ONLCR leaked CRLF: {body:?}");
|
|
|
}
|
|
|
|
|
|
+ /// The append mode must not truncate the main phase's log — the depth
|
|
|
+ /// suite shares it so one report carries both tables.
|
|
|
+ #[test]
|
|
|
+ fn run_tool_append_keeps_prior_output() {
|
|
|
+ let tmp =
|
|
|
+ std::env::temp_dir().join(format!("sm121-bench-append-test-{}", std::process::id()));
|
|
|
+ fs::create_dir_all(&tmp).unwrap();
|
|
|
+ let log = tmp.join("append.log");
|
|
|
+ let run =
|
|
|
+ |a: &str, append| run_tool("sh", &["-c".into(), a.into()], &tmp, &log, append).unwrap();
|
|
|
+ assert_eq!(run("echo main", false), 0);
|
|
|
+ assert!(fs::read_to_string(&log).unwrap().contains("main\n"));
|
|
|
+ assert_eq!(run("echo depth", true), 0);
|
|
|
+ let body = fs::read_to_string(&log).unwrap();
|
|
|
+ let (i1, i2) = (body.find("main\n"), body.find("depth\n"));
|
|
|
+ assert!(
|
|
|
+ i1.is_some() && i2.is_some_and(|d| i1.unwrap() < d),
|
|
|
+ "append truncated the log: {body:?}"
|
|
|
+ );
|
|
|
+ let _ = fs::remove_dir_all(&tmp);
|
|
|
+ }
|
|
|
+
|
|
|
#[test]
|
|
|
fn served_model_parsed_from_args() {
|
|
|
let content = fs::read_to_string("recipes/glm53-flash-r27-spark/recipe.toml")
|