XAD documentation

JIT compiler

Overview

template <class Real, std::size_t N = 1> class JITCompiler;

Only scalar mode is supported (N == 1); other values of N fail to compile.

JITCompiler is a tape-like recorder/executor for JIT-enabled builds. It:

  • records computations into a JITGraph
  • compiles the graph using a pluggable JITBackend
  • provides forward execution and adjoint propagation

Compile-time feature flag

This API is only available when XAD is compiled with XAD_ENABLE_CODEGEN, which needs the separately licensed xad-codegen library.

Typical usage

using AD = xad::AReal<double, 1>;
xad::JITCompiler<double, 1> jit;
AD x = 2.0;
AD y = 3.0;
jit.registerInput(x);
jit.registerInput(y);
jit.newRecording();
AD z = x * y + x;
jit.registerOutput(z);
jit.compile();
double out = 0.0;
jit.forward(&out);              // out = 8
jit.setDerivative(z.getSlot(), 1.0);
jit.computeAdjoints();
double dzdx = jit.getDerivative(x.getSlot());   // 4
x = 3.0;                        // run again with new input values
jit.clearDerivatives();
jit.forward(&out);              // out = 12

Construct

explicit JITCompiler(bool activate = true) uses the JITGraphInterpreter backend.

explicit JITCompiler(std::unique_ptr<JITBackend<Real>> backend, bool activate = true) uses the given backend.

With activate, the new compiler becomes the active one for the thread. A tape that is active then is deactivated while the compiler records, and activated again when the compiler is destroyed (unless another tape or compiler is active by then). A JITCompiler can be moved, but not copied.

Recording control (TLS)

JITCompiler mirrors the tape pattern: a thread-local “active” compiler can be set, so that XAD expression construction/assignment can record into the active JIT compiler when no tape is active.

isActive

bool isActive() const

Checks whether this instance is the active compiler for the current thread.

getActive

static JITCompiler* getActive()

Returns the active compiler for the current thread, or nullptr if none is active.

activate / deactivate / deactivateAll

void activate(), void deactivate(), static void deactivateAll()

Manage the thread-local active compiler pointer.

Graph and backend

getGraph

JITGraph& getGraph()

Access the recorded graph (mainly for backends and debugging).

setBackend

void setBackend(std::unique_ptr<JITBackend<Real>> backend)

Replaces the execution backend (requires recompilation of the current graph).

compile

void compile()

Compiles the currently recorded graph with the current backend.

Inputs/outputs

registerInput / registerInputs

void registerInput(active_type& inp), template <class Inner> void registerInputs(std::vector<Inner>& v), template <class It> void registerInputs(It first, It last)

Registers independent variables as graph inputs, in this order. The variables must stay alive: forward and computeAdjoints read their current values.

registerOutput / registerOutputs

void registerOutput(active_type& outp), template <class Inner> void registerOutputs(std::vector<Inner>& v), template <class It> void registerOutputs(It first, It last)

Registers dependent variables as graph outputs, in this order. An output that does not depend on the inputs is recorded as a constant.

newRecording

void newRecording()

Starts a new recording using the existing registered inputs, which must stay alive; clearAll drops them.

Execution

vectorWidth / numInputs / numOutputs

std::size_t vectorWidth() const, std::size_t numInputs() const, std::size_t numOutputs() const

The backend's number of parallel evaluations per execution (lanes), and the number of inputs and outputs of the compiled graph.

forward

void forward(Real* outputs)

Executes the forward pass with the current values of the registered inputs, and writes the numOutputs() output values to outputs. With a backend whose vectorWidth() is more than 1, every lane gets the same inputs and lane 0 is returned.

computeAdjoints

void computeAdjoints()

Runs the graph with the current values of the registered inputs and propagates the output adjoints set with setDerivative (or derivative) back to the inputs, whose derivatives can then be read with getDerivative.

setInput

void setInput(std::size_t inputIndex, const Real* values)

Sets the values of one input directly on the backend: vectorWidth() values, one per lane. For use with forwardAndBackward; forward and computeAdjoints overwrite them with the registered inputs' values.

forwardAndBackward

void forwardAndBackward(Real* outputs, Real* inputGradients)

Runs the backend's forward and backward passes on the inputs set with setInput, with all output adjoints 1. outputs takes numOutputs() * vectorWidth() values and inputGradients numInputs() * vectorWidth() values. It does not change the derivatives stored in the compiler.

Derivatives

The slot s is that of a recorded variable (x.getSlot()) or a graph node.

derivative

derivative_type& derivative(slot_type s) gets a reference to the derivative of s. The const version returns 0 for a slot without a stored derivative.

getDerivative

derivative_type getDerivative(slot_type s) const returns the derivative of s (0 if none is stored).

setDerivative

void setDerivative(slot_type s, const derivative_type& d) sets the derivative of s, e.g. to seed an output adjoint before computeAdjoints.

clearDerivatives

void clearDerivatives() resets all stored derivatives to 0, keeping the graph.

clearAll

void clearAll() clears the graph, the registered inputs and the derivatives, and resets the backend.

Status

getMemory

std::size_t getMemory() const returns an estimate of the memory in bytes taken by the graph and the derivatives.

Questions or issues: GitHub Discussions · issues. Would you rather we AAD-enabled your code? Get a free speed-up estimate.