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`. /// The cache entry for `bytes` compiled on `rung`.
pub fn key(rung: Rung, bytes: &[u8]) -> String { 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. /// Where QNN's compiled context for `bytes` lives.
+38 -11
View File
@@ -185,6 +185,9 @@ pub struct Model {
role: Role, role: Role,
form: Form, form: Form,
bytes: Arc<[u8]>, 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. /// 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 /// one run; a scan and a develop click can want the same detector at
/// once, and the second waits on the first. /// once, and the second waits on the first.
pub fn acquire(&self) -> Result<Acquired, Error> { 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 { 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(); api::ensure_installed();
let (rung, cfg) = { let (rung, cfg) = {
let s = state().lock().unwrap(); let s = state().lock().unwrap();
let selected = current_rung(&s); let selected = current_rung(&s);
( (
effective_rung(&s, selected, role, form, bytes), effective_rung(&s, selected, role, form, hash),
s.config.clone(), 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() { if let Some(entry) = registry().lock().unwrap().get(&key).cloned() {
*entry.last_used.lock().unwrap() = Instant::now(); *entry.last_used.lock().unwrap() = Instant::now();
@@ -456,20 +459,26 @@ pub fn form_suffix(form: Form) -> &'static str {
/// this crate existed. /// this crate existed.
pub fn open(role: Role, form: Form, bytes: &[u8]) -> Result<Model, Error> { pub fn open(role: Role, form: Form, bytes: &[u8]) -> Result<Model, Error> {
let bytes: Arc<[u8]> = Arc::from(bytes); let bytes: Arc<[u8]> = Arc::from(bytes);
acquire(role, form, &bytes)?; let hash = engines::hash(&bytes);
Ok(Model { role, form, 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, /// 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). /// 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; let mut rung = selected;
if !rung.serves(role) || 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 // The embedder on a Hexagon device, or an f32 detector where the int8
// sibling was missing: neither can go to the NPU. // sibling was missing: neither can go to the NPU.
rung = rung.fallback(); 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 = rung.fallback();
} }
rung rung
@@ -570,16 +579,34 @@ mod tests {
wanted: 0, wanted: 0,
}; };
assert_eq!( 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 Rung::Cpu
); );
assert_eq!( 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 Rung::Cpu
); );
// An int8 detector whose context is not compiled yet: also the CPU. // An int8 detector whose context is not compiled yet: also the CPU.
assert_eq!( 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 Rung::Cpu
); );
} }
+6 -6
View File
File diff suppressed because one or more lines are too long