Calls fn on args, but differentiates the result with rule instead of
through the operations fn performs. Use it where those operations give no
gradient – e.g. a prim_while() loop, which gradient() cannot
differentiate – or an inaccurate one, while the derivative has a formula of
its own.
Arguments
- args
(named
list())
The arguments offn:arrayishvalues, or trees of them in the sense of pjrt'sRTree, by name.rulegives the gradient with respect to these. No entry may be namedout,gradorrequired, the names under whichrulereceives the result, its gradient and which gradients are required. An R value materializes at its default data type, as inprim_while().- fn
(
function)
Computes the result from the entries ofargs, which it receives as arguments. Returns an array, or a tree of them.- rule
(
function|anvl_rule_reverse)
Computes the gradient. A function receives the entries ofargsas arguments, plusout, the result offn,grad, the gradient with respect toout, of the same structure, data types and shapes, andrequired, a namedlogical()with one element per entry ofargs:TRUEwhere the gradient with respect to that entry is required, i.e. where any of its arrays requires one. Returns a named list with the gradient with respect to each required entry – for an arrayx, the sum over the elements ofoutof theirgradtimes their derivative with respect tox– ofx's structure, data types and shapes. An entry that is not required, e.g. an integer, which never is, can be left out or beNULL.Alternatively, a
rule_reverse()with aforwardpass, which replacesfnwhere the result is differentiated, so that the backward pass can read what the forward pass computed. Its passes are those of a primitive's rule, except thatinputsare the entries ofargsby name rather than one array per operand,paramsis an empty list, andoutputs,grads,requiredand the returned gradients are those of a function above.
Value
(arrayish | list)
The result of fn.
Gradients
rule is traced when the gradient is, once the function being
differentiated has been traced. It can close over constants – R values and
arrays – but not over a traced value: pass that through args instead.
fn may close over traced values, but gradient() cannot differentiate
with respect to those, as rule only gives the gradient with respect to
args. Pass such a value through args instead.
StableHLO
Lowers the operations of fn in place. rule is not lowered: a gradient
is traced, as anvl operations, before the program is lowered.
Examples
# x^n by repeated multiplication: a while loop, which gradient() cannot
# differentiate on its own
pow_loop <- function(x, n) {
prim_while(
init = list(i = 1L, y = x),
cond = function(i, y) i < n,
body = function(i, y) list(i = i + 1L, y = y * x)
)$y
}
pow <- function(x, n) {
prim_custom_gradient(
list(x = x, n = n),
fn = pow_loop,
rule = function(x, n, out, grad, required) list(x = grad * n * x^(n - 1L))
)
}
x <- nv_scalar(2)
pow(x, 3L)
#> AnvlArray
#> 8
#> [ CPUf32{} ]
# the derivative 3 * x^2
jit(gradient(pow, wrt = "x"))(x, 3L)
#> $x
#> AnvlArray
#> 12
#> [ CPUf32{} ]
#>
# and, as it is computed from x, the second derivative 6 * x
dpow <- function(x, n) gradient(pow, wrt = "x")(x, n)$x
jit(gradient(dpow, wrt = "x"))(x, 3L)
#> $x
#> AnvlArray
#> 12
#> [ CPUf32{} ]
#>
# With a forward pass, x^(n - 1) is computed once, for the result and for
# its gradient
pow_fwd <- function(x, n) {
prim_custom_gradient(
list(x = x, n = n),
fn = pow_loop,
rule = rule_reverse(forward = function(inputs, params, required) {
p <- inputs$x^(inputs$n - 1L)
list(
outputs = p * inputs$x,
backward = function(inputs, outputs, grads, params, required) {
list(x = grads * inputs$n * p)
}
)
})
)
}
jit(gradient(pow_fwd, wrt = "x"))(x, 3L)
#> $x
#> AnvlArray
#> 12
#> [ CPUf32{} ]
#>