API Reference¶
The public surface is small: two entry points cover almost all use, plus registration hooks for extending Tangent with new gradients and tensor types.
Differentiation¶
tangent.grad_util.grad = cached_grad(_grad_uncached)
module-attribute
¶
tangent.grad_util.autodiff = cached_autodiff(_autodiff_uncached)
module-attribute
¶
tangent.grad_util.vjp(func, wrt=(0,), optimized=True, check_dims=True, preserve_result=False, verbose=0)
¶
Convenience function to produce vector-Jacobian products.
See autodiff for function arguments.
Uses reverse-mode joint-motion autodiff to produce the VJP.
Source code in tangent/grad_util.py
tangent.grad_util.jvp(func, wrt=(0,), optimized=True, check_dims=True, preserve_result=False, verbose=0)
¶
Convenience function to produce Jacobian-vector products.
See autodiff for function arguments.
Uses forward-mode autodiff to produce the JVP.
Source code in tangent/grad_util.py
Differentiable ODEs¶
tangent.ode.odeint(func, y0, ts, args=(), num_steps=10)
¶
Integrate dy/dt = func(y, t, *args), returning y at each time in ts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
Dynamics |
required | |
y0
|
Initial state (array). |
required | |
ts
|
1-D increasing array of output times; |
required | |
args
|
Tuple of extra arguments (the parameters |
()
|
|
num_steps
|
Fixed RK4 sub-steps per output interval. |
10
|
Returns:
| Type | Description |
|---|---|
|
Array of shape |
Source code in tangent/ode.py
Custom gradients¶
See the Custom Gradients guide for a walkthrough of when to use each of these.
tangent.custom_vjp.custom_vjp(func)
¶
Mark func as differentiated by a user-registered VJP rule.
Returns func with two decorator attributes: func.defvjp registers the
reverse-mode rule and func.defjvp the (optional) forward-mode rule.
Until defvjp is called, differentiating a caller of func transforms
its body as usual.
Source code in tangent/custom_vjp.py
tangent.custom_vjp.register_adjoint(func)
¶
Register a reverse-mode (adjoint) template for an existing callable.
Use this to teach Tangent the gradient of a function it cannot transform -
typically a library or backend op you do not own. For your own Python
functions, prefer custom_vjp, whose rule is plain Python.
The decorated function is an adjoint template written in Tangent's DSL:
its first parameter is the primal output, the rest are the primal inputs
(matched by position to the differentiated call), and it assigns each
input's gradient into d[<param>] from the output gradient d[<output>]::
@tangent.register_adjoint(numpy.hypot)
def hypot_adjoint(z, x, y):
d[x] = d[z] * x / z
d[y] = d[z] * y / z
Registering a rule shadows any built-in rule for func. The template must
have retrievable source (define it in a module, not the REPL), like any
function Tangent differentiates.
Returns:
| Type | Description |
|---|---|
|
A decorator that registers its argument as the adjoint template and |
|
|
returns it unchanged. |
Source code in tangent/custom_vjp.py
tangent.custom_vjp.register_tangent(func)
¶
Register a forward-mode (tangent) template for an existing callable.
The forward-mode counterpart of register_adjoint. The decorated template
receives the primal output followed by the primal inputs, and assigns the
output's tangent into d[<output>] from the input tangents d[<input>]::
@tangent.register_tangent(numpy.hypot)
def hypot_tangent(z, x, y):
d[z] = (x * d[x] + y * d[y]) / z
Returns:
| Type | Description |
|---|---|
|
A decorator that registers its argument as the tangent template and |
|
|
returns it unchanged. |
Source code in tangent/custom_vjp.py
tangent.custom_vjp.stop_gradient(x)
¶
Identity in the primal; zero derivative in both modes.
Values flowing through stop_gradient are treated as constants by the
differentiator - the standard way to freeze part of a computation.
Working in notebooks and the REPL¶
tangent.capture.function(fn=None, *, source=None)
¶
Decorator: capture fn's source now so Tangent can differentiate it later.
Usable bare (@tangent.function) or with an explicit source string
(@tangent.function(source=...)) for functions whose source is not
otherwise retrievable.
Source code in tangent/capture.py
Shape checking¶
tangent.shape_check.check_shapes(func, *inputs)
¶
Abstractly evaluate func on the given inputs' shapes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
The function to check (a primal, or a |
required | |
*inputs
|
Example inputs - real arrays, or |
()
|
Returns:
| Type | Description |
|---|---|
|
The output |
Raises:
| Type | Description |
|---|---|
ShapeError
|
On a rank/broadcast/matmul/reshape mismatch, with the
offending line of |
Source code in tangent/shape_check.py
Debugging¶
tangent.explain.explain(func, *args, wrt=(0,), out=print)
¶
Show the primal, the generated adjoint, and a verified gradient.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
The function to differentiate. |
required | |
*args
|
Arguments to evaluate at. |
()
|
|
wrt
|
Argument indices to differentiate with respect to. |
(0,)
|
|
out
|
Where to print (pass |
print
|
Returns:
| Type | Description |
|---|---|
|
A dict with |
|
|
|
|
|
|
|
|
influence the output - and |
|
|
provably do not affect the output. |
Source code in tangent/explain.py
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 | |
tangent.explain.source_map(df, func=None)
¶
Map generated-gradient lines back to primal statements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
A gradient function produced by |
required | |
func
|
Optionally the primal function, to resolve primal line numbers in
the user's source file. Defaults to the one recorded on |
None
|
Returns:
| Type | Description |
|---|---|
|
A list of dicts |
|
|
number and text in the generated source, the primal statement that line |
|
|
serves (None for scaffolding), and the primal's 1-based line number in |
|
|
the user's file (None when unresolvable). |
Source code in tangent/explain.py
Gradient surgery: insert_grad_of¶
with tangent.insert_grad_of(x) as dx: splices arbitrary code into the
generated backward pass, with dx bound to the gradient of x at that point -
Tangent's original killer feature for inspecting or modifying a gradient
mid-flow. It survives the optimization passes (DCE/CSE), so the injected code
is not eliminated:
import tangent
def f(x):
y = x * x
with tangent.insert_grad_of(y) as dy:
print('gradient flowing into y:', dy)
dy = dy * 0.9 # scale the gradient (surgery)
return y * 3.0
df = tangent.grad(f) # the `with` block runs during the backward pass
To call f directly (without differentiating) the block must be removed; the
tangent decorator does that:
tangent.utils.insert_grad_of(var)
¶
The context manager that allows insertion of arbitrary adjoint code.
This function can be used as a context manager e.g. with insert_grad_of(x) as dx
to write code that will be inserted in the adjoint while having access to the
gradients of certain variables.
This function is handled by reverse mode automatic differentiation, and
shouldn't actually ever be called. If the user wants to use a function
containing this context manager without taking the derivative, the tangent
decorator should be used to remove it from the code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var
|
The variable of which we want the gradient. |
required |
Returns:
| Type | Description |
|---|---|
|
The gradient of this value. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If this context manager isn't removed using the |
Source code in tangent/utils.py
Extending Tangent¶
Registering gradient templates¶
To teach Tangent the gradient of a library op it does not already know, use the
public tangent.register_adjoint (reverse mode) and tangent.register_tangent
(forward mode) decorators:
import numpy as np
import tangent
@tangent.register_adjoint(np.sinc) # reverse rule: fills d[x] from d[y]
def asinc(y, x):
d[x] = d[y] * (np.cos(np.pi * x) / x - y / x)
@tangent.register_tangent(np.sinc) # forward rule: fills d[y] from d[x]
def tsinc(y, x):
d[y] = d[x] * (np.cos(np.pi * x) / x - y / x)
Inside a template, d[v] denotes the derivative associated with variable
v; the template's parameter names bind to the call site's result and
arguments in order. (These wrap the lower-level tangent.grads.adjoint /
tangent.tangents.tangent_ decorators that Tangent's own backend extensions
use.) For your own Python functions, prefer
custom_vjp, whose rule is plain Python.
Runtime type registries¶
New tensor types participate in gradient bookkeeping through the dispatch
registries in tangent.utils:
tangent.utils.register_init_grad(t, init_grad_function)
¶
Register a new gradient initializer.
Gradient initializers are used to initialize new adjoint and tangent variables. TODO: Link to the document explaining the overall terminology and mechanics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
A Python type object. The data type supported by the initializer. |
required | |
init_grad_function
|
A unary function that takes an argument of type t and returns a zero object of the same size as the argument. For example, the gradient initializer for Numpy objects is zeros_like. |
required |
Source code in tangent/utils.py
tangent.utils.register_add_grad(left_type, right_type, add_grad_function)
¶
Register a new gradient adder supporting the given types.
Gradient adders are used to add (in the sense of arithmetic addition) intermediate adjoint and tangent variables. TODO: Link to the document explaining the overall terminology and mechanics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left_type
|
A Python type object. The data type of the left operand supported by the adder. |
required | |
right_type
|
A Python type object. The data type of the right operand supported by the adder. |
required | |
add_grad_function
|
A binary function that takes two arguments, left and right, of the types left_type and right_type respectively, and returns their sum. For example, the gradient adder for Numpy objects is np.add. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the given type pair was already registered. |
Source code in tangent/utils.py
tangent.utils.register_unbroadcast(t, unbroadcaster_function)
¶
Register a new unbroadcaster.
Unbroadcasters are used to undo broadcasting, e.g. np.eye(3) + 3 will broadcast 3 to np.shape(np.eye(3)). In the backward pass, we have to undo this.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
A Python type object. The data type supported by the unbroadcaster. |
required | |
unbroadcaster_function
|
A binary function that takes a first argument of type t, and a second argument that t needs to be unbroadcast to. |
required |
Source code in tangent/utils.py
tangent.utils.register_unreduce(t, unreducer_function)
¶
Register a new unreducer.
Unreducers are used to undo reduction, e.g. np.sum(np.eye(3)) will reduce a (3,3) array to a scalar. In the backward pass, we have to undo this.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
A Python type object. The data type supported by the unreducer. |
required | |
unreducer_function
|
A function with the same signature
as e.g. |
required |
Source code in tangent/utils.py
tangent.utils.register_matmul_grad(t, grad_x_function, grad_y_function)
¶
Register the partial gradients of the @ (matmul) operator for type t.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
A Python type object. The array type the operands belong to. |
required | |
grad_x_function
|
A ternary function fn(dz, x, y) returning d[z]/d[x] contributions for z = x @ y (including any rank-promotion cases). |
required | |
grad_y_function
|
A ternary function fn(dz, x, y) returning d[z]/d[y]. |
required |
Source code in tangent/utils.py
Frontend passes¶
tangent.passes.register_pass(pass_, before=None, after=None)
¶
Insert an additional pass relative to a named existing pass.
This is the extension hook for backends and users: it splices a new Pass
into the pipeline without editing the built-in sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pass_
|
A |
required | |
before
|
Name of the pass to insert immediately before. |
None
|
|
after
|
Name of the pass to insert immediately after.
Exactly one of |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
On a name collision, a missing anchor, or if not exactly one
of |
Source code in tangent/passes.py
Utilities¶
tangent.backend_status()
¶
Return the load status of Tangent's optional backend extensions.
Returns:
| Type | Description |
|---|---|
|
A dict mapping backend names (e.g. 'jax', 'torch') to one of |
|
|
'available', 'not installed', or 'broken: |
Missing optional backends are logged at DEBUG level at import time; to see those messages, enable debug logging before importing tangent:
logging.getLogger('tangent').setLevel(logging.DEBUG)
Source code in tangent/__init__.py
tangent.utils.checkpoint()
¶
Annotation: segment-checkpoint the loops inside this block.
Used as a context manager around a loop::
with tangent.checkpoint():
for i in range(n):
...
In the primal it is a no-op. During reverse-mode differentiation it forces
segment (sqrt-n) checkpointing for the loops directly inside it,
regardless of the min_length threshold and without needing
grad(..., checkpoint=True) - and, because the user opted in explicitly,
it also applies to loops whose length is not a compile-time constant.
Source code in tangent/utils.py
tangent.grads.UNIMPLEMENTED_ADJOINTS / tangent.tangents.UNIMPLEMENTED_TANGENTS
hold the ops known to lack a derivative in each mode; calls to them raise the
clean ReverseNotImplementedError / ForwardNotImplementedError.