swactor/crates/mvp-system/tests/python_worker_protocol.rs
Zachery Aaron Shores-Chmielewski 504c2d13ad feat: mvp-chat benchmarking
Add end-to-end timing instrumentation and an xtask benchmark report for mvp-chat runs.

- benchmark_observability: add a shared stamping module — stamp(component) emitting schema/pid/monotonic+wall ms from a process-global start and sequence counter, plus unix_ms_now() — stamped onto every mvp-chat/orchestrator/worker-node event and frame-archive record
- mvp-chat: thread a run_id (new --run-id, defaults to 1) through config and the orchestrator CLI, add per-phase started/ready/failed emits for ensure_orch_binary/ensure_worker_binary/prepare_node_image, and a prompt_complete record carrying tokens_generated/elapsed_ms/final_text bytes
- orchestrator/worker-node: stamp bootstrap and prompt events, add arrival_unix_ms to archived frames, propagate MVP_RUN_ID/MVP_LOGICAL_NODE_ID/MVP_STAGE_INDEX into the tinygrad worker, default the device to CPU for the process provider, emit a prompt_rpc started span, and drop the MVP_TINYGRAD_TEST_MODE passthrough
- tinygrad_worker.py: stamp every control() event and tag it with run/node/stage env, add a CPU:X86 fallback when clang is absent, and remove the test_mode() short-circuits
- xtask: replace the flat dump-log fact assertions with a benchmark report builder (build_benchmark_report) that requires named spans (prepare_runtime, ensure_*_binary, weights_loaded, prompt_rpc) and emits per-prompt first-token/decode/tokens-per-second latency; wrap the cargo run in XtaskBenchmark synthetic frames and pass a unix-ms --run-id

Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-23 09:35:04 +04:00

132 lines
4 KiB
Rust
Executable file

#![cfg(target_os = "linux")]
use std::io::{BufRead, BufReader, Write};
use std::process::{Child, ChildStdin, Command, Stdio};
use serde_json::{Value, json};
struct WorkerProcess {
child: Child,
stdin: ChildStdin,
stdout: BufReader<std::process::ChildStdout>,
}
impl WorkerProcess {
fn spawn() -> Self {
let script = worker_script();
let mut child = Command::new("python3")
.arg(&script)
.env("DEV", "CPU")
.env("MVP_RUN_ID", "9")
.env("MVP_LOGICAL_NODE_ID", "3")
.env("MVP_STAGE_INDEX", "2")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap_or_else(|e| panic!("spawn {}: {e}", script.display()));
let stdin = child.stdin.take().expect("worker stdin");
let stdout = BufReader::new(child.stdout.take().expect("worker stdout"));
Self {
child,
stdin,
stdout,
}
}
fn send_collect_until(&mut self, command: Value, expected_type: &str) -> Vec<Value> {
writeln!(self.stdin, "{command}").expect("write worker command");
self.stdin.flush().expect("flush worker command");
let mut events = Vec::new();
loop {
let mut line = String::new();
let read = self.stdout.read_line(&mut line).expect("read worker event");
assert_ne!(read, 0, "worker exited before {expected_type}");
let event: Value = serde_json::from_str(line.trim_end()).expect("worker event JSON");
let actual_type = event.get("type").and_then(Value::as_str).unwrap_or("");
assert_ne!(
actual_type, "WorkerFatal",
"worker fatal while waiting for {expected_type}: {event}"
);
let done = actual_type == expected_type;
events.push(event);
if done {
return events;
}
}
}
}
impl Drop for WorkerProcess {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
#[test]
fn benchmark_observability_worker_ready_includes_stamps_and_identity() {
if !tinygrad_available() {
eprintln!("skipping Python worker protocol check: tinygrad is unavailable");
return;
}
let mut worker = WorkerProcess::spawn();
let events = worker.send_collect_until(
json!({"type":"InitializeWorker","helper_abi_version":1,"backend":{"device":"CPU"}}),
"WorkerReady",
);
assert!(
events
.iter()
.any(|event| event.get("type").and_then(Value::as_str) == Some("TinygradImportStarted")),
"expected tinygrad import milestone in {events:?}"
);
assert!(
events
.iter()
.any(|event| event.get("type").and_then(Value::as_str) == Some("WorkerReady")),
"expected WorkerReady in {events:?}"
);
for event in &events {
assert_eq!(
event
.get("benchmark")
.and_then(|benchmark| benchmark.get("schema"))
.and_then(Value::as_u64),
Some(1),
"{event}"
);
assert_eq!(
event.get("run_id").and_then(Value::as_u64),
Some(9),
"{event}"
);
assert_eq!(
event.get("node_id").and_then(Value::as_u64),
Some(3),
"{event}"
);
assert_eq!(
event.get("stage_index").and_then(Value::as_u64),
Some(2),
"{event}"
);
}
}
fn tinygrad_available() -> bool {
Command::new("python3")
.args(["-c", "import tinygrad"])
.status()
.is_ok_and(|status| status.success())
}
fn worker_script() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("apps/mvp-node/tinygrad_worker.py")
.canonicalize()
.expect("tinygrad worker script exists")
}