utorch.autograd
utorch.autograd
Tensor
A multidimensional array and computational graph node.
Tensor tracks numerical data alongside the operations and parent nodes
that produced it, dynamically building a Directed Acyclic Graph (DAG) during
the forward pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Numerical data (scalar, list, or NumPy ndarray). If None, the node is initialized as an intermediate operation node. |
None
|
parents
|
Sequence[Tensor] | None
|
Upstream antecedent nodes that fed into this computation. |
None
|
fun
|
Callable[..., Any] | None
|
The forward mathematical function evaluated at this node. |
None
|
name
|
str | None
|
Optional human-readable identifier for visualization. |
None
|
args
|
Any
|
Optional extra static arguments passed to |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
value |
ndarray
|
The evaluated forward output tensor. |
parents |
list[Tensor]
|
Direct input nodes in the computational graph. |
fun |
Callable | None
|
The mathematical operation that generated this node. |
args |
Any
|
Additional constants or axes supplied to the operation. |
name |
str | None
|
Label used in graph diagrams and debugging. |
grad |
int | float | ndarray
|
Accumulated gradient (computed in Branch 3+). |
Example
Initialize a computational graph node via leaf or inner node dispatch.
In a dynamic Directed Acyclic Graph (DAG), every node belongs to one of two fundamental categories:
- Leaf Nodes (Inputs, Constants, Parameters):
- Created directly by the user with a known starting
value. - Have no antecedent parents (
parents = []) and no creator function (fun = None). - Represent independent variables at the base of the tree (e.g., weights, biases, inputs).
-
Dispatched to:
_init_if_leaf(value). -
Inner Nodes (Intermediate Operations):
- Created automatically as the output of an operation (e.g.,
+,*,relu). - Have antecedent parent nodes (
parents) and an associated mathematical function (fun). - Numerical value is evaluated dynamically during the forward pass by
applying
fun(*parent_values). - Dispatched to:
_init_as_inner_node(parents, fun, args).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Numerical data (scalar int/float, list, or NumPy array). If provided, initialized as a leaf node. |
None
|
parents
|
Sequence[Tensor] | None
|
Upstream antecedent |
None
|
fun
|
Callable[..., Any] | None
|
The forward mathematical function evaluated at this node
(e.g., |
None
|
name
|
str | None
|
Optional human-readable identifier for visualization. |
None
|
args
|
Any
|
Optional static constants passed to |
None
|
Source code in utorch/autograd/tensor.py
__add__
__eq__
__ge__
Element-wise greater or equal: self >= other.
__gt__
__hash__
__le__
__lt__
__matmul__
__mul__
__neg__
__pow__
__radd__
__repr__
Return a string representation of the tensor for inspection.
Source code in utorch/autograd/tensor.py
__rmul__
__rsub__
Right-hand element-wise subtraction: other - self.
__rtruediv__
Right-hand element-wise division: other / self.
__sub__
__truediv__
abs
backward
Execute reverse-mode automatic differentiation (backpropagation).
Computes the gradient of this output tensor with respect to all upstream nodes in the computational graph using the multivariate chain rule.
Because gradients are accumulated as Tensor instances rather than raw
floats, the backward pass dynamically constructs a secondary computation
graph, enabling arbitrary higher-order derivatives (e.g., double backward
for Hessians).
The backward algorithm executes in five distinct pedagogical phases:
- Scalar Verification & Seed Initialization:
- Reverse-mode autograd starts at a scalar objective (e.g., loss).
- The root gradient is seeded with the identity: dL/dL = 1.0.
-
If an explicit
grad_tensoris supplied, it is used as the seed. -
Reverse Topological Ordering:
- Performs a Depth-First Search (DFS) from
selfbackwards to all reachable ancestor nodes. - Arranges nodes so every child appears strictly before its parents.
-
Guarantees that when a node is processed, it has already collected all incoming gradient contributions from all downstream paths.
-
Primitive Derivative Dispatch:
- Traverses nodes in reverse topological order.
-
For each intermediate operation node, looks up its analytical derivative rule in
SCALAR_PRIMITIVESusingnode.op. -
Local Chain Rule Evaluation:
- For binary operations z = f(x, y): dx = d_rule_left(grad_z, x, y) dy = d_rule_right(grad_z, x, y)
-
For unary operations y = f(x): dx = d_rule(grad_y, x)
-
Gradient Accumulation (Multivariate Chain Rule):
- Local gradient contributions are added to antecedent parents using
+=(parent.grad = parent.grad + d_parent). - Accumulation (rather than overwriting) is mathematically mandatory when a variable is reused across multiple paths (diamond graphs).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grad_tensor
|
Tensor | None
|
Seed gradient for this node. If None, defaults to Tensor(1.0). |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called on a non-scalar tensor without an explicit
|
ValueError
|
If an operation node has no matching primitive rule in
|
Examples:
import utorch as ut
# 1. First-order derivative of a polynomial: y = x^2 + 3x
x = ut.Tensor(4.0, name="x")
y = (x ** 2) + 3.0 * x # dy/dx = 2x + 3 = 11.0
y.backward()
print(x.grad.value) # 11.0
# 2. Second-order derivative (double backward): d2y/dx2 = 2.0
first_grad = x.grad
x.zero_grad()
first_grad.backward()
print(x.grad.value) # 2.0
Source code in utorch/autograd/tensor.py
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 | |
clip_min
cos
cosh
exp
log
mean
Compute the arithmetic mean of tensor elements across specified axis.
Source code in utorch/autograd/tensor.py
relu
sigmoid
sign
sin
single_operator
Apply a unary operation taking self as the sole parent node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fun
|
Callable[..., Any]
|
Unary mathematical function (e.g. np.sin, np.exp). |
required |
args
|
Any
|
Optional static parameters. |
None
|
Returns:
| Type | Description |
|---|---|
Tensor
|
A new Tensor representing the operation's output. |
Source code in utorch/autograd/tensor.py
sum
Sum tensor elements across specified axis.
Source code in utorch/autograd/tensor.py
tanh
transpose
Permute the dimensions of the tensor.
Source code in utorch/autograd/tensor.py
ops
Scalar derivative rules for reverse-mode automatic differentiation.
matmul_backward
Compute the Vector-Jacobian Product (VJP) for matrix multiplication.
Mathematical Intuition & Derivation: Let Y = A @ B, where: A has shape (M, K) B has shape (K, N) Y has shape (M, N) Let G = dL/dY be the incoming gradient of shape (M, N).
Instead of computing the massive (M*N) x (M*K) Jacobian matrix,
reverse-mode autograd evaluates the Vector-Jacobian Product directly.
By index notation, each output element is:
Y_{i, j} = sum_k (A_{i, k} * B_{k, j})
Taking partial derivatives with respect to an entry A_{i, k}:
dL/dA_{i, k} = sum_j (dL/dY_{i, j} * dY_{i, j}/dA_{i, k})
= sum_j (G_{i, j} * B_{k, j})
= sum_j (G_{i, j} * (B^T)_{j, k})
In matrix form:
dL/dA = G @ B^T (Shape: (M, N) @ (N, K) -> (M, K))
Similarly, for an entry B_{k, j}:
dL/dB_{k, j} = sum_i (dL/dY_{i, j} * dY_{i, j}/dB_{k, j})
= sum_i (G_{i, j} * A_{i, k})
= sum_i ((A^T)_{k, i} * G_{i, j})
In matrix form:
dL/dB = A^T @ G (Shape: (K, M) @ (M, N) -> (K, N))
Dimensional Sanity Check (The "Shape Alignment Rule"): - dL/dA must match A.shape (M, K): G (M, N) @ B.T (N, K) -> (M, K) [Inner dimension N cancels] - dL/dB must match B.shape (K, N): A.T (K, M) @ G (M, N) -> (K, N) [Inner dimension M cancels]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grad
|
Tensor
|
Incoming gradient Tensor G of shape (M, N). |
required |
left
|
Tensor
|
Matrix A of shape (M, K). |
required |
right
|
Tensor
|
Matrix B of shape (K, N). |
required |
Returns:
| Type | Description |
|---|---|
tuple[Tensor, Tensor]
|
Tuple of (dL/dA, dL/dB) matching shapes of A and B respectively. |
Source code in utorch/autograd/ops.py
mean_backward
Compute the Vector-Jacobian Product (VJP) for tensor mean reduction.
Mathematical Intuition
The arithmetic mean is a scaled summation: mean(X) = (1 / N) * sum(X) where N is the number of elements collapsed along the reduced axes.
By the chain rule, every element that contributed to the mean receives an equal fractional share of the incoming gradient: dL/dX_i = (1 / N) * dL/dY
Therefore, mean_backward is simply sum_backward scaled by (1 / N): dL/dX = (1 / N) * sum_backward(grad, x, axis)
Broadcasting & Normalization Mechanics: 1. Determine N (the count of elements reduced): - If axis is None: N is the total count of all elements in X. - If axis is specified (e.g. axis=0 on shape (4, 3)): N is the product of the lengths of the reduced dimensions (here N = 4). 2. Leverage sum_backward to re-insert singleton dimensions and stretch the gradient back to x.shape. 3. Divide the broadcasted gradient by N.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grad
|
Tensor
|
Incoming gradient Tensor from downstream nodes. |
required |
x
|
Tensor
|
The parent Tensor that was reduced. |
required |
axis
|
int | tuple[int, ...] | None
|
The axis or axes along which the mean was computed. |
None
|
Returns:
| Type | Description |
|---|---|
Tensor
|
Tensor of shape |
Source code in utorch/autograd/ops.py
sum_backward
Compute the Vector-Jacobian Product (VJP) for tensor summation.
Mathematical Intuition
Summation distributes incoming gradients equally across all elements that contributed to the sum. For Y = sum(X), dY/dX_i = 1.0. Therefore: dL/dX = dL/dY * 1.0 (broadcasted to X's original shape).
Broadcasting Mechanics
When an axis is collapsed during sum(), that dimension is eliminated from the output shape (e.g., shape (2, 4, 3) reduced along axis=1 becomes shape (2, 3)).
To broadcast the incoming gradient back to (2, 4, 3), we must: 1. Normalize negative indices (e.g., axis=-1 becomes axis=2). 2. Re-insert a singleton dimension (size 1) at the collapsed axes: (2, 3) -> reshape -> (2, 1, 3). 3. Use np.broadcast_to to stretch size 1 back to the original size 4: (2, 1, 3) -> broadcast -> (2, 4, 3).
Source code in utorch/autograd/ops.py
tensor
Computational graph node and tensor abstraction.
Tensor
A multidimensional array and computational graph node.
Tensor tracks numerical data alongside the operations and parent nodes
that produced it, dynamically building a Directed Acyclic Graph (DAG) during
the forward pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Numerical data (scalar, list, or NumPy ndarray). If None, the node is initialized as an intermediate operation node. |
None
|
parents
|
Sequence[Tensor] | None
|
Upstream antecedent nodes that fed into this computation. |
None
|
fun
|
Callable[..., Any] | None
|
The forward mathematical function evaluated at this node. |
None
|
name
|
str | None
|
Optional human-readable identifier for visualization. |
None
|
args
|
Any
|
Optional extra static arguments passed to |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
value |
ndarray
|
The evaluated forward output tensor. |
parents |
list[Tensor]
|
Direct input nodes in the computational graph. |
fun |
Callable | None
|
The mathematical operation that generated this node. |
args |
Any
|
Additional constants or axes supplied to the operation. |
name |
str | None
|
Label used in graph diagrams and debugging. |
grad |
int | float | ndarray
|
Accumulated gradient (computed in Branch 3+). |
Example
Initialize a computational graph node via leaf or inner node dispatch.
In a dynamic Directed Acyclic Graph (DAG), every node belongs to one of two fundamental categories:
- Leaf Nodes (Inputs, Constants, Parameters):
- Created directly by the user with a known starting
value. - Have no antecedent parents (
parents = []) and no creator function (fun = None). - Represent independent variables at the base of the tree (e.g., weights, biases, inputs).
-
Dispatched to:
_init_if_leaf(value). -
Inner Nodes (Intermediate Operations):
- Created automatically as the output of an operation (e.g.,
+,*,relu). - Have antecedent parent nodes (
parents) and an associated mathematical function (fun). - Numerical value is evaluated dynamically during the forward pass by
applying
fun(*parent_values). - Dispatched to:
_init_as_inner_node(parents, fun, args).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Numerical data (scalar int/float, list, or NumPy array). If provided, initialized as a leaf node. |
None
|
parents
|
Sequence[Tensor] | None
|
Upstream antecedent |
None
|
fun
|
Callable[..., Any] | None
|
The forward mathematical function evaluated at this node
(e.g., |
None
|
name
|
str | None
|
Optional human-readable identifier for visualization. |
None
|
args
|
Any
|
Optional static constants passed to |
None
|
Source code in utorch/autograd/tensor.py
__add__
__eq__
__ge__
Element-wise greater or equal: self >= other.
__gt__
__hash__
__le__
__lt__
__matmul__
__mul__
__neg__
__pow__
__radd__
__repr__
Return a string representation of the tensor for inspection.
Source code in utorch/autograd/tensor.py
__rmul__
__rsub__
Right-hand element-wise subtraction: other - self.
__rtruediv__
Right-hand element-wise division: other / self.
__sub__
__truediv__
abs
backward
Execute reverse-mode automatic differentiation (backpropagation).
Computes the gradient of this output tensor with respect to all upstream nodes in the computational graph using the multivariate chain rule.
Because gradients are accumulated as Tensor instances rather than raw
floats, the backward pass dynamically constructs a secondary computation
graph, enabling arbitrary higher-order derivatives (e.g., double backward
for Hessians).
The backward algorithm executes in five distinct pedagogical phases:
- Scalar Verification & Seed Initialization:
- Reverse-mode autograd starts at a scalar objective (e.g., loss).
- The root gradient is seeded with the identity: dL/dL = 1.0.
-
If an explicit
grad_tensoris supplied, it is used as the seed. -
Reverse Topological Ordering:
- Performs a Depth-First Search (DFS) from
selfbackwards to all reachable ancestor nodes. - Arranges nodes so every child appears strictly before its parents.
-
Guarantees that when a node is processed, it has already collected all incoming gradient contributions from all downstream paths.
-
Primitive Derivative Dispatch:
- Traverses nodes in reverse topological order.
-
For each intermediate operation node, looks up its analytical derivative rule in
SCALAR_PRIMITIVESusingnode.op. -
Local Chain Rule Evaluation:
- For binary operations z = f(x, y): dx = d_rule_left(grad_z, x, y) dy = d_rule_right(grad_z, x, y)
-
For unary operations y = f(x): dx = d_rule(grad_y, x)
-
Gradient Accumulation (Multivariate Chain Rule):
- Local gradient contributions are added to antecedent parents using
+=(parent.grad = parent.grad + d_parent). - Accumulation (rather than overwriting) is mathematically mandatory when a variable is reused across multiple paths (diamond graphs).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grad_tensor
|
Tensor | None
|
Seed gradient for this node. If None, defaults to Tensor(1.0). |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called on a non-scalar tensor without an explicit
|
ValueError
|
If an operation node has no matching primitive rule in
|
Examples:
import utorch as ut
# 1. First-order derivative of a polynomial: y = x^2 + 3x
x = ut.Tensor(4.0, name="x")
y = (x ** 2) + 3.0 * x # dy/dx = 2x + 3 = 11.0
y.backward()
print(x.grad.value) # 11.0
# 2. Second-order derivative (double backward): d2y/dx2 = 2.0
first_grad = x.grad
x.zero_grad()
first_grad.backward()
print(x.grad.value) # 2.0
Source code in utorch/autograd/tensor.py
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 | |
clip_min
cos
cosh
exp
log
mean
Compute the arithmetic mean of tensor elements across specified axis.
Source code in utorch/autograd/tensor.py
relu
sigmoid
sign
sin
single_operator
Apply a unary operation taking self as the sole parent node.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fun
|
Callable[..., Any]
|
Unary mathematical function (e.g. np.sin, np.exp). |
required |
args
|
Any
|
Optional static parameters. |
None
|
Returns:
| Type | Description |
|---|---|
Tensor
|
A new Tensor representing the operation's output. |
Source code in utorch/autograd/tensor.py
sum
Sum tensor elements across specified axis.
Source code in utorch/autograd/tensor.py
tanh
transpose
Permute the dimensions of the tensor.
Source code in utorch/autograd/tensor.py
topological_sort
Graph traversal and topological sorting for the autograd engine.
topological_sort
Return nodes in reverse topological order (root first, down to leaves).
Uses DFS with a visited set so shared parent nodes (diamond dependencies) are processed only after all their dependent children have been evaluated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
root
|
Any
|
The output node from which to trace backwards. |
required |
Returns:
| Type | Description |
|---|---|
list[Any]
|
List of nodes starting with root and ending at leaf nodes. |
Source code in utorch/autograd/topological_sort.py
unbroadcast
unbroadcast
Project a broadcasted gradient tensor back to its input operand shape.
During forward evaluation of binary operations (e.g., A + B), smaller operands are implicitly expanded via NumPy broadcasting rules (e.g., a bias vector of shape (10,) added to a batch matrix of shape (32, 10)).
In the backward pass, incoming gradients match the expanded output shape (32, 10). To maintain dimensional alignment, the gradient must be summed across all broadcasted axes so that grad.shape == target_shape.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grad
|
Tensor
|
Incoming gradient Tensor with the expanded shape. |
required |
target_shape
|
tuple[int, ...]
|
The original shape of the parent input Tensor. |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
A new Tensor reduced to match |
Examples:
import numpy as np
import utorch as ut
from utorch.autograd.ops import unbroadcast
# Batch gradient of shape (32, 10)
grad = ut.Tensor(np.ones((32, 10)))
# Unbroadcast to bias vector shape (10,)
bias_grad = unbroadcast(grad, (10,))
print(bias_grad.shape) # (10,)
print(bias_grad.value[0]) # 32.0 (summed across batch dimension 0)