No jobs
Advanced 11 min 3 of 4

One encoder, two decoders

Not two models. One pretrained encoder whose skips are consumed twice — and in the default mode it runs twice, which is where the cost is.

What this repairs

Treating multi-task output as multiple models, when it is one shared encoder whose skips are consumed twice.

A UNet is an encoder, a bottleneck, and a decoder that consumes the encoder's skip connections on the way back up. The joint model splits it at the bottleneck: the first convolution, the timestep embedding, the down blocks and the mid block are shared; the up blocks, the output normalisation and the output convolution exist twice.

The dehazing decoder keeps the pretrained up blocks. The depth decoder is a deep copy of them — not a random initialisation. Both branches therefore start as the same pretrained image decoder and diverge only under training, which is the whole reason a depth decoder trained on a modest dataset works at all: it begins already knowing how to turn latent features into spatially coherent output, and only has to learn what to put there.

What sharing actually saves

  1. Cdual=2E+2DC_{\text{dual}} = 2E + 2D

    Two encoder passes and two decoder passes per sampler step, where E and D are the cost of one encoder and one decoder pass. The encoder is not amortised across tasks at all in this mode — only its parameters are.

  2. Csingle=E+2DC_{\text{single}} = E + 2D

    One encoder pass, both decoders. The saving is exactly one encoder pass per step — not half the model, which is the intuition the phrase "shared encoder" invites.

  3. CsingleCdual=E+2D2E+2D\frac{C_{\text{single}}}{C_{\text{dual}}} = \frac{E + 2D}{2E + 2D}

    The ratio, left in symbols because the split between encoder and decoder cost is an architectural fact rather than something this page should assert. Measure it before quoting it.

  4. E=D    34(25% saved)E = D \;\Longrightarrow\; \frac{3}{4} \quad (25\% \text{ saved})

    For a roughly symmetric UNet the saving is a quarter, not a half. Worth having in mind before treating single-pass as an optimisation: it also forces one skip set to serve two tasks, which is a modelling change and not only a cost change. In parameters the split is measurable and it is not symmetric: summed off the shipped SD2.1 checkpoint, the decoder is 59 % of the UNet and the shared trunk 41 %, so the joint model comes to 1.38 billion parameters against 1.73 billion for two separate single-task models — 20 % fewer, not half.

What sharing an encoder bets is that the two tasks want the same features.

For these two tasks that is a strong bet, and it is worth stating explicitly rather than assuming. Dehazing needs to know where surfaces are, how far away they are and how thick the haze is along each ray — because the haze thickness at a pixel is a function of its distance. Depth estimation needs the same three things. The tasks are not merely compatible; the physical model that relates them is the one in the fog topic, and the shared encoder is that relationship expressed as an architecture.

The failure mode is negative transfer, and it is what happens when the bet is wrong: two tasks that want different features contend for one encoder, and both end up worse than two separate models would have been. This is not hypothetical — the same repository ships optional per-task skip adapters, zero-initialised so they are the identity at the start, whose entire purpose is to let each decoder re-specialise the shared skips when they turn out not to suit it. They are off by default, which is the honest way to ship a mitigation for a problem you do not currently have.

Check yourself

Single-pass runs the shared encoder once instead of twice. Does that make single-task inference cheaper on a single-pass model?

Show answer

No, and the model logs a message saying so, which is a good sign that this catches people.

A single-pass model has a twelve-channel first convolution, so it is shape-incompatible with the eight-channel single-task forward path. Asking it for one task routes through the full joint denoising loop — one shared twelve-channel encode per step, both decoders — and returns the branch you asked for, discarding the other. You pay for both tasks and use one.

The saving is real only when you consume both outputs from the one shared trajectory. That is the general shape of the trap with multi-task architectures: the efficiency argument is about the joint workload, and it silently inverts the moment the deployment only wants one of the outputs.

Euler View - ML Experiment Monitor