AnyLearn
All lessons

Making Differentiable Rendering Affordable

Correct gradients are useless if computing them exhausts memory. Radiative backpropagation, path replay backpropagation's constant-memory trick, and why differentiable renderers needed their own compiler.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 7

The memory wall

Reverse-mode automatic differentiation, the backpropagation used to train every neural network, works by recording operations on a tape during the forward pass, then walking it backwards.

That is a fine trade for a neural network. A forward pass performs a bounded number of operations, and storing intermediate activations costs memory proportional to the network's size.

Apply the same recipe to a path tracer and the arithmetic becomes alarming. Consider a modest render: one million pixels, 128 samples per pixel, up to 10 bounces per path. That is on the order of a billion ray-surface interactions, each involving intersection tests, BSDF evaluations, and sampling.

Recording all of it produces a tape far larger than any GPU's memory.

The options that work for neural networks do not rescue this. Checkpointing, recomputing segments instead of storing them, helps in proportion to how much you are willing to recompute, and here you would need to discard nearly everything. Reducing samples trades directly against gradient noise, which is already the binding constraint.

So differentiable rendering needed something structurally different, and finding it required using what is known about light transport rather than treating the renderer as an arbitrary program.

Full lesson text

All 7 steps on one page, for reading, reference, and search.

Show

1. The memory wall

Reverse-mode automatic differentiation, the backpropagation used to train every neural network, works by recording operations on a tape during the forward pass, then walking it backwards.

That is a fine trade for a neural network. A forward pass performs a bounded number of operations, and storing intermediate activations costs memory proportional to the network's size.

Apply the same recipe to a path tracer and the arithmetic becomes alarming. Consider a modest render: one million pixels, 128 samples per pixel, up to 10 bounces per path. That is on the order of a billion ray-surface interactions, each involving intersection tests, BSDF evaluations, and sampling.

Recording all of it produces a tape far larger than any GPU's memory.

The options that work for neural networks do not rescue this. Checkpointing, recomputing segments instead of storing them, helps in proportion to how much you are willing to recompute, and here you would need to discard nearly everything. Reducing samples trades directly against gradient noise, which is already the binding constraint.

So differentiable rendering needed something structurally different, and finding it required using what is known about light transport rather than treating the renderer as an arbitrary program.

2. Radiative backpropagation

The breakthrough came from recognising that the backward pass is not an arbitrary computation. It is itself a light transport problem.

Merlin Nimier-David, Sebastien Speierer, Benoit Ruiz, and Wenzel Jakob published "Radiative backpropagation: an adjoint method for lightning-fast differentiable rendering" in 2020.

The idea is best understood physically. In the forward pass, light flows from sources, scatters through the scene, and reaches the camera. In the backward pass, you have an error signal at each pixel that must be attributed to the scene parameters responsible for it.

The insight is that this error signal propagates through the scene by the same physics. Treat the image-space derivative as a quantity emitted from the camera, transport it through the scene using the ordinary rules of light transport, and deposit derivative contributions on the surfaces it encounters.

This is an adjoint method, a classical technique in simulation: rather than recording a forward computation, solve a second, related simulation backwards.

What it buys is decisive. You no longer store a tape, because the backward pass is a simulation you run, not a recording you replay. Memory becomes proportional to scene size rather than to the number of operations performed.

3. Path replay backpropagation

Radiative backpropagation removed the memory problem and left a cost behind: the backward simulation is a second full transport solve, so its complexity can grow unfavourably with path length.

Delio Vicini, Sebastien Speierer, and Wenzel Jakob addressed this in "Path Replay Backpropagation: Differentiating Light Paths using Constant Memory and Linear Time", published in ACM Transactions on Graphics volume 40, number 4, in 2021. The title states the achievement precisely.

The mechanism is a trick that is obvious in hindsight and depends on a specific property of Monte Carlo renderers. A path is generated from a pseudorandom number sequence, and that sequence is determined by a seed.

So you do not need to store the path. You store the seed, and regenerate the identical path on demand by replaying the random number generator.

During the backward pass, replay each path, recompute what you need, and accumulate derivatives. Memory becomes constant in path length, since a seed is a few bytes regardless of how many bounces the path had, and the time cost stays linear.

Trading a small amount of recomputation for the elimination of storage is what made differentiating full path-traced scenes practical.

4. Three ways to get the gradient

Each generation removed a specific bottleneck. Naive taping is limited by memory, radiative backpropagation replaces the tape with a simulation, and path replay replaces stored paths with regenerable seeds.

flowchart TD
  A["Need gradient of image with respect to scene"] --> B["Naive: record every operation on a tape"]
  B --> C["Memory grows with billions of interactions"]
  C --> D["Infeasible for real scenes"]
  A --> E["Radiative backprop: transport the error signal"]
  E --> F["Backward pass is a simulation, no tape"]
  F --> G["Memory proportional to scene, not operations"]
  A --> H["Path replay: store only the random seed"]
  H --> I["Regenerate each path on demand"]
  I --> J["Constant memory, linear time"]

5. Why the renderer needed its own compiler

Efficient algorithms are necessary and, on modern hardware, not sufficient. Differentiable rendering also required rethinking how the code executes.

The difficulty is a mismatch between rendering and GPU execution. GPUs are fastest when many threads execute the same instruction on different data. A path tracer violates this constantly: different rays hit different materials, take different numbers of bounces, and follow different branches. The threads diverge, and throughput collapses.

The response in the Mitsuba line was to build a compiler. Dr.Jit is a just-in-time compiler designed for this workload. Rather than executing the renderer instruction by instruction, it traces the computation, fuses operations into large kernels, eliminates redundant work, and specialises code for the scene at hand.

Crucially it handles both directions, so the derivative computation is compiled and optimised alongside the primal one rather than bolted on.

This is why Mitsuba 3 is described as retargetable: the same renderer source compiles into variants for different modes, including scalar or vectorised execution, GPU or CPU, and forward or reverse differentiation.

The general lesson is worth extracting. A differentiable simulator is not a simulator with derivatives added. The requirement reaches into the execution model.

6. Gradients of a noisy objective

One difficulty remains after memory and speed are solved, and it is intrinsic rather than engineering.

A rendered image is a Monte Carlo estimate, so it is noisy. Its gradient is therefore also a random variable, and optimisation proceeds on a stochastic objective.

This is familiar territory from stochastic gradient descent, and much of the intuition transfers. It also has renderer-specific characteristics worth knowing.

Gradient noise scales with image noise. Fewer samples per iteration means cheaper steps and noisier gradients, and the sample count becomes a tuning parameter trading iteration cost against gradient quality.

Some parameters are far noisier than others. A parameter influencing many pixels, such as a global light intensity, averages over many samples and produces a stable gradient. A parameter affecting a small region, such as one vertex, receives few relevant samples and a correspondingly noisy signal.

Correlated sampling helps. Using the same random seeds across iterations makes the noise consistent between steps, so differences reflect parameter changes rather than resampling.

And biased gradient estimators, from approximate reparameterization, are a distinct hazard: noise averages out over iterations, whereas bias does not.

7. The cost picture

Putting the engineering together, what it actually costs to differentiate a renderer.

ConcernNaive approachCurrent practice
MemoryTape of every operationConstant per path, via replay
Backward costReplay a huge tapeA second transport simulation
Path length scalingMemory grows linearlyMemory constant, time linear
GPU utilisationThread divergenceJIT compilation and kernel fusion
Gradient qualityNoise scales with image noiseCorrelated sampling, sample tuning
Geometry gradientsSilently zeroRequires the previous lesson's machinery

A useful rule of thumb: a well-implemented differentiable render costs on the order of a small multiple of the corresponding forward render, rather than the orders of magnitude that naive taping would demand.

Two gotchas worth stating plainly.

Sample count is a real hyperparameter. Too few and gradients are too noisy to optimise; too many and each iteration is wasteful. Ramping it upward as optimisation converges is a common and effective pattern.

Check that geometry gradients are non-zero before trusting any inverse rendering setup. That single check catches the most common silent failure in the whole pipeline.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. Why does standard tape-based reverse-mode autodiff fail for path tracing?
    • Ray-surface intersections are not differentiable operations
    • A render involves on the order of a billion interactions, producing a tape far larger than GPU memory
    • Monte Carlo estimators cannot be composed with the chain rule
    • The tape must be stored in double precision
  2. What is the core insight of radiative backpropagation?
    • Gradients can be approximated by finite differences of nearby renders
    • Only the final bounce of each path contributes meaningful gradient
    • The backward pass is itself a light transport problem, so the error signal can be transported through the scene
    • Derivatives can be precomputed once per scene and reused
  3. How does path replay backpropagation achieve constant memory?
    • It compresses the tape using a lossy encoding
    • It limits paths to a fixed maximum number of bounces
    • It stores only the final radiance value of each path
    • It stores the random seed and regenerates the identical path on demand during the backward pass
  4. Why did differentiable rendering require a specialised JIT compiler such as Dr.Jit?
    • Path tracing causes thread divergence on GPUs, so operations must be traced, fused into kernels, and specialised
    • Automatic differentiation is not supported by standard GPU drivers
    • Scene files must be parsed at every optimisation step
    • Floating point precision must be raised for gradient computation
  5. Which parameter typically receives the noisiest gradient signal?
    • A global light intensity affecting the whole image
    • A single vertex position affecting a small image region
    • An overall exposure multiplier
    • A material roughness applied to a large surface

Related lessons

AI
advanced

Finding the Next One: Fusion Beyond Attention

The pattern that made attention slow recurs across the stack, and once you know what to look for it is easy to find. This lesson applies the diagnosis to normalisation layers, optimizer steps, loss functions and inference decoding, covers why fused attention silently stops applying when a model deviates slightly from standard, and gives the profiling routine that decides where to look first.

10 steps·~15 min
AI
advanced

Writing Fused Kernels Without Writing CUDA

The reason most teams never fuse anything is that CUDA asks you to manage threads, shared memory and synchronisation by hand. Triton moves the unit of programming from a thread to a block and hands the rest to a compiler. This lesson covers what that buys, what it still asks of you, how to decide a kernel is worth writing, and how to be sure it is correct.

10 steps·~15 min
AI
advanced

Attention Is Memory-Bound, and Nobody Noticed for Five Years

For years attention was optimised by reducing FLOPs, and approximate methods that cut FLOPs kept failing to run faster. The reason is that attention was never compute-bound: it spends its time moving a matrix between GPU memory tiers. This lesson establishes that hierarchy, counts the traffic a standard implementation generates, and shows why an exact algorithm beat every approximation.

10 steps·~15 min
AI
advanced

Online Softmax: Tiling Across a Reduction

Softmax normalises over a whole row, which appears to require the whole row before anything can be produced, which appears to require the score matrix to exist. This lesson works through the rescaling identity that removes that obstacle, the running max and sum that make it numerically safe, and the backward pass trick of recomputing the matrix from two saved statistics.

10 steps·~15 min