Files
DarkRoom/core/dr-denoise/src/onnx.rs
T
dtourolle 14f08a565f Feed the denoise network without making it wait for the CPU
A 20 MP frame spent 0.32 s outside the network: each tile's mosaic and
sigma gathered on one thread, then its 24 MB output copied out of the
runtime and back into the frame, all in series with the device. Tiles are
now gathered on every core by a producer thread one tile ahead, so the
gather overlaps the run; the centre is written back across cores; and the
tile interface hands its inputs over and lends its output, so neither
side is copied. With a stand-in network that does nothing, the tiler's own
time falls to 0.14 s at the 1408 tile and 0.09 s at 2048. The exactness
and Bayer-phase tests are unchanged and pass.
2026-10-04 07:30:09 -04:00

79 lines
2.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! TRACES: FR-DEV-3g
//! The denoise network under the inference engine.
//!
//! The shipped export takes `mosaic` and `sigma`, `1×1×1408×1408`, and
//! returns `rgb`, `1×3×1408×1408` (darkroom-denoise `denoise/export.py`,
//! fixed shape because every model the engine runs is). The engine picks the
//! rung: fp16 on TensorRT and MIGraphX, which measured 0.00 dB from f32; f32
//! on CUDA and the CPU; on the Hexagon the `.a16w16.onnx` sibling, 16-bit
//! activations and weights, 0.00 dB from f32 on the tablet itself where int8
//! lost 5–9 dB (docs/dev/inference.md §1.5). That sibling is the same network
//! with the Bayer packing spelled `SpaceToDepth`, which QNN can hold and the
//! 6-D reshape it replaces it cannot.
use crate::tile::TileNet;
use crate::DenoiseError;
use dr_inference_engine::{Model, Role};
/// The edge of the tile the shipped export takes.
pub const TILE: usize = 1408;
pub struct OnnxNet {
model: Model,
tile: usize,
}
impl OnnxNet {
pub fn from_path(path: &std::path::Path) -> Result<Self, DenoiseError> {
let (path, form) = dr_inference_engine::resolve_model(Role::Denoiser, path);
let bytes = std::fs::read(&path)?;
Ok(OnnxNet {
model: dr_inference_engine::open(Role::Denoiser, form, &bytes)?,
tile: TILE,
})
}
/// Where it runs, for a status line.
pub fn rung(&self) -> Result<dr_inference_engine::Rung, DenoiseError> {
Ok(self.model.acquire()?.rung())
}
}
impl TileNet for OnnxNet {
fn tile(&self) -> usize {
self.tile
}
fn run(
&mut self,
mosaic: Vec<f32>,
sigma: Vec<f32>,
write: &mut dyn FnMut(&[f32]),
) -> Result<(), DenoiseError> {
let n = self.tile;
let shape = ndarray::IxDyn(&[1, 1, n, n]);
// The vectors become the tensors: no copy on the way in.
let m = ort::value::Tensor::from_array(
ndarray::Array::from_shape_vec(shape.clone(), mosaic)
.map_err(|e| DenoiseError::Model(e.to_string()))?,
)?;
let s = ort::value::Tensor::from_array(
ndarray::Array::from_shape_vec(shape, sigma)
.map_err(|e| DenoiseError::Model(e.to_string()))?,
)?;
let acquired = self.model.acquire()?;
let mut session = acquired.lock();
let outputs = session.run(ort::inputs!["mosaic" => m, "sigma" => s])?;
let (shape, data) = outputs[0].try_extract_tensor::<f32>()?;
let dims: Vec<i64> = shape.iter().copied().collect();
if dims != [1, 3, n as i64, n as i64] {
return Err(DenoiseError::Model(format!(
"output is {dims:?}, expected [1, 3, {n}, {n}]"
)));
}
// And none on the way out: the frame is written from the runtime's buffer.
write(data);
Ok(())
}
}