Arithmetic
7 functions — 7 documented.
add¶
Element-wise addition.
Shapes are broadcast by NumPy rules. Broadcasting is implemented by giving the expanded axes stride 0 rather than by materialising a copy — Shape holds strides in bytes and permits 0, so a broadcast operand re-reads the same element instead of allocating an expanded one.
Parameters
- input (Tensor)
- The left operand.
- other (Tensor)
- The right operand, broadcastable against
input.
Returns
A tensor of the broadcast shape.
Exact: the two backends agree bit for bit, so the test suite compares them with no tolerance at all.
Example
>>> a = vkml.tensor(np.array([1.0, 2.0], dtype=np.float32))
>>> b = vkml.tensor(np.array([[10.0], [20.0]], dtype=np.float32))
>>> vkml.add(a, b).numpy()
array([[11., 12.],
[21., 22.]], dtype=float32)
Implementation
| Declared in | include/vkml/api/ops.h:12 |
|---|---|
| Graph node | OpKind::Add |
| CPU kernel | src/backend/cpu/kernels_elementwise.cpp:241 |
| Vulkan shader | shaders/binary.comp (132 lines · 3 specialisation constants) |
| Gradient rule | autograd.cpp:192 |
| History | commits touching the CPU kernel |
| Tests (≥4) | test_f16.py test_nan_semantics.py test_ops_vs_torch.py |
sub¶
Element-wise subtraction, input − other.
Shapes are broadcast by NumPy rules. Broadcasting is implemented by giving the expanded axes stride 0 rather than by materialising a copy — Shape holds strides in bytes and permits 0, so a broadcast operand re-reads the same element instead of allocating an expanded one.
Parameters
- input (Tensor)
- The left operand.
- other (Tensor)
- The right operand, broadcastable against
input.
Returns
A tensor of the broadcast shape.
Exact on both backends.
Example
>>> vkml.sub(vkml.tensor(np.array([5.0, 3.0], dtype=np.float32)),
... vkml.tensor(np.array([2.0, 1.0], dtype=np.float32))).numpy()
array([3., 2.], dtype=float32)
Implementation
| Declared in | include/vkml/api/ops.h:13 |
|---|---|
| Graph node | OpKind::Sub |
| CPU kernel | src/backend/cpu/kernels_elementwise.cpp:245 |
| Vulkan shader | shaders/binary.comp (132 lines · 3 specialisation constants) |
| Gradient rule | autograd.cpp:197 |
| History | commits touching the CPU kernel |
| Tests (≥3) | test_ops_vs_torch.py |
mul¶
Element-wise multiplication.
Shapes are broadcast by NumPy rules. Broadcasting is implemented by giving the expanded axes stride 0 rather than by materialising a copy — Shape holds strides in bytes and permits 0, so a broadcast operand re-reads the same element instead of allocating an expanded one.
Parameters
- input (Tensor)
- The left operand.
- other (Tensor)
- The right operand, broadcastable against
input.
Returns
A tensor of the broadcast shape.
Exact on both backends.
Example
>>> vkml.mul(vkml.tensor(np.array([2.0, 3.0], dtype=np.float32)),
... vkml.tensor(np.array([4.0, 5.0], dtype=np.float32))).numpy()
array([ 8., 15.], dtype=float32)
Implementation
| Declared in | include/vkml/api/ops.h:14 |
|---|---|
| Graph node | OpKind::Mul |
| CPU kernel | src/backend/cpu/kernels_elementwise.cpp:261 |
| Vulkan shader | shaders/binary.comp (132 lines · 3 specialisation constants) |
| Gradient rule | autograd.cpp:202 |
| Decisions | ADR 0013 |
| Benchmarked | mul |
| History | commits touching the CPU kernel |
| Tests (≥18) | test_device_limits.py test_nan_semantics.py test_nn_vs_torch.py test_ops_vs_torch.py test_vulkan_kernels.py |
div¶
Element-wise division, input / other.
IEEE division throughout: x/0 is ±inf and 0/0 is NaN, rather than raising.
Shapes are broadcast by NumPy rules. Broadcasting is implemented by giving the expanded axes stride 0 rather than by materialising a copy — Shape holds strides in bytes and permits 0, so a broadcast operand re-reads the same element instead of allocating an expanded one.
Parameters
- input (Tensor)
- The left operand.
- other (Tensor)
- The right operand, broadcastable against
input.
Returns
A tensor of the broadcast shape.
Example
>>> vkml.div(vkml.tensor(np.array([1.0, 0.0], dtype=np.float32)),
... vkml.tensor(np.array([2.0, 0.0], dtype=np.float32))).numpy()
array([0.5, nan], dtype=float32)
Implementation
| Declared in | include/vkml/api/ops.h:15 |
|---|---|
| Graph node | OpKind::Div |
| CPU kernel | src/backend/cpu/kernels_elementwise.cpp:265 |
| Vulkan shader | shaders/binary.comp (132 lines · 3 specialisation constants) |
| Gradient rule | autograd.cpp:235 |
| History | commits touching the CPU kernel |
| Tests (≥1) | test_ops_vs_torch.py |
See also mul, reciprocal
pow¶
Element-wise power, input ** other.
Matches std::pow, not GLSL's pow. GLSL leaves pow(x, y) explicitly undefined for x < 0, while std::pow is defined there whenever y is an integer — std::pow(-2, 3) is -8, not NaN. Leaving the built-in to handle it would make the two backends disagree on a case the CPU answers perfectly well, so the shader peels the sign off and reapplies it.
Shapes are broadcast by NumPy rules. Broadcasting is implemented by giving the expanded axes stride 0 rather than by materialising a copy — Shape holds strides in bytes and permits 0, so a broadcast operand re-reads the same element instead of allocating an expanded one.
Parameters
- input (Tensor)
- The left operand.
- other (Tensor)
- The right operand, broadcastable against
input.
Returns
A tensor of the broadcast shape.
Example
>>> vkml.pow(vkml.tensor(np.array([-2.0, 2.0], dtype=np.float32)),
... vkml.tensor(np.array([3.0, 3.0], dtype=np.float32))).numpy()
array([-8., 8.], dtype=float32)
In the Vulkan kernel
pow matching std::pow, which the CPU kernel uses.
GLSL's pow(x, y) is explicitly undefined for x < 0, but std::pow is defined there whenever y is an integer -- std::pow(-2, 3) is -8, not NaN. Leaving the built-in to handle it would make the two backends disagree on a case the CPU answers perfectly well, so the sign is peeled off and reapplied.
Implementation
| Declared in | include/vkml/api/ops.h:16 |
|---|---|
| Graph node | OpKind::Pow |
| CPU kernel | src/backend/cpu/kernels_elementwise.cpp:269 |
| Vulkan shader | shaders/binary.comp (132 lines · 3 specialisation constants) |
| Gradient rule | autograd.cpp:242 |
| History | commits touching the CPU kernel |
| Tests (≥5) | test_autograd_vs_torch.py test_ops_vs_torch.py test_vulkan_kernels.py |
maximum¶
Element-wise maximum of two tensors.
NaN propagates, matching torch.maximum. GLSL's max() is undefined for NaN and std::fmax returns the non-NaN operand, so the shader tests for NaN explicitly and returns a quiet NaN written from its bit pattern — not 0.0/0.0, which the compiler is free to constant-fold or treat as undefined.
This is tested as exact, so the difference is not academic.
Shapes are broadcast by NumPy rules. Broadcasting is implemented by giving the expanded axes stride 0 rather than by materialising a copy — Shape holds strides in bytes and permits 0, so a broadcast operand re-reads the same element instead of allocating an expanded one.
Parameters
- input (Tensor)
- The left operand.
- other (Tensor)
- The right operand, broadcastable against
input.
Returns
A tensor of the broadcast shape.
Example
>>> vkml.maximum(vkml.tensor(np.array([1.0, float('nan')], dtype=np.float32)),
... vkml.tensor(np.array([2.0, 5.0], dtype=np.float32))).numpy()
array([ 2., nan], dtype=float32)
Implementation
| Declared in | include/vkml/api/ops.h:17 |
|---|---|
| Graph node | OpKind::Maximum |
| CPU kernel | src/backend/cpu/kernels_elementwise.cpp:273 |
| Vulkan shader | composed, or dispatched through a shared kernel |
| Gradient rule | autograd.cpp:253 |
| History | commits touching the CPU kernel |
| Tests (≥1) | test_nan_semantics.py |
See also minimum, clamp_min, amax, relu
minimum¶
Element-wise minimum of two tensors.
NaN propagates, by the same explicit test as maximum.
Shapes are broadcast by NumPy rules. Broadcasting is implemented by giving the expanded axes stride 0 rather than by materialising a copy — Shape holds strides in bytes and permits 0, so a broadcast operand re-reads the same element instead of allocating an expanded one.
Parameters
- input (Tensor)
- The left operand.
- other (Tensor)
- The right operand, broadcastable against
input.
Returns
A tensor of the broadcast shape.
Example
>>> vkml.minimum(vkml.tensor(np.array([1.0, float('nan')], dtype=np.float32)),
... vkml.tensor(np.array([2.0, 5.0], dtype=np.float32))).numpy()
array([ 1., nan], dtype=float32)
Implementation
| Declared in | include/vkml/api/ops.h:18 |
|---|---|
| Graph node | OpKind::Minimum |
| CPU kernel | src/backend/cpu/kernels_elementwise.cpp:283 |
| Vulkan shader | composed, or dispatched through a shared kernel |
| Gradient rule | autograd.cpp:258 |
| History | commits touching the CPU kernel |
| Tests | none found by name |