Debugging Gradients¶
Tangent's core advantage: when a gradient looks wrong, you can read it.
1. Print the generated code¶
The generated function is annotated with comments tying each adjoint block to the primal statement it differentiates:
# Grad of: y = x * x
_bx = tangent.unbroadcast(by * x, x)
_bx2 = tangent.unbroadcast(by * x, x)
bx = tangent.add_grad(bx, _bx)
bx = tangent.add_grad(bx, _bx2)
optimized=False disables the cleanup passes if you want to see the naive,
fully-explicit derivative.
2. Step through it¶
The gradient is a plain function — pdb, IDE breakpoints, and print
all work inside the backward pass:
3. Check against finite differences¶
The pattern the test suite uses everywhere:
def numeric_grad(f, x, eps=1e-6):
return (f(x + eps / 2) - f(x - eps / 2)) / eps
assert abs(tangent.grad(f)(2.0) - numeric_grad(f, 2.0)) < 1e-5
4. Inject debugging into the backward pass¶
insert_grad_of lets you run arbitrary code at the corresponding point of
the backward pass — print an intermediate gradient, clip it, or assert on
it:
from tangent import insert_grad_of
def f(x):
y = x * x
with insert_grad_of(y) as dy:
print('gradient flowing into y:', dy)
return y * 3.0
5. Visualize gradient flow¶
With the viz extra installed:
Understanding errors¶
Tangent fails loudly and specifically:
| Error | Meaning |
|---|---|
TangentParseError |
The function uses a construct Tangent rejects (the message includes a suggested rewrite) |
ReverseNotImplementedError / ForwardNotImplementedError |
A called op has no registered derivative in that mode — register one or rewrite |
GradientNotFoundError |
A called function has neither a derivative nor retrievable source |
The philosophy is reject-or-be-correct: if your function compiles, its gradient is trustworthy; if it does not, the error tells you why and what to do instead.