Hash a model's bytes once, at open, not on every acquire

A border fill acquires the filler once a tile, and each acquire hashed
the 28 MB model twice — 60 ms a tile, a third of the tile's run on a
throttled TensorRT. The Model keeps its hash from open.
This commit is contained in:
2026-09-19 20:41:21 +02:00
parent 54a80e688c
commit 031ba7b77d
3 changed files with 50 additions and 18 deletions
+6 -1
View File
@@ -29,7 +29,12 @@ pub fn hash(bytes: &[u8]) -> u64 {
/// The cache entry for `bytes` compiled on `rung`.
pub fn key(rung: Rung, bytes: &[u8]) -> String {
format!("{}:{:016x}", rung.label(), hash(bytes))
key_of(rung, hash(bytes))
}
/// The same, from a hash already taken.
pub fn key_of(rung: Rung, hash: u64) -> String {
format!("{}:{:016x}", rung.label(), hash)
}
/// Where QNN's compiled context for `bytes` lives.
+38 -11
View File
@@ -185,6 +185,9 @@ pub struct Model {
role: Role,
form: Form,
bytes: Arc<[u8]>,
/// `engines::hash` of the bytes, taken once: an acquire per tile of a
/// border fill must not hash 28 MB each time.
hash: u64,
}
/// A loaded session, held for one `run` and its output decoding.
@@ -203,7 +206,7 @@ impl Model {
/// one run; a scan and a develop click can want the same detector at
/// once, and the second waits on the first.
pub fn acquire(&self) -> Result<Acquired, Error> {
acquire(self.role, self.form, &self.bytes)
acquire(self.role, self.form, &self.bytes, self.hash)
}
pub fn form(&self) -> Form {
@@ -247,17 +250,17 @@ fn registry() -> &'static Mutex<Registry> {
})
}
fn acquire(role: Role, form: Form, bytes: &Arc<[u8]>) -> Result<Acquired, Error> {
fn acquire(role: Role, form: Form, bytes: &Arc<[u8]>, hash: u64) -> Result<Acquired, Error> {
api::ensure_installed();
let (rung, cfg) = {
let s = state().lock().unwrap();
let selected = current_rung(&s);
(
effective_rung(&s, selected, role, form, bytes),
effective_rung(&s, selected, role, form, hash),
s.config.clone(),
)
};
let key = format!("{role:?}:{}", engines::key(rung, bytes));
let key = format!("{role:?}:{}", engines::key_of(rung, hash));
if let Some(entry) = registry().lock().unwrap().get(&key).cloned() {
*entry.last_used.lock().unwrap() = Instant::now();
@@ -456,20 +459,26 @@ pub fn form_suffix(form: Form) -> &'static str {
/// this crate existed.
pub fn open(role: Role, form: Form, bytes: &[u8]) -> Result<Model, Error> {
let bytes: Arc<[u8]> = Arc::from(bytes);
acquire(role, form, &bytes)?;
Ok(Model { role, form, bytes })
let hash = engines::hash(&bytes);
acquire(role, form, &bytes, hash)?;
Ok(Model {
role,
form,
bytes,
hash,
})
}
/// Where a request lands: the selected rung unless the role's precision rule,
/// 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 {
fn effective_rung(s: &State, selected: Rung, role: Role, form: Form, hash: u64) -> Rung {
let mut rung = selected;
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();
}
if rung.compiles() && !s.cache.compiled.contains(&engines::key(rung, bytes)) {
if rung.compiles() && !s.cache.compiled.contains(&engines::key_of(rung, hash)) {
rung = rung.fallback();
}
rung
@@ -570,16 +579,34 @@ mod tests {
wanted: 0,
};
assert_eq!(
effective_rung(&s, Rung::Hexagon, Role::Embedder, Form::F32, b""),
effective_rung(
&s,
Rung::Hexagon,
Role::Embedder,
Form::F32,
engines::hash(b"")
),
Rung::Cpu
);
assert_eq!(
effective_rung(&s, Rung::Hexagon, Role::Detector, Form::F32, b""),
effective_rung(
&s,
Rung::Hexagon,
Role::Detector,
Form::F32,
engines::hash(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""),
effective_rung(
&s,
Rung::Hexagon,
Role::Detector,
Form::Int8,
engines::hash(b"")
),
Rung::Cpu
);
}
+6 -6
View File
File diff suppressed because one or more lines are too long