function grads(f) {
    _util.assert(_util.isFunction(f), ()=>"The f passed in grads(f) must be a function");
    return (args, dy)=>{
        _util.assert(Array.isArray(args), ()=>"The args passed in grads(f)(args) must be an array of `Tensor`s or `TensorLike`s");
        // args can be of any dtype, thus null as the last argument.
        const $args = (0, _tensorUtilEnv.convertToTensorArray)(args, "args", "tf.grads", "string_or_numeric");
        const $dy = dy != null ? (0, _tensorUtilEnv.convertToTensor)(dy, "dy", "tf.grads") : null;
        return (0, _engine.ENGINE).tidy(()=>{
            const { value, grads } = (0, _engine.ENGINE).gradients(()=>f(...$args), $args, $dy);
            if ($dy != null) _util.assertShapesMatch(value.shape, $dy.shape, "The shape of dy passed in grads(f)([x1,...], dy) must match the shape returned by f([x1,...])");
            checkGrads(grads);
            return grads;
        });
    };
}
Provided `f(x1, x2,...)`, returns another function `g([x1, x2,...], dy?)`,
which gives an array of gradients of `f()` with respect to each input
[`x1`,`x2`,...].

If `dy` is passed when calling `g()`, the gradient of
`f(x1,...).mul(dy).sum()` with respect to each input is computed instead.
The provided `f` must take one or more tensors and return a single tensor
`y`. If `f()` takes a single input, we recommend using `tf.grad` instead.

```js
// f(a, b) = a * b
const f = (a, b) => a.mul(b);
// df / da = b, df / db = a
const g = tf.grads(f);

const a = tf.tensor1d([2, 3]);
const b = tf.tensor1d([-2, -3]);
const [da, db] = g([a, b]);
console.log('da');
da.print();
console.log('db');
db.print();
```

@param f The function `f(x1, x2,...)` to compute gradients for.

@doc {heading: 'Training', subheading: 'Gradients'}