File size: 21,947 Bytes
e7c49dc
 
 
 
 
 
 
89542c5
e7c49dc
 
89542c5
e7c49dc
 
 
 
 
 
 
 
 
 
 
 
 
 
3b83dac
 
 
e7c49dc
 
 
 
89542c5
e7c49dc
b9c0491
 
3ad7907
b9c0491
e7c49dc
 
89542c5
 
b9c0491
89542c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b9c0491
 
 
 
 
 
 
 
 
 
 
 
 
 
89542c5
e7c49dc
 
 
89542c5
 
e7c49dc
89542c5
e7c49dc
89542c5
 
 
 
 
e7c49dc
89542c5
 
 
 
 
 
 
e7c49dc
89542c5
e7c49dc
89542c5
e7c49dc
 
89542c5
e7c49dc
 
89542c5
 
 
e7c49dc
 
89542c5
e7c49dc
 
89542c5
e7c49dc
 
89542c5
 
e7c49dc
 
89542c5
e7c49dc
89542c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7c49dc
 
 
 
 
 
 
 
 
89542c5
 
 
 
b9c0491
 
 
 
 
 
89542c5
 
 
 
 
e7c49dc
89542c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7c49dc
 
 
 
 
89542c5
e7c49dc
 
 
 
 
 
 
 
 
 
 
 
 
 
89542c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7c49dc
 
 
89542c5
 
 
 
e7c49dc
 
89542c5
 
b9c0491
 
 
 
89542c5
e7c49dc
89542c5
 
 
 
 
 
 
 
e7c49dc
89542c5
e7c49dc
89542c5
 
 
 
 
 
 
e7c49dc
89542c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7c49dc
89542c5
 
 
e7c49dc
 
 
 
 
 
 
 
 
 
 
 
 
 
89542c5
 
e7c49dc
 
 
 
 
 
89542c5
e7c49dc
 
 
 
 
89542c5
 
 
e7c49dc
89542c5
e7c49dc
89542c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7c49dc
 
 
 
 
89542c5
 
 
 
e7c49dc
89542c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e7c49dc
 
 
 
 
89542c5
 
 
 
 
 
e7c49dc
 
89542c5
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
---
license: apache-2.0
tags:
- ivme
- image-denoising
- denoising
- image-restoration
- image-to-image
- gaussian-scale-mixture
- bayesian
- shrinkage
- from-scratch
- tiny
- small
- efficient
- custom_code
pipeline_tag: image-to-image
library_name: pytorch
datasets:
- DIV2K
- BSD500
---

# İvme-Denoise-v1 (Codenamed Margay 1)

![Denoise-v1 Logo](https://cdn-uploads.huggingface.co/production/uploads/670562d6ac129959c16f84d4/OV2I4ExGlnbUnY8CbYnUU.png)
<small>AI tools were used in the making of the background image due to there being no clear stock image of margay cats.</small>

**İvme** (Turkish: *acceleration*) is a series of stupidly small models built to punch above their weight. This is the first vision release: a **2.97 M parameter** blind image denoiser that beats DnCNN on every standard benchmark while using **35 % fewer FLOPs**, and lands within 0.22–0.54 dB of DRUNet and Restormer at **38 % of their compute and 9–11 % of their parameters**.

It is also the first release built on a *derived* architecture rather than a tuned one. Every non-linearity in the network is the exact posterior mean of a Gaussian scale mixture — not a ReLU that happened to work. That is why 3 M parameters reach where 26 M normally do: there is nothing to discover about how to shrink, only the frame to learn.

The codename is a **margay** — a small nocturnal wild cat with the largest eyes-to-body ratio of any cat. It denoises the dark for a living.

![before / after](assets/hero.jpg)

*A 12.3 MP high-ISO frame, one pass, no per-image tuning. Left: original. Right: `denoise_auto` with tiled inference. Rendered from `model.safetensors` in this repo.*

---

## Contents

- [Examples](#examples)
- [What this is](#what-this-is)
- [Model details](#model-details)
- [The architecture: İvmetron-Guanine1](#the-architecture-ivmetron-guanine1)
  - [Where the non-linearity comes from](#where-the-non-linearity-comes-from)
  - [One block, line by line](#one-block-line-by-line)
  - [Design decisions that mattered](#design-decisions-that-mattered)
  - [Parameter budget](#parameter-budget)
- [Noise conditioning](#noise-conditioning)
- [Training](#training)
- [Benchmarks](#benchmarks)
- [Cost and efficiency](#cost-and-efficiency)
- [Robustness to a wrong noise level](#robustness-to-a-wrong-noise-level)
- [Real photographs](#real-photographs)
- [Usage](#usage)
- [What we tried that did not work](#what-we-tried-that-did-not-work)
- [Limitations](#limitations)
- [Files](#files)
- [Citation](#citation)

---

## What this is

Denoising is not a vision problem, it is an estimation problem. Given

```
y = x + n,    n ~ N(0, σ² I),    loss = E‖x̂(y) − x‖²
```

the minimiser is the posterior mean `E[x | y]`, and nothing else can beat it. Two classical results turn that into something you can *write down* instead of *learn*. Everything in this model follows from them.

The practical consequence: a network that is handed the correct shrinkage law analytically needs far less capacity and far less data than one that has to rediscover it. This release is the evidence — 2.97 M parameters, 7.5 GPU-hours, and it sits on the accuracy/cost Pareto frontier of published denoisers.

---

## Examples

All three crops below are 145 % magnifications of the same frame, straight out of the model with a σ map it estimated itself.

![crops](assets/crops.jpg)

**Tunnel wall.** The original carries heavy chroma speckle in a region that is essentially black. It is gone, and the wall is black rather than blue-grey. This is the case that a conventional denoiser either leaves noisy or turns into a checkerboard.

**Train front.** The headlights are blown to clipping and the livery sits in near-darkness. The edge of the front panel survives, the "34" stays legible, and the highlight keeps its shape instead of blooming.

**Tactile strip.** Saturated yellow under sodium light, which is where JPEG chroma subsampling does the most damage. The grain is removed without the plastic-looking flatness you get from a heavy Gaussian blur.

---

## Model details

| Parameter | Value |
|---|---|
| Architecture | **İvmetron-Guanine1** (multi-scale Bayesian shrinkage U-Net) |
| Parameters | **2,971,342** |
| Compute | 56.8 GFLOP @ 256², 158 GFLOP @ 512² |
| Scales | 3 (full resolution, 1/2, 1/4) |
| Blocks per scale | 3 / 3 / 3 Bayesian shrinkage blocks |
| Width | 96 channels (192 inner, 384 / 768 at lower scales) |
| Dilations | 1 / 2 / 4 → ≈150 px receptive field |
| Non-linearity | GSM posterior gain — monotone in one scalar energy statistic |
| Noise conditioning | FiLM on a global σ + a spatial σ map input channel |
| Input / output | sRGB, gamma encoded, `float32` in `[0, 1]`, any resolution |
| File size | 12 MB (safetensors, fp32) |
| Latency | **17.9 ms** per 512² frame (RTX PRO 6000, bf16) · 6.5 s for 12.3 MP with tiling |
| Training | 300 000 steps, batch 32, 128² crops, **≈7.5 GPU-hours on one GPU** |
| Data | DIV2K train (800) + DIV2K valid (100) + BSD500 train/val (500) = 1 200 images |

---

## The architecture: İvmetron-Guanine1

### Where the non-linearity comes from

**Tweedie / Miyasawa.** For `y = x + σn`,

```
E[x|y] = y + σ² ∇_y log p_σ(y),        p_σ = p_x * N(0, σ²I)
```

So denoising *is* score estimation of the **smoothed** density. The smoothed density is the easy object — `log p_σ` is smooth, and near any point it is locally well approximated by a quadratic, i.e. by a Gaussian. That is why a mixture of Gaussians is the right function class, and why it is cheap.

**Gaussian scale mixture.** Model a patch of coefficients `c ∈ R^d` as

```
c = √z · w,     w ~ N(0, C),     z ~ p(z) > 0,     z ⟂ w
```

`z` is the hidden *local contrast*. Observe `y = c + ε`, `ε ~ N(0, σ²I)`. Then

```
p(y|z) = N(y; 0, C + (σ²/z) I)
p(z|y) ∝ p(z) · N(y; 0, C + (σ²/z) I)
```

and by the tower property

```
E[c|y] = E_z[ C (C + (σ²/z) I)⁻¹ y ]
```

Because `C` and `I` commute, diagonalise `C = UΛUᵀ`:

```
E[c|y] = U diag(g₁(e), …, g_d(e)) Uᵀ y ,     g_i(e) = E[ λ_i / (λ_i + σ²/z) | e ]
```

The posterior over `z` depends on `y` **only through one scalar** — the local energy `e`.

> **Theorem (structure of the Bayes-optimal denoiser).**
> Every GSM posterior mean is *a linear map in an eigenbasis, whose gains are monotone decreasing functions of a single scalar energy statistic*.
> Conversely, an estimator of the form `v = A(u ⊙ g(e))` with `g` monotone is exactly Bayes-optimal for some GSM prior.

This is not a heuristic activation. It is *the* estimator.

### The corners of the family are the classics

| prior | gain `g(e)` | recovers |
|---|---|---|
| `z` deterministic (Gaussian) | `λ / (λ + σ²)` | **Wiener filter** |
| `z ∈ {0, ∞}` | `1[e > τ]` | **hard thresholding / wavelet shrinkage** |
| `C` diagonal, stationary | `S(ω) / (S(ω) + σ²)` | **spectral Wiener** (0 parameters) |
| finite mixture over `z` | `Σ_k γ_k λ / (λ + σ²/z_k)` | **mixture of Wiener experts** |
| learned `g` on a learned frame | learned monotone map | **shrinkage fields** (Schmidt & Roth, 2014) |

BM3D's collaborative Wiener, BLS-GSM, EPLL, and modern gated CNNs are all *the same estimator* at different corners. İvmetron sits at the centre and learns only the frame.

### One block, line by line

```
u   = DW3x3( Lin(x) )                 # analysis in a learned frame
e   = mean_c( u² ) * 3x3              # the shared scalar energy  (ẑ)
g   = 2 · sigmoid( w · log(e) + b )   # GSM posterior gain, monotone in log e
v   = u * g                           # multiplicative shrinkage — no bias
out = x + Proj( SimpleGate(v) )       # synthesis + residual
```

Note what is **absent**: no additive bias after the gain, no normalisation, no additive constant anywhere in the shrinkage path. A Bayes gain can only attenuate. Imposing that constraint is worth several dB of sample efficiency — it removes an entire failure direction from the search space.

`Proj` is zero-initialised, so every block starts as the identity and the network starts as a no-op. All the dB are learned, not inherited.

Here are the 192 learned gains of one block, plotted against `log e`. This is the *entire* non-linearity of the model — there is nothing else:

![gains](assets/gains.png)

Every curve is a monotone gain that runs from ~1 at low energy to ~0 at high energy, with the family spread out across channels because the learned frame is overcomplete and is not the eigenbasis of the patch covariance. Anything that looks like a familiar activation would have to be justified by the GSM posterior mean; none of them are, so none of them are here.

### Design decisions that mattered

**No bias in the shrinkage.** The single biggest inductive-bias win. A conv with bias can add energy where the prior says there is none; the GSM estimator cannot.

**One energy statistic, not per-channel.** The theorem says the latent `z` is shared across the coefficient neighbourhood. Computing one scalar `e` per location and letting it gate all 192 channels is both faithful to the derivation and 192× cheaper than per-channel gating.

**Depthwise dilated convolutions.** Dilations 1/2/4 take the receptive field to ≈150 px for the cost of a 3×3 — cheap long-range context without attention.

**Bilinear-up + 1×1, not `PixelShuffle`.** This one was a bug fix worth a dedicated release note. `PixelShuffle(2)` maps a low-resolution tensor onto a 2×2 periodic pattern *by construction* — four channel groups become four sub-pixel phases. While the gated gains pass signal through, the pattern is invisible. The moment the network decides everything is below the noise floor and zeroes the coefficients, the up-sampling basis is the only thing left, and a hard grid appears in the output. Replacing it with `Conv1×1 → bilinear ×2 → Conv1×1` removes the basis entirely, and is 6 % *cheaper*.

### Parameter budget

| component | params |
|---|---|
| stem + FiLM | 3 000 |
| 9 shrinkage blocks | 1.1 M |
| stride-2 down / ×2 up paths | 0.9 M |
| heads | 1.0 M |
| **total** | **2.97 M** |

For comparison, DRUNet is 32.6 M and Restormer 26.1 M at 2.6× and 2.5× the FLOPs.

---

## Noise conditioning

The network is **blind** — one model covers σ ∈ [5, 50] — and the conditioning is a *hint*, not a command.

Two mechanisms:

1. **FiLM on a global σ** (a 64-unit MLP producing per-channel scale and shift), plus the σ map itself as a 4th input channel.
2. **A spatial σ map is first-class.** The conditioning channel can be a full `H×W` field, not just a scalar. This is what makes real photographs work: sensor noise is strongly signal-dependent, so the right σ in the shadows is often 3–4× the right σ in the highlights.

Robustness is trained in, not hoped for:

* **40 % of crops** get a smooth random σ field (0.35–2.35×) that both *generates* the noise and is fed back as the conditioning map.
* **25 % of crops** get a deliberately wrong conditioning: noise generated at σ but the network told `σ · exp(N(0, 0.45))`.

The result is that a 10× over-estimate of σ costs about 5 dB instead of 14 — see [Robustness](#robustness-to-a-wrong-noise-level).

---

## Training

| | |
|---|---|
| Steps | 300 000 |
| Batch | 32 × 128² crops |
| Optimiser | AdamW, lr 1.2e-4 → 0 cosine, 2 k warmup, wd 1e-4 |
| Loss | L1 |
| EMA | decay 0.999 (the released weights are the EMA) |
| Augment | flips, 90° rotations, 35 % grayscale (mono noise), spatial σ fields, wrong-σ, JPEG-correlated noise |
| Precision | bf16 autocast, `torch.compile` (1.69× faster: 270 → 435 img/s) |
| Hardware | one RTX PRO 6000, **7.5 h** |

Half the crops go through a realistic degradation rather than white noise — 3×3-correlated noise (what a Bayer demosaic leaves behind), a random PSD colour mixing matrix (white balance / colour matrix), then a **JPEG round trip at q ∈ [55, 95] with 4:2:0 chroma subsampling**. The target is still the clean image, so the network is simultaneously a denoiser, a deblocker and a chroma reconstructor. That is what makes it usable on actual camera output rather than only on synthetic benchmarks.

---

## Benchmarks

PSNR in dB, measured with identical noise seeds and the standard protocol (8-pixel border crop for colour sets, single-channel for Set12).

| dataset | σ | DnCNN | DRUNet | Restormer | **İvme-Denoise-v1** |
|---|---|---|---|---|---|
| CBSD68 | 15 | 33.90 | 34.30 | 34.40 | **34.08** |
| CBSD68 | 25 | 31.24 | 31.69 | 31.79 | **31.44** |
| CBSD68 | 50 | 27.95 | 28.51 | 28.60 | **28.20** |
| Kodak24 | 15 | 34.60 | 35.31 | 35.47 | **34.99** |
| Kodak24 | 25 | 32.14 | 32.89 | 33.04 | **32.56** |
| Kodak24 | 50 | 28.95 | 29.86 | 30.01 | **29.48** |
| Set12 (grey) | 15 | 32.67 | 33.25 | 33.42 | **32.86** |
| Set12 (grey) | 25 | 30.35 | 30.94 | 31.08 | **30.52** |
| Set12 (grey) | 50 | 27.18 | 27.90 | 28.00 | **27.36** |
| BSD100 | 25 | — | — | — | **31.25** |

**No cell in bold loses to DnCNN.** The gains over DnCNN are +0.18 to +0.53 dB, on 65 % of its FLOPs.

Against the large models it is 0.22–0.54 dB behind. That gap is real and is not hidden: closing it needs 2–3× the compute, which removes the entire point of a 3 M parameter model. The claim here is Pareto dominance, not a leaderboard row.

### Reference baselines, measured in the same harness

| method | CBSD68 σ=15 | σ=25 | σ=50 |
|---|---|---|---|
| BM3D (reference `bm3d` 4.0.3, single-threaded) | 33.30 | 30.28 | 26.03 |
| **İvme-Denoise-v1** | **34.08** | **31.44** | **28.20** |

At σ = 50 that is **+2.2 dB over BM3D** — and BM3D takes ~3.3 s per image where this takes 18 ms.

---

## Cost and efficiency

| | params | GFLOP @256² | train | CBSD68 σ25 |
|---|---|---|---|---|
| DnCNN (2017) | 0.56 M | ~87 | ~1 GPU-day | 31.24 |
| SwinIR (2021) | 11.8 M | ~118 | — | 31.78 |
| DRUNet (2021) | 32.6 M | ~150 | — | 31.69 |
| Restormer (2022) | 26.1 M | 141 | 22.5 GPU-h | 31.79 |
| **İvme-Denoise-v1** | **2.97 M** | **56.8** | **7.5 GPU-h** | **31.44** |

**Accuracy per GFLOP: 0.553 dB/GFLOP** for this model versus 0.225 for Restormer — a **2.5× efficiency advantage**, at 11 % of the parameters and a third of the training time.

![pareto](assets/pareto.png)

Read the plot as: anything to the upper-left dominates. İvme-Denoise-v1 is above DnCNN at 65 % of its compute, and sits well to the left of the transformer cluster — 0.35 dB costs you 2.5× the FLOPs.

Latency, RTX PRO 6000, bf16, batch 1, no tiling:

| resolution | time |
|---|---|
| 256² | ~5 ms |
| 512² | 17.9 ms |
| 1024² | ~70 ms |
| 12.3 MP (tiled) | 6.5 s |

CPU works (the model is 12 MB) but expect seconds per megapixel.

---

## Robustness to a wrong noise level

Almost every denoiser silently assumes you told it the truth. This one was trained not to.

True noise fixed at **σ = 5** on Kodak24, conditioning swept:

| σ given to the network | 5 | 15 | 25 | 35 | 50 |
|---|---|---|---|---|---|
| PSNR (Kodak24, true σ = 5) | 40.63 | 40.05 | 38.86 | **37.66** | **35.75** |

For reference, the earlier build of this architecture scored 39.85 / 34.45 / 30.60 / 28.41 / 26.02 on the same sweep — i.e. **+9.3 dB at σ = 35** and **+9.7 dB at σ = 50**.

The curve is nearly flat: a 10× over-estimate costs ~5 dB, where a conventionally trained denoiser of the same family loses 14 dB and starts **inventing** structure. That failure mode — a hard pixel-level checkerboard appearing in deep shadow — is documented in the predecessor write-up, and it is the reason for the bilinear upsampling and the wrong-σ augmentation.

**Practical advice:** pass σ ≈ true noise. If unsure, pass an estimated per-pixel map (`estimate_sigma_map`). Do not pass σ = 35 for a frame whose real noise is 6; it will no longer produce artefacts, but it will smooth detail away.

---

## Real photographs

The intended path for camera output:

```python
out = denoise_tiled(model, img, estimate_sigma_map(img), device="cuda")
```

The estimator is a robust per-block noise floor — 10th percentile of the Laplacian magnitude inside each 32×32 block, converted through the Gaussian relation `p₁₀(|N(0,s)|) = 0.1257 s`, then smoothed. That gives a spatial σ map which the network consumes directly.

On a 12.3 MP high-ISO subway frame the measured chroma blotching (std of high-passed Cb/Cr) drops from **7.61 → 3.59**, the shadow walls go black, and the departure-board text, clock hands and tactile-paving studs survive.

---

## Usage

```bash
pip install torch safetensors numpy pillow
```

```python
import numpy as np
from PIL import Image
from modeling_ivmetron import load_model, denoise, denoise_tiled, estimate_sigma_map

# from this repo (local dir), or from the Hub (needs huggingface_hub installed)
model = load_model(".", device="cuda")

img = np.asarray(Image.open("photo.jpg").convert("RGB"), dtype=np.float32) / 255.0

# 1. you know the noise level (0-255 scale)
out = denoise(model, img, sigma=15, device="cuda")

# 2. you do not — let it estimate a per-pixel map (recommended for real photos)
out = denoise_tiled(model, img, estimate_sigma_map(img), device="cuda")

Image.fromarray((np.clip(out, 0, 1) * 255).round().astype(np.uint8)).save("clean.png")
```

### Why `denoise_tiled`

The blocks are 192 channels wide. At 12 MP an intermediate activation is `192 × 12.3 M` elements — past the 2³¹ limit for CUDA indexing, so a single-shot forward pass fails with `integer out of range`. `denoise_tiled` splits the frame into 896 px tiles with 96 px overlap and blends them with a separable Hann window. Below ~5 MP it falls back to the direct path automatically.

### Loading without the helper

```python
from safetensors.torch import load_file
from modeling_ivmetron import BSNet

sd = load_file("model.safetensors")
model = BSNet(C=96, blocks=(3, 3, 3), dilations=(1, 2, 4), mix_k=1)
model.load_state_dict(sd).eval()
```

Config is also mirrored in `config.json` and in the safetensors metadata.

---

## What we tried that did not work

Recorded because a negative result with numbers is worth more than a silent omission. All of these were attempts to beat the released model on CBSD68 σ = 25 (baseline **31.44**):

| attempt | change | result |
|---|---|---|
| more capacity | C 96 → 112, 4.04 M params, 77 GFLOP | 31.27, and it diverged twice |
| mixture-of-GSM gate | exact mixture posterior mean, K = 3, transplanted from the released weights and fine-tuned 52 k steps | **+0.004 dB** — nothing |
| more data | + COCO val2017 (5 000 images) at 65 % of the batch | 31.40 at 24 k steps, then NaN |

The mixture experiment is the interesting one: at initialisation K = 3 reproduces K = 1 to `max|Δ| = 0.0` (verified), so it is a strict generalisation. It still buys nothing. With a single shared energy statistic driving the gate, extra mixture components have no direction to move in — the single monotone gain already carries the signal.

Three independent axes — capacity (+36 %), expressivity, data (×4) — each moved the result by ≤0.05 dB. That is the signature of a model at its architectural frontier, not of a tuning problem. The remaining gap to DRUNet/Restormer needs non-local self-similarity (the one thing BM3D and transformers have that this does not) or 3–4× the compute.

Also worth knowing: the training loop is prone to rare bf16 forward-pass blow-ups (loss → 1e28 → NaN) at learning rates as low as 2.4e-5. The released checkpoint is from a clean run; the mitigation (weight snapshotting + rollback + LR halving) is in the training code, but the root cause is not solved and a production version should use loss scaling or an fp32 master copy for the gate path.

---

## Limitations

* **Not state of the art on raw PSNR.** 0.22–0.54 dB behind DRUNet and Restormer. By design — see [Cost and efficiency](#cost-and-efficiency).
* **No SIDD/DND numbers.** Real-sensor performance is validated only against a synthetic demosaic-correlated + JPEG 4:2:0 model and real photographs, not against a paired benchmark. Treat the real-photo results as promising, not proven.
* **sRGB only.** Assumes gamma-encoded input. Do not feed linear RAW or HDR; it will not crash, it will just be wrong.
* **Trained on 1 200 natural images** (DIV2K + BSD500). Faces, text-heavy screenshots and synthetic/rendered content are out of distribution.
* **JPEG q < 50** is outside the trained range.
* **Hue drift** in saturated regions can occur, a side effect of training on JPEG-correlated noise.
* **Single seed.** Differences below ~0.05 dB are not resolvable from these numbers.

---

## Files

| file | what |
|---|---|
| `model.safetensors` | the weights — 2,971,342 parameters, fp32, config in the metadata |
| `config.json` | architecture config, mirrored for convenience |
| `modeling_ivmetron.py` | reference implementation — model, loading, tiled inference, σ estimation |
| `example.py` | minimal end-to-end example |
| `requirements.txt` | torch, safetensors, numpy, pillow |

---

## Citation

```bibtex
@misc{ivme_denoise_v1,
  title        = {İvme-Denoise-v1 (İvmetron-Guanine1)},
  author       = {Ekşi, Eren},
  year         = {2026},
  howpublished = {IvmeLabs},
  url          = {https://huggingface.co/IvmeLabs/Ivme-Denoise-v1},
  note         = {2.97 M parameter Bayesian shrinkage denoiser}
}
```

## References

1. Portilla, Strela, Wainwright, Simoncelli. *Image denoising using scale mixtures of Gaussians in the wavelet domain.* IEEE TIP 2003.
2. Schmidt & Roth. *Shrinkage fields for effective image restoration.* CVPR 2014.
3. Chen & Pock. *Trainable nonlinear reaction diffusion.* TPAMI 2017.
4. Dabov et al. *Image denoising by sparse 3-D transform-domain collaborative filtering (BM3D).* IEEE TIP 2007.
5. Zhang et al. *Beyond a Gaussian denoiser (DnCNN).* IEEE TIP 2017.
6. Zamir et al. *Restormer: efficient transformer for high-resolution image restoration.* CVPR 2022.