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)
    }