Denoise a whole frame in one call where the GPU takes any size

A fixed 1408 tile is exact only in its centre, and Best keeps 896 of
every 1408 it computes: 2.47 photosites of work for each one kept. The
tiler now takes a network of any size as well as a square one, and
plans the frame as the fewest equal tiles under the rung's limit --
one tile, the whole frame and its reflected border, whenever it fits.
If the first call of a plan fails, as a GPU out of memory does, the
kept centre is halved and the frame planned again.

Each shipped network names its any-size sibling (mosaic-best.onnx
beside mosaic-best-1408.onnx). OnnxNet::open takes it where the engine
runs whole frames and the file is installed, and the 1408 tiles
otherwise; open_tiled forces the tiles, and denoise_raw's DR_PLAN=tiles
uses it to compare. The cache key stays on the fixed model: the output
is the same network's. Tests hold any-size tiles, a grid of them and a
plan rebuilt after a failure to the square tiles' answer in every Bayer
phase.
This commit is contained in:
2026-10-06 21:47:19 -04:00
parent 56f4180347
commit 9cba420fd5
6 changed files with 461 additions and 108 deletions
+5 -5
View File
@@ -179,7 +179,7 @@ impl DevelopSession {
iso: self.denoise.iso,
cache_key: self.denoise.file_hash.as_ref().map(|h| h.key(&model)),
model,
halo: net.halo,
net,
cancel,
})
}
@@ -316,8 +316,8 @@ struct Work {
profile: Option<Vec<(f32, f32)>>,
iso: Option<u32>,
model: std::path::PathBuf,
/// The context `model` needs past a tile's kept centre.
halo: usize,
/// The network `model` is: its context and its whole-frame sibling.
net: dr_denoise::Shipped,
cancel: Arc<AtomicBool>,
cache_key: Option<String>,
}
@@ -351,8 +351,8 @@ impl Work {
.map_err(|e| e.to_string())?;
let noise = dr_denoise::noise::for_frame_with(&raw, self.profile.as_deref(), self.iso)
.ok_or("this photograph gives no way to measure its noise")?;
let mut net = dr_denoise::onnx::OnnxNet::from_path(&self.model, self.halo)
.map_err(|e| e.to_string())?;
let mut net =
dr_denoise::onnx::OnnxNet::open(&self.model, self.net).map_err(|e| e.to_string())?;
let rung = net
.rung()
.map(|r| r.label().to_string())
+5 -3
View File
@@ -35,9 +35,11 @@ pub fn init(runtime_dirs: Vec<PathBuf>) {
(Role::EyeClassifier, crate::library::SUNGLASSES_MODEL),
(Role::Inpainter, crate::library::INPAINT_MODEL),
]);
wanted.extend(
[dr_denoise::FAST, dr_denoise::MEDIUM, dr_denoise::BEST].map(|n| (Role::Denoiser, n.file)),
);
let denoisers = [dr_denoise::FAST, dr_denoise::MEDIUM, dr_denoise::BEST];
wanted.extend(denoisers.map(|n| (Role::Denoiser, n.file)));
// Their any-size siblings, which the engine compiles only on a rung
// that runs whole frames (TensorRT; denoise.md §14).
wanted.extend(denoisers.map(|n| (Role::WholeDenoiser, n.whole)));
let models: Vec<(Role, PathBuf)> = wanted
.into_iter()
.filter_map(|(role, name)| Some((role, crate::library::shared_model(name)?)))