Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ include("skainet-data:skainet-data-simple")
// ====== IO
include("skainet-io:skainet-io-core")
include("skainet-io:skainet-io-gguf")
include("skainet-io:skainet-io-image")

// ====== models
include("skainet-models:skainet-model-yolo")

// ====== Integrations
include("skainet-integrations:skainet-simple-cpu")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import sk.ainet.lang.types.DType
import sk.ainet.lang.ops.TensorOp
import sk.ainet.lang.ops.InProgress
import sk.ainet.lang.tensor.data.TensorDataFactory
import sk.ainet.lang.tensor.ops.UpsampleMode

@InProgress("cpu", owner = "team:cpu", issue = "task-ops.md#defaultcpuops")
public open class DefaultCpuOpsBase(protected val dataFactory: TensorDataFactory) : TensorOps {
Expand Down Expand Up @@ -536,6 +537,37 @@ public open class DefaultCpuOpsBase(protected val dataFactory: TensorDataFactory
return CpuTensor(outData, this, input.dtype)
}

@TensorOp()
@InProgress("cpu", owner = "team:cpu", issue = "task-ops.md#op-upsample2d")
override fun <T : DType, V> upsample2d(
input: Tensor<T, V>,
scale: Pair<Int, Int>,
mode: UpsampleMode,
alignCorners: Boolean
): Tensor<T, V> {
require(input.rank == 4) { "upsample2d: input must be 4D (N, C, H, W)" }
val (scaleH, scaleW) = scale
require(scaleH > 0 && scaleW > 0) { "upsample2d: scale factors must be positive" }
require(mode == UpsampleMode.Nearest) { "upsample2d: only Nearest mode is implemented on CPU backend" }

val n = input.shape[0]
val c = input.shape[1]
val inH = input.shape[2]
val inW = input.shape[3]
val outH = inH * scaleH
val outW = inW * scaleW
val outShape = Shape(n, c, outH, outW)

val outData = dataFactory.init<T, V>(outShape, input.dtype) { idx ->
val oh = idx[2]
val ow = idx[3]
val ih = oh / scaleH
val iw = ow / scaleW
input.data.get(idx[0], idx[1], ih, iw)
}
return CpuTensor(outData, this, input.dtype)
}

@TensorOp()
@InProgress("cpu", owner = "team:cpu", issue = "task-ops.md#op-maxpool2d")
override fun <T : DType, V> maxPool2d(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package sk.ainet.sk.ainet.exec.tensor.ops

import kotlin.test.Test
import kotlin.test.assertEquals
import sk.ainet.context.DirectCpuExecutionContext
import sk.ainet.context.data
import sk.ainet.lang.tensor.Shape
import sk.ainet.lang.tensor.dsl.tensor
import sk.ainet.lang.tensor.ops.UpsampleMode
import sk.ainet.lang.types.FP32

class DefaultCpuOpsUpsampleTest {
private val ctx = DirectCpuExecutionContext()
private val ops = ctx.ops

@Test
fun nearest_mode_repeats_values() {
data(ctx) { _ ->
val input = tensor<FP32, Float> {
shape(1, 1, 2, 2) {
init { idx -> (1 + idx[2] * 2 + idx[3]).toFloat() }
}
}

val upsampled = ops.upsample2d(
input = input,
scale = 2 to 2,
mode = UpsampleMode.Nearest,
alignCorners = false
)

assertEquals(Shape(1, 1, 4, 4), upsampled.shape)
assertEquals(1f, upsampled.data[0, 0, 0, 0])
assertEquals(2f, upsampled.data[0, 0, 0, 2])
assertEquals(3f, upsampled.data[0, 0, 2, 0])
assertEquals(4f, upsampled.data[0, 0, 3, 3])
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sk.ainet.lang.trace
import sk.ainet.lang.tensor.Shape
import sk.ainet.lang.tensor.Tensor
import sk.ainet.lang.tensor.ops.TensorOps
import sk.ainet.lang.tensor.ops.UpsampleMode
import sk.ainet.lang.types.DType

/**
Expand Down Expand Up @@ -77,12 +78,49 @@ public class TracingTensorOps(
kernelSize: Pair<Int, Int>,
stride: Pair<Int, Int>,
padding: Pair<Int, Int>
): Tensor<T, V> = base.maxPool2d(input, kernelSize, stride, padding)
): Tensor<T, V> {
val out = base.maxPool2d(input, kernelSize, stride, padding)
val attrs = mapOf("kernel" to kernelSize, "stride" to stride, "padding" to padding)
sink.onOpExecuted(
OpTrace(
opType = "maxPool2d",
inputs = listOf(session.refOf(input)),
outputs = listOf(session.refOf(out)),
attributes = attrs
)
)
return out
}

override fun <T : DType, V> upsample2d(
input: Tensor<T, V>,
scale: Pair<Int, Int>,
mode: UpsampleMode,
alignCorners: Boolean
): Tensor<T, V> {
val out = base.upsample2d(input, scale, mode, alignCorners)
val inputs = listOf(session.refOf(input))
val outputs = listOf(session.refOf(out))
val attrs = OpAttributeFactory.shapesAndDTypes(listOf(input), listOf(out)) + mapOf(
"scale" to listOf(scale.first, scale.second),
"mode" to mode.name,
"alignCorners" to alignCorners
)
sink.onOpExecuted(OpTrace(opType = "upsample2d", inputs = inputs, outputs = outputs, attributes = attrs))
return out
}

// ---- Shape ops ----
override fun <T : DType, V> reshape(tensor: Tensor<T, V>, newShape: Shape): Tensor<T, V> = base.reshape(tensor, newShape)
override fun <T : DType, V> flatten(tensor: Tensor<T, V>, startDim: Int, endDim: Int): Tensor<T, V> = base.flatten(tensor, startDim, endDim)
override fun <T : DType, V> concat(tensors: List<Tensor<T, V>>, dim: Int): Tensor<T, V> = base.concat(tensors, dim)
override fun <T : DType, V> concat(tensors: List<Tensor<T, V>>, dim: Int): Tensor<T, V> {
val out = base.concat(tensors, dim)
val inputs = tensors.map { session.refOf(it) }
val outputs = listOf(session.refOf(out))
val attrs = mapOf("dim" to dim, "count" to tensors.size)
sink.onOpExecuted(OpTrace(opType = "concat", inputs = inputs, outputs = outputs, attributes = attrs))
return out
}
override fun <T : DType, V> split(tensor: Tensor<T, V>, splitSize: Int, dim: Int): List<Tensor<T, V>> = base.split(tensor, splitSize, dim)
override fun <T : DType, V> squeeze(tensor: Tensor<T, V>, dim: Int?): Tensor<T, V> = base.squeeze(tensor, dim)
override fun <T : DType, V> unsqueeze(tensor: Tensor<T, V>, dim: Int): Tensor<T, V> = base.unsqueeze(tensor, dim)
Expand All @@ -97,6 +135,15 @@ public class TracingTensorOps(
return out
}

override fun <T : DType, V> silu(tensor: Tensor<T, V>): Tensor<T, V> {
val out = base.silu(tensor)
val inputs = listOf(session.refOf(tensor))
val outputs = listOf(session.refOf(out))
val attrs = OpAttributeFactory.unary(tensor, out)
sink.onOpExecuted(OpTrace(opType = "silu", inputs = inputs, outputs = outputs, attributes = attrs))
return out
}

override fun <T : DType, V> softmax(tensor: Tensor<T, V>, dim: Int): Tensor<T, V> {
val out = base.softmax(tensor, dim)
val inputs = listOf(session.refOf(tensor))
Expand All @@ -114,7 +161,6 @@ public class TracingTensorOps(
sink.onOpExecuted(OpTrace(opType = "sigmoid", inputs = inputs, outputs = outputs, attributes = attrs))
return out
}
override fun <T : DType, V> silu(tensor: Tensor<T, V>): Tensor<T, V> = base.silu(tensor)
override fun <T : DType, V> gelu(tensor: Tensor<T, V>): Tensor<T, V> = base.gelu(tensor)

// ---- Reductions ----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,38 @@ internal class RecordingTensorOpsDecorator(private val base: TensorOps) : Tensor
return out
}

override fun <T : DType, V> upsample2d(
input: Tensor<T, V>,
scale: Pair<Int, Int>,
mode: UpsampleMode,
alignCorners: Boolean
): Tensor<T, V> {
val out = base.upsample2d(input, scale, mode, alignCorners)
val params = mapOf(
"scale" to scale.toList(),
"mode" to mode.name,
"alignCorners" to alignCorners
)
record(Upsample2dOperation<T, V>(params), listOf(input), listOf(out))
return out
}

override fun <T : DType, V> concat(tensors: List<Tensor<T, V>>, dim: Int): Tensor<T, V> {
val out = base.concat(tensors, dim)
record(
ConcatRecordingOperation(mapOf("dim" to dim, "count" to tensors.size)),
tensors,
listOf(out)
)
return out
}

override fun <T : DType, V> silu(tensor: Tensor<T, V>): Tensor<T, V> {
val out = base.silu(tensor)
record(SimpleActivationOperation("silu"), listOf(tensor), listOf(out))
return out
}

// --- Shape ops ---
override fun <T : DType, V> reshape(tensor: Tensor<T, V>, newShape: Shape): Tensor<T, V> {
val out = base.reshape(tensor, newShape)
Expand Down Expand Up @@ -285,14 +317,60 @@ internal class RecordingTensorOpsDecorator(private val base: TensorOps) : Tensor
}

// Delegations for unrecorded/less critical ops
override fun <T : DType, V> concat(tensors: List<Tensor<T, V>>, dim: Int): Tensor<T, V> = base.concat(tensors, dim)
override fun <T : DType, V> split(tensor: Tensor<T, V>, splitSize: Int, dim: Int): List<Tensor<T, V>> = base.split(tensor, splitSize, dim)
override fun <T : DType, V> silu(tensor: Tensor<T, V>): Tensor<T, V> = base.silu(tensor)
override fun <T : DType, V> gelu(tensor: Tensor<T, V>): Tensor<T, V> = base.gelu(tensor)
override fun <T : DType, V> sum(tensor: Tensor<T, V>, dim: Int?): Tensor<T, V> = base.sum(tensor, dim)
override fun <T : DType, V> mean(tensor: Tensor<T, V>, dim: Int?): Tensor<T, V> = base.mean(tensor, dim)
override fun <T : DType, V> variance(tensor: Tensor<T, V>, dim: Int?): Tensor<T, V> = base.variance(tensor, dim)
override fun <T : DType, V> sqrt(tensor: Tensor<T, V>): Tensor<T, V> = base.sqrt(tensor)
override fun <T : DType, TTo : DType, V> convert(tensor: Tensor<T, V>, targetType: TTo): Tensor<TTo, V> = base.convert(tensor, targetType)
override fun <T : DType, V> tril(tensor: Tensor<T, V>, k: Int): Tensor<T, V> = base.tril(tensor, k)
}
}

private class ConcatRecordingOperation(
override val parameters: Map<String, Any> = emptyMap()
) : BaseOperation("concat", "shape", parameters) {
override fun <T : DType, V> execute(inputs: List<Tensor<T, V>>): List<Tensor<T, V>> =
throw UnsupportedOperationException("execute not supported in recording op")

override fun validateInputs(inputs: List<TensorSpec>): ValidationResult =
if (inputs.size < 2) ValidationResult.Invalid(listOf("concat requires at least 2 inputs")) else ValidationResult.Valid

override fun inferOutputs(inputs: List<TensorSpec>): List<TensorSpec> {
// validateInputs ensures at least 2 inputs, so first() is safe
val first = inputs.first()
return listOf(
TensorSpec(
name = "concat_output",
shape = first.shape,
dtype = first.dtype,
requiresGrad = inputs.any { it.requiresGrad }
)
)
}

override fun clone(newParameters: Map<String, Any>): Operation = ConcatRecordingOperation(newParameters)
}

private class SimpleActivationOperation(
activationName: String
) : BaseOperation(activationName, "activation", emptyMap()) {
override fun <T : DType, V> execute(inputs: List<Tensor<T, V>>): List<Tensor<T, V>> =
throw UnsupportedOperationException("execute not supported in recording op")

override fun validateInputs(inputs: List<TensorSpec>): ValidationResult =
if (inputs.size != 1) ValidationResult.Invalid(listOf("Activation requires 1 input")) else ValidationResult.Valid

override fun inferOutputs(inputs: List<TensorSpec>): List<TensorSpec> =
listOf(
TensorSpec(
name = "activation_output",
// validateInputs ensures exactly 1 input, so first() is safe
shape = inputs.first().shape,
dtype = inputs.first().dtype,
requiresGrad = inputs.any { it.requiresGrad }
)
)

override fun clone(newParameters: Map<String, Any>): Operation = SimpleActivationOperation(name)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class GraphTensorOps(
override fun <T : DType, V> add(a: Tensor<T, V>, b: Tensor<T, V>): Tensor<T, V> {
val result = baseOps.add(a, b)
if (executionContext.isRecording) {
executionContext.currentTape?.recordOperation(AddOperation<T,V>(), listOf(a, b), listOf(result))
executionContext.currentTape?.recordOperation(AddOperation<T, V>(), listOf(a, b), listOf(result))
}
return result
}
Expand Down Expand Up @@ -48,24 +48,40 @@ public class GraphTensorOps(
padding: Pair<Int, Int>
): Tensor<T, V> = baseOps.maxPool2d(input, kernelSize, stride, padding)

override fun <T : DType, V> reshape(tensor: Tensor<T, V>, newShape: Shape): Tensor<T, V> = baseOps.reshape(tensor, newShape)
override fun <T : DType, V> flatten(tensor: Tensor<T, V>, startDim: Int, endDim: Int): Tensor<T, V> = baseOps.flatten(tensor, startDim, endDim)
override fun <T : DType, V> upsample2d(
input: Tensor<T, V>,
scale: Pair<Int, Int>,
mode: UpsampleMode,
alignCorners: Boolean
): Tensor<T, V> = baseOps.upsample2d<T, V>(input, scale, mode, alignCorners)

override fun <T : DType, V> reshape(tensor: Tensor<T, V>, newShape: Shape): Tensor<T, V> =
baseOps.reshape(tensor, newShape)

override fun <T : DType, V> flatten(tensor: Tensor<T, V>, startDim: Int, endDim: Int): Tensor<T, V> =
baseOps.flatten(tensor, startDim, endDim)

override fun <T : DType, V> relu(tensor: Tensor<T, V>): Tensor<T, V> = baseOps.relu(tensor)
override fun <T : DType, V> softmax(tensor: Tensor<T, V>, dim: Int): Tensor<T, V> = baseOps.softmax(tensor, dim)
override fun <T : DType, V> sigmoid(tensor: Tensor<T, V>): Tensor<T, V> = baseOps.sigmoid(tensor)
override fun <T : DType, V> silu(tensor: Tensor<T, V>): Tensor<T, V> = baseOps.silu(tensor)
override fun <T : DType, V> gelu(tensor: Tensor<T, V>): Tensor<T, V> = baseOps.gelu(tensor)

override fun <T : DType, V> concat(tensors: List<Tensor<T, V>>, dim: Int): Tensor<T, V> = baseOps.concat(tensors, dim)
override fun <T : DType, V> split(tensor: Tensor<T, V>, splitSize: Int, dim: Int): List<Tensor<T, V>> = baseOps.split(tensor, splitSize, dim)
override fun <T : DType, V> concat(tensors: List<Tensor<T, V>>, dim: Int): Tensor<T, V> =
baseOps.concat(tensors, dim)

override fun <T : DType, V> split(tensor: Tensor<T, V>, splitSize: Int, dim: Int): List<Tensor<T, V>> =
baseOps.split(tensor, splitSize, dim)

override fun <T : DType, V> squeeze(tensor: Tensor<T, V>, dim: Int?): Tensor<T, V> = baseOps.squeeze(tensor, dim)
override fun <T : DType, V> unsqueeze(tensor: Tensor<T, V>, dim: Int): Tensor<T, V> = baseOps.unsqueeze(tensor, dim)

override fun <T : DType, V> sum(tensor: Tensor<T, V>, dim: Int?): Tensor<T, V> = baseOps.sum(tensor, dim)
override fun <T : DType, V> mean(tensor: Tensor<T, V>, dim: Int?): Tensor<T, V> = baseOps.mean(tensor, dim)
override fun <T : DType, V> variance(tensor: Tensor<T, V>, dim: Int?): Tensor<T, V> = baseOps.variance(tensor, dim)
override fun <T : DType, V> sqrt(tensor: Tensor<T, V>): Tensor<T, V> = baseOps.sqrt(tensor)
override fun <TFrom : DType, TTo : DType, V> convert(tensor: Tensor<TFrom, V>, targetType: TTo): Tensor<TTo, V> = baseOps.convert(tensor, targetType)
override fun <TFrom : DType, TTo : DType, V> convert(tensor: Tensor<TFrom, V>, targetType: TTo): Tensor<TTo, V> =
baseOps.convert(tensor, targetType)

override fun <T : DType, V> tril(tensor: Tensor<T, V>, k: Int): Tensor<T, V> = baseOps.tril(tensor, k)
}
Loading
Loading