diff --git a/crates/gpu-gates/src/bin/gate_qwen3_serve.rs b/crates/gpu-gates/src/bin/gate_qwen3_serve.rs index 75541df9c..1cc25822e 100644 --- a/crates/gpu-gates/src/bin/gate_qwen3_serve.rs +++ b/crates/gpu-gates/src/bin/gate_qwen3_serve.rs @@ -129,7 +129,14 @@ //! out holds the second slot's fixed stores past the plan's count — the //! open refuses by name and the placed qwen35moe server never listens //! (`placed_slots_load_names_the_split` red); one counted for every slot -//! names twice the planes and the clause is red. `slots_count_their_checkpoints` +//! names twice the planes and the clause is red. +//! `placed_props_engine_placement_is_the_plans`: the placed arm's `/props` +//! `engine.placement` is its plan's — the card, then the host, routed experts +//! on the host exactly when the plan record's `host_experts` is not 0; +//! `props_engine_whole_load_names_no_placement`: +//! the whole-card arm opens with no plan, so it reports none and its line +//! says why. FAIL-first: a seat that reports no placement is red on the +//! placed arm. `slots_count_their_checkpoints` //! reads both servers' stderr: a qwen35moe file's two sequences each pin //! up to the body's budget at the slot's context of checkpoints on the host, and //! the seat's `checkpoints:` line names two sequences and twice the budget @@ -1872,6 +1879,41 @@ mod gate { &named("props_names_the_slot_ctx"), total.is_some_and(|t| n_ctx == t / 2), ); + // `/props`' placement is the plan's when the load placed one (its plan record printed): the + // card, then the host, and routed experts on the host exactly when the record puts them + // there. A whole-card load has no plan: no placement, and the seat's line says why. + let placement = &props["engine"]["placement"]; + match plan.as_ref().and_then(Option::as_ref) { + Some(rec) => { + let devices = placement["devices"].as_array().cloned().unwrap_or_default(); + let host_experts = devices + .get(1) + .and_then(|d| d["classes"]["experts"].as_u64()) + .unwrap_or(0); + println!("{}: props placement {placement}", arm.dir); + check( + ok, + &named("props_engine_placement_is_the_plans"), + devices.len() == 2 + && devices[0]["device"] + .as_str() + .and_then(|d| d.strip_prefix("GPU")) + .is_some_and(|n| n.parse::().is_ok()) + && devices[1]["device"] == "CPU" + && devices[0]["bytes"].as_u64().is_some_and(|b| b > 0) + && u64_of(Some(rec), "host_experts") + .is_some_and(|h| (h > 0) == (host_experts > 0)), + ); + } + None => check( + ok, + &named("props_engine_whole_load_names_no_placement"), + placement.is_null() + && log.contains( + "/props leaves the placement out: a whole-card load opens with no plan", + ), + ), + } // The whole arm's set total is its whole-fit verdict's, held by the // two slots' planes; the placed arm's is the plan's. if let (Total::Set, Some(t)) = (arm.total, total) { diff --git a/crates/gpu-gates/src/bin/shared/serve_seats/qwen3.rs b/crates/gpu-gates/src/bin/shared/serve_seats/qwen3.rs index e8544f0c2..8a80c8709 100644 --- a/crates/gpu-gates/src/bin/shared/serve_seats/qwen3.rs +++ b/crates/gpu-gates/src/bin/shared/serve_seats/qwen3.rs @@ -182,7 +182,7 @@ use bloomery_gpu::model::{GpuModel, MAX_PASS_ROWS, Slots, StepMode}; use bloomery_gpu::{Gpu, Qwen3moeModel}; use bloomery_gpu_gates::bind::{ ChatSurface, Listen, Pass, Seat, SeatEngine, SlotStep, Vocab, arm_screen, bind_server, - note_line, parallel_line, slots_given, + note_line, parallel_line, placement_of, slots_given, }; use bloomery_gpu_gates::generate::Place; use bloomery_gpu_gates::record::{self, Record}; @@ -1443,6 +1443,24 @@ pub fn run(args: &[String]) -> Result { load.print_plan(&levers, vision.as_ref())?; std::process::exit(0); } + // `/props`' placement: a placed load's plan, the one its open makes from the same plan and + // levers; a whole-card load opens with no plan, so it reports none and says so. + let placement = match &load { + Q3Load::Placed { q, total, .. } => { + let plan = q.plan(*total, &plan_levers)?; + placement_of(NAME, plan.machine, &plan) + } + Q3Load::Whole(_) => { + eprintln!( + "{NAME}: /props leaves the placement out: a whole-card load opens with no plan" + ); + None + } + }; + let props = EngineProps { + placement, + ..EngineProps::default() + }; drop(split); let open = path.clone(); let device = match &load { @@ -1456,7 +1474,7 @@ pub fn run(args: &[String]) -> Result { slot_ctx, vocab, device, - EngineProps::default(), + props, 0, )?, _ => SeatEngine::spawn( @@ -1464,7 +1482,7 @@ pub fn run(args: &[String]) -> Result { slot_ctx, vocab, device, - EngineProps::default(), + props, 0, )?, };