deepbox/ndarray
Autograd
Reverse-mode automatic differentiation with GradTensor, trainable leaves from parameter, noGrad and customOp.
- Autograd
- GradTensor
Overview
parameter(data)creates a leaf withrequiresGrad: true. Use it for hand-written optimization problems. For network training you do not wrap data:model.forward(tensor)returns aGradTensorthat tracks the weights.backward()adds to.gradon every leaf, as in PyTorch, so clear gradients between steps withoptimizer.zeroGrad(),module.zeroGrad()orx.zeroGrad(). Gradients of intermediate nodes are not kept. On a non-scalar resultbackward()seeds ones, and you can pass your own seed tensor.noGrad(fn)runs a synchronous callback with recording off and returns its result. An async callback throwsDeepboxError.model.eval()does not turn tracking off, so wrap inference innoGrad.GradTensorhas the same methods asTensor. Operations that produce no gradient, such asargmax, comparisons andisnan, return a plainTensor. A plainTensorexposesrequiresGrad(alwaysfalse),grad(null) and abackward()that throws, so code that receivesAnyTensorcan call them without narrowing.customOpattaches a hand-written backward rule to a precomputed result. The*Gradhelpers (softmaxGrad,dropoutGrad,im2colGradand others) are the differentiable building blocks that thennlayers use.
GradTensor
Tensor wrapper that records a computation graph for reverse-mode autodiff.
Gradients accumulate into leaf tensors across backward() calls until zeroGrad() is called; gradients of intermediate nodes are recomputed on every pass.
The method surface matches Tensor, so code reads the same with or without gradient tracking. Results that cannot carry a gradient (argmax, comparisons, argsort, any, all) are plain Tensors, as in PyTorch.
const w = parameter([1, 2]);const loss = w.mul(w).sum();loss.backward();console.log(loss.item()); // 5console.log(w.grad?.toArray()); // [2, 4]Members (104)
constructor(data: number | number[] | number[][] | number[][][], options?: GradTensorOptions)constructor(args: { readonly tensor: Tensor; readonly requiresGrad: boolean; readonly prev: readonly GradTensor[]; readonly backward: BackwardFn; })readonly tensor: TensorrequiresGrad: booleanshape: ShapeGet the shape of the underlying tensor.
size: numberGet the total number of elements.
ndim: numberGet the number of dimensions.
dtype: DTypeGet the data type of the underlying tensor.
device: Tensor["device"]Get the device where the tensor resides.
isDeviceTensor: booleantruewhen the tensor's storage lives in device memory (seeTensor.isDeviceTensor).strides: readonly number[]Get the memory strides of the underlying tensor.
offset: numberGet the offset into the underlying data buffer.
data: TypedArrayGet the underlying data buffer.
grad: Tensor | nullGet the accumulated gradient for this tensor.
T: GradTensorTranspose with the axes reversed (like
.Tin NumPy and PyTorch).static isGradTensor(value: unknown): value is GradTensorCheck if a value is a GradTensor (works across module boundaries).
static create(args: { readonly tensor: Tensor; readonly requiresGrad: boolean; readonly prev: readonly GradTensor[]; readonly backward: BackwardFn; }): GradTensorBuild a GradTensor from an existing tensor, graph parents and backward rule.
static fromTensor(t: Tensor, options?: GradTensorOptions): GradTensorWrap a tensor as a leaf GradTensor without copying its data.
static scalar(value: number, options?: GradTensorOptions): GradTensorCreate a 0-d GradTensor holding
value(default dtype float32).toArray(): ReturnType<Tensor["toArray"]>Convert to a nested JS array (delegates to the underlying tensor).
at(...indices: number[]): ReturnType<Tensor["at"]>Read a single element by multi-index (delegates to the underlying tensor).
astype(dtype: DType): GradTensorCast to another numeric dtype, differentiably.
setGrad(grad: Tensor): voidReplace the stored gradient with
grad.zeroGrad(): voidReset the gradient to zeros (shape and dtype of the tensor).
detach(): GradTensorReturn a GradTensor that shares this tensor's data but is cut off from the graph (
requiresGradis false).setRequiresGrad(value: boolean): voidTurn gradient tracking on or off for this tensor.
hasGrad(): booleanWhether a gradient has been stored on this tensor.
backward(grad?: Tensor): voidBackpropagate gradients from this node through the recorded graph.
add(otherArg: number | Tensor | GradTensor): GradTensorElementwise sum with broadcasting.
sub(otherArg: number | Tensor | GradTensor): GradTensorElementwise difference with broadcasting.
mul(otherArg: number | Tensor | GradTensor): GradTensorElementwise product with broadcasting.
neg(): GradTensorElementwise negation.
sum(axisArg?: Axis | readonly Axis[], keepdims?: boolean): GradTensorSum over
axis, or over all elements whenaxisis omitted.div(otherArg: number | Tensor | GradTensor): GradTensorElementwise quotient with broadcasting.
pow(exponent: number | Tensor | GradTensor): GradTensorRaise every element to a constant power.
sqrt(): GradTensorElementwise square root,
pow(0.5).matmul(otherArg: Tensor | GradTensor): GradTensorMatrix product with
numpy.matmul/torch.matmulsemantics.relu(): GradTensorRectified linear unit,
max(x, 0); the gradient at 0 is 0.sigmoid(): GradTensorLogistic function
1 / (1 + exp(-x)).square(): GradTensorElementwise square,
pow(2).exp(): GradTensorElementwise exponential.
log(): GradTensorElementwise natural logarithm.
tanh(): GradTensorElementwise hyperbolic tangent (keeps the input's float dtype).
slice(...args: SliceRange[]): GradTensorSlice the tensor (see slice); the gradient is scattered back into a zero tensor of the original shape.
gather(indicesArg: Tensor | GradTensor, axis: Axis): GradTensorSelect entries along
axisusing a 1-D index tensor.mean(axis?: Axis | readonly Axis[], keepdims?: boolean): GradTensorMean over
axis, or over all elements whenaxisis omitted.max(axis?: Axis | readonly Axis[], keepdims?: boolean): GradTensorMaximum over
axis, or over all elements whenaxisis omitted.reshape(newShape: Shape): GradTensorReshape the GradTensor to a new shape without copying data.
flatten(): GradTensorFlatten the GradTensor to a 1-dimensional array.
view(shape: Shape, strides?: readonly number[], offset?: number): GradTensorCreate a view of the GradTensor with a different shape.
transpose(axes?: readonly number[]): GradTensorPermute the axes (reverse them when
axesis omitted).min(axis?: Axis | readonly Axis[], keepdims?: boolean): GradTensorMinimum over
axis, or over all elements whenaxisis omitted.abs(): GradTensorElementwise absolute value; the gradient at 0 is 0.
clip(minVal?: number, maxVal?: number): GradTensorClamp to
[minVal, maxVal]; either bound may be omitted.leakyRelu(negativeSlope?: number): GradTensorLeaky ReLU:
xforx > 0, otherwisenegativeSlope * x.elu(alpha?: number): GradTensorExponential linear unit:
xforx > 0, otherwisealpha * (exp(x) - 1).gelu(options?: GeluApproximation | GeluOptions): GradTensorGaussian error linear unit.
hardtanh(minVal?: number, maxVal?: number): GradTensorClamp to
[minVal, maxVal]; the gradient is 1 strictly inside the interval and 0 elsewhere.tanhshrink(): GradTensorx - tanh(x).sin(): GradTensorElementwise sine.
cos(): GradTensorElementwise cosine.
tan(): GradTensorElementwise tangent.
log1p(): GradTensorlog(1 + x), accurate for smallx.expm1(): GradTensorexp(x) - 1, accurate for smallx.softplus(): GradTensorSoftplus,
log(1 + exp(x)), computed without overflow.logSigmoid(): GradTensorLog of the logistic function,
-softplus(-x), stable for large|x|.swish(): GradTensorSwish (SiLU),
x * sigmoid(x).mish(): GradTensorMish,
x * tanh(softplus(x)).selu(): GradTensorScaled ELU,
1.0507... * elu(x, 1.6732...), the self-normalizing activation.softmax(axis?: Axis): GradTensorSoftmax along
axis(default last).logSoftmax(axis?: Axis): GradTensorLog-softmax along
axis(default last).cumsum(axis?: Axis): GradTensorCumulative sum along
axis; the input is flattened first whenaxisis omitted (NumPy semantics).prod(axis?: Axis | readonly Axis[], keepdims?: boolean): GradTensorProduct over
axis, a list of axes, or all elements.var(axis?: Axis | readonly Axis[], keepdims?: boolean, ddof?: number): GradTensorVariance over
axis, a list of axes, or all elements.std(axis?: Axis | readonly Axis[], keepdims?: boolean, ddof?: number): GradTensorStandard deviation over
axis, a list of axes, or all elements.maximum(other: number | Tensor | GradTensor): GradTensorElementwise maximum with broadcasting.
minimum(other: number | Tensor | GradTensor): GradTensorElementwise minimum with broadcasting.
static where(condition: GradTensor | Tensor, a: GradTensor | number, b: GradTensor | number): GradTensorSelect between two tensors by a condition, with broadcasting:
condition ? a : b.clone(): GradTensorDifferentiable copy: the result owns its own buffer and the gradient flows through unchanged (like
torch.clone).item(): number | bigintValue of a single-element tensor as a JS number (or bigint for
int64), liketorch.Tensor.item().squeeze(axis?: Axis | readonly Axis[]): GradTensorRemove axes of size 1 (functional form:
squeeze).unsqueeze(axis: number): GradTensorInsert an axis of size 1 (functional form:
unsqueeze).argmax(axis?: Axis, keepdims?: boolean): TensorIndex of the maximum (functional form:
argmax).argmin(axis?: Axis, keepdims?: boolean): TensorIndex of the minimum (functional form:
argmin).eq(other: number | Tensor | GradTensor): TensorElement-wise equality, giving a plain
boolTensor(functional form:equal).ne(other: number | Tensor | GradTensor): TensorElement-wise inequality, giving a plain
boolTensor(functional form:notEqual).gt(other: number | Tensor | GradTensor): TensorElement-wise greater-than, giving a plain
boolTensor(functional form:greater).ge(other: number | Tensor | GradTensor): TensorElement-wise greater-or-equal, giving a plain
boolTensor(functional form:greaterEqual).lt(other: number | Tensor | GradTensor): TensorElement-wise less-than, giving a plain
boolTensor(functional form:less).le(other: number | Tensor | GradTensor): TensorElement-wise less-or-equal, giving a plain
boolTensor(functional form:lessEqual).isnan(): TensorElement-wise NaN test, giving a plain
boolTensor(functional form:isnan).any(axis?: Axis | readonly Axis[], keepdims?: boolean): TensorTrue where any element is non-zero, as a plain
boolTensor(functional form:any).all(axis?: Axis | readonly Axis[], keepdims?: boolean): TensorTrue where all elements are non-zero, as a plain
boolTensor(functional form:all).floor(): GradTensorRound down (functional form:
floor).ceil(): GradTensorRound up (functional form:
ceil).round(decimals?: number): GradTensorRound half to even (functional form:
round).flip(axes?: Axis | readonly Axis[]): GradTensorReverse the order of elements along axes (functional form:
flip).sort(axis?: Axis | undefined, descending?: boolean): GradTensorSorted values along an axis (functional form:
sort), liketorch.sort(x).values.argsort(axis?: Axis | undefined, descending?: boolean): TensorIndices that would sort the tensor along an axis (functional form:
argsort).dot(other: Tensor | GradTensor): GradTensorDot product, the same as GradTensor.matmul.
to(device: Device): Promise<GradTensor>Move the tensor, and its gradient if it has one, to another device (see
Tensor.to).cpu(): Promise<GradTensor>Move the tensor and its gradient to the CPU.
dispose(): voidRelease the device memory of the tensor and of its gradient (see
Tensor.dispose).toString(maxElements?: number): stringReturn a human-readable string representation of this GradTensor.
col2imGrad
function col2imGrad(cols: GradTensor, outputShape: readonly number[], kernelSize: [ number, number ], stride: [ number, number ], padding: [ number, number ]): GradTensor;Column to Image operation for GradTensor (transpose/adjoint of im2col). The backward of col2im is im2col.
Parameters
colsGradTensoroutputShapereadonly number[]kernelSize[ number, number ]stride[ number, number ]padding[ number, number ]
Returns
GradTensor
concatGrad
function concatGrad(parts: readonly GradTensor[], axis?: number): GradTensor;Concatenate GradTensors along an existing axis. Backward slices the gradient back to each input's segment.
Parameters
partsreadonly GradTensor[]- Tensors to join; shapes must agree on every axis but
axis. axisoptionalnumber- Axis to join along; negative values count from the end.
Returns
GradTensor
Throws
DeepboxErrorIfpartsis empty.
customOp
function customOp(output: Tensor, grads: ReadonlyArray<readonly [ GradTensor, (outGrad: Tensor) => Tensor ]>): GradTensor;Wrap a precomputed output tensor with an explicit reverse-mode rule.
Details
Layers that compute their forward value with hand-written kernels (Conv3d, ConvTranspose, pooling, Embedding, RNN cells, …) use this to attach a correct backward instead of returning a detached leaf. Each entry in grads is [input, gradFn]; gradFn(outGrad) returns that input's gradient tensor (already reduced to the input's shape). Honors the global no-grad context and only records the graph when some input requires grad.
Parameters
outputTensorgradsReadonlyArray<readonly [ GradTensor, (outGrad: Tensor) => Tensor ]>
Returns
GradTensor
dropoutGrad
function dropoutGrad(input: GradTensor, p?: number, training?: boolean): GradTensor;Inverted dropout: during training each element is zeroed with probability p and the survivors are scaled by 1 / (1 - p), so the expected value is unchanged. Returns input itself when training is false or p is 0. Integer and bool inputs are converted to float32 first, because the scale is not an integer in general.
Parameters
inputGradTensor- Input tensor.
poptionalnumber- Drop probability in
[0, 1)(default 0.5). trainingoptionalboolean- Apply dropout only when true (default true).
Returns
GradTensor
Throws
InvalidParameterErrorIfpis outside[0, 1).
im2colGrad
function im2colGrad(input: GradTensor, kernelSize: [ number, number ], stride: [ number, number ], padding: [ number, number ]): GradTensor;Differentiable im2col: unfold sliding kernel windows of an NCHW input into columns. The backward pass is col2imGrad's forward (col2im).
Parameters
inputGradTensorkernelSize[ number, number ]stride[ number, number ]padding[ number, number ]
Returns
GradTensor
logSoftmaxGrad
function logSoftmaxGrad(source: GradTensor, axis?: Axis): GradTensor;Differentiable log-softmax along axis, computed as x - max - log(sum(exp(x - max))) (log-sum-exp form, stable for large inputs).
Host float32 and float64 tensors use a fused kernel (one sweep per lane, in float64, with the closed-form gradient g - exp(y) * sum(g)); other inputs use the composite formulation from differentiable primitives.
Parameters
sourceGradTensor- Logits. Integer and bool inputs are converted to float32.
axisoptionalAxis- Axis to normalize over (default: last).
Returns
GradTensor
noGrad
function noGrad<T>(fn: () => T): T;Run fn with gradient tracking disabled and return its result.
Details
Operations executed inside do not record a graph, so their results have requiresGrad false. Leaf tensors created inside (for example with parameter) keep the requiresGrad flag they were created with, as in PyTorch. The previous state is restored afterwards, also when fn throws.
Important: The callback must be synchronous. Passing an async function will cause gradEnabled to be restored before the awaited work finishes, silently breaking gradient tracking inside the async continuation.
Parameters
fn() => T
Returns
T
Throws
DeepboxErrorIf the callback returns a Promise or other thenable
parameter
function parameter(data: number | number[] | number[][] | number[][][] | Tensor, options?: GradTensorOptions): GradTensor;Create a leaf GradTensor with requiresGrad = true. The flag also holds when the tensor is created inside noGrad (PyTorch semantics); only operations run inside noGrad skip recording.
Parameters
datanumber | number[] | number[][] | number[][][] | Tensor- Nested numbers or an existing tensor.
optionsoptionalGradTensorOptions- Optional
dtypeused whendatais not already a tensor.
Returns
GradTensor
softmaxGrad
function softmaxGrad(source: GradTensor, axis?: Axis): GradTensor;Differentiable softmax along axis.
Host float32 and float64 tensors use a fused kernel (one sweep per lane, in float64, with the closed-form gradient). Other inputs, such as device tensors, are computed as exp(x - max) / sum(exp(x - max)) from differentiable primitives with the maximum treated as a constant (softmax is shift-invariant), so large inputs do not overflow either way.
Parameters
sourceGradTensor- Logits. Integer and bool inputs are converted to float32.
axisoptionalAxis- Axis to normalize over (default: last).
Returns
GradTensor
stackGrad
function stackGrad(parts: readonly GradTensor[]): GradTensor;Stack a list of same-shape GradTensors along a new leading axis. Backward splits the upstream gradient back to each input. Used by the recurrent layers to assemble per-timestep hidden states differentiably.
Parameters
partsreadonly GradTensor[]- Tensors to stack; all must have the same shape. The result takes the dtype and device of the first one.
Returns
GradTensor
Throws
DeepboxErrorIfpartsis empty.ShapeErrorIf the shapes differ.
varianceGrad
function varianceGrad(input: GradTensor, axis?: Axis, correction?: number): GradTensor;Differentiable variance.
Parameters
inputGradTensor- Input tensor.
axisoptionalAxis- Axis to reduce; when omitted, all elements are reduced.
correctionoptionalnumber- Delta degrees of freedom: the divisor is
n - correction(default 1, the unbiased estimator). The divisor is clamped at 0, son <= correctionyields NaN or Infinity instead of a negative variance.
Returns
GradTensor
import { GradTensor, noGrad, parameter } from "deepbox/ndarray"; const x = parameter([[1, 2], [3, 4]]);const w = parameter([[0.5], [0.25]]); const y = x.matmul(w).sum();y.backward();console.log(x.grad?.toString());console.log(w.grad?.toString()); w.zeroGrad(); // gradients accumulate until you clear themconst prediction = noGrad(() => x.matmul(w));console.log(prediction.requiresGrad); // falseconsole.log(x instanceof GradTensor);Found a mistake? Open an issue.