Apply tensor operations
This page covers using tensors once you have them. To build them in the first place, see Build tensors with the data DSL; for the exhaustive op list, see Operator reference.
Every snippet is compiled and executed in CI from skainet-docs-samples
(TensorBasics.kt).
Operations are extensions on Tensor
SKaiNET’s eager ops are extension functions on Tensor<T, V>. There is no execution
context to thread through each call — the tensor carries its own backend ops, so an
op reads exactly like the math:
fun ops(ctx: DirectCpuExecutionContext): Tensor<FP32, Float> {
lateinit var a: Tensor<FP32, Float>
lateinit var b: Tensor<FP32, Float>
data(ctx) {
a = tensor { shape(2, 3) { from(1f, 2f, 3f, 4f, 5f, 6f) } }
b = tensor { shape(3, 2) { from(1f, 0f, 0f, 1f, 1f, 1f) } }
}
val product = a.matmul(b) // [2,3] x [3,2] -> [2,2]
val transposed = product.t() // [2,2] -> [2,2]
val flat = transposed.reshape(Shape(4))
return flat.relu()
}
matmul does the [2,3] x [3,2] → [2,2] product, t() transposes, reshape
changes the view to [4], and relu() is an elementwise activation. Binary
operators (+, -, *, /) are overloaded for both tensor-tensor and
tensor-scalar forms.
Broadcasting
Elementwise binary ops broadcast operands with compatible shapes, following the usual
right-aligned rules. A per-column bias of shape [1, 3] adds across every row of a
[2, 3] matrix; a scalar broadcasts to every element:
fun broadcast(ctx: DirectCpuExecutionContext): Tensor<FP32, Float> {
lateinit var matrix: Tensor<FP32, Float>
lateinit var bias: Tensor<FP32, Float>
data(ctx) {
matrix = tensor { shape(2, 3) { from(1f, 2f, 3f, 4f, 5f, 6f) } }
bias = tensor { shape(1, 3) { from(10f, 20f, 30f) } }
}
val biased = matrix + bias // [2,3] + [1,3] -> [2,3]
return biased + 100f // scalar broadcasts to every element
}
Constructing the inputs
For completeness, the tensors above are built with the data DSL — one tensor, or several captured into `lateinit var`s:
fun oneTensor(ctx: DirectCpuExecutionContext): Tensor<FP32, Float> =
data<FP32, Float>(ctx) {
tensor {
shape(2, 2) { from(1f, 2f, 3f, 4f) }
}
}
The initialization strategies available inside shape(…) { … }:
fun initStrategies(ctx: DirectCpuExecutionContext): List<Tensor<FP32, Float>> {
lateinit var zeros: Tensor<FP32, Float>
lateinit var ones: Tensor<FP32, Float>
lateinit var filled: Tensor<FP32, Float>
lateinit var gaussian: Tensor<FP32, Float>
lateinit var ramp: Tensor<FP32, Float>
data(ctx) {
zeros = tensor { shape(2, 3) { zeros() } }
ones = tensor { shape(2, 3) { ones() } }
filled = tensor { shape(2, 3) { full(0.5f) } }
gaussian = tensor { shape(2, 3) { randn(mean = 0f, std = 0.02f) } }
ramp = tensor { shape(2, 3) { init { idx -> (idx[0] + idx[1]).toFloat() } } }
}
return listOf(zeros, ones, filled, gaussian, ramp)
}
Related
-
Operator reference — the full, KSP-generated op surface.
-
Kotlin getting started — use these ops inside a model.