Keep the embedder off the Hexagon, and let the probe example ask for a runtime

On the tablet the engine compiled arcface for the NPU: the routing
compared the form a rung wants with the form on offer, and for the
embedder both are f32, so nothing said no. A rung now says which roles
it serves at all, and the Hexagon does not serve the embedder (§7 —
its vectors must compare across devices). Tested at the routing seam.

dr-segment's onnx_probe example still named ort-tract, which is what
stopped the workspace test build.
This commit is contained in:
2026-09-19 16:21:53 +02:00
parent cbbe67fbd7
commit 95c9cffc0d
4 changed files with 50 additions and 7 deletions
+44 -5
View File
@@ -95,12 +95,21 @@ impl Rung {
/// The model form this rung wants for a role.
fn form(self, role: Role) -> Form {
match (self, role) {
(Rung::Hexagon, Role::Embedder) => Form::F32,
(Rung::Hexagon, _) => Form::Int8,
match self {
Rung::Hexagon => Form::Int8,
_ => Form::F32,
}
}
/// Whether this rung runs `role` at all. The Hexagon takes int8 graphs
/// only, and the embedder is never int8 (§7) — it runs on the CPU
/// beside a detector on the NPU, so its vectors compare across devices.
fn serves(self, role: Role) -> bool {
match self {
Rung::Hexagon => role != Role::Embedder,
_ => true,
}
}
}
/// How long a session outlives its last use unless [`Config::decay`] says
@@ -407,7 +416,7 @@ fn current_rung(s: &State) -> Rung {
/// caller adds [`form_suffix`] to the `model_id` it records.
pub fn resolve_model(role: Role, canonical: &Path) -> (PathBuf, Form) {
let rung = current_rung(&state().lock().unwrap());
if rung.form(role) == Form::Int8 {
if rung.serves(role) && rung.form(role) == Form::Int8 {
let sibling = int8_sibling(canonical);
if sibling.is_file() {
return (sibling, Form::Int8);
@@ -451,7 +460,7 @@ pub fn open(role: Role, form: Form, bytes: &[u8]) -> Result<Model, Error> {
/// the form on offer, or a missing engine says one lower (§6 step 4).
fn effective_rung(s: &State, selected: Rung, role: Role, form: Form, bytes: &[u8]) -> Rung {
let mut rung = selected;
if rung.form(role) != form {
if !rung.serves(role) || rung.form(role) != form {
// The embedder on a Hexagon device, or an f32 detector where the int8
// sibling was missing: neither can go to the NPU.
rung = rung.fallback();
@@ -541,6 +550,36 @@ mod tests {
assert_eq!(loaded(), 1);
}
#[test]
fn the_hexagon_never_takes_the_embedder() {
assert!(!Rung::Hexagon.serves(Role::Embedder));
assert!(Rung::Hexagon.serves(Role::Detector));
assert_eq!(Rung::Hexagon.form(Role::Detector), Form::Int8);
// A detector offered in f32 on a Hexagon device lands on the CPU.
let s = State {
config: Config::default(),
cache: Cache {
rung: Some(Rung::Hexagon),
..Cache::default()
},
probing: false,
wanted: 0,
};
assert_eq!(
effective_rung(&s, Rung::Hexagon, Role::Embedder, Form::F32, b""),
Rung::Cpu
);
assert_eq!(
effective_rung(&s, Rung::Hexagon, Role::Detector, Form::F32, b""),
Rung::Cpu
);
// An int8 detector whose context is not compiled yet: also the CPU.
assert_eq!(
effective_rung(&s, Rung::Hexagon, Role::Detector, Form::Int8, b""),
Rung::Cpu
);
}
#[test]
fn the_status_line_reads_as_the_floor_before_init() {
let s = status();