From 7afa5e395aed0fc836f5ba6b419d9912ef209443 Mon Sep 17 00:00:00 2001 From: Julius Kunze Date: Wed, 3 May 2017 12:03:18 +0200 Subject: [PATCH] Added TensorFlow sample (#541) * Added TensorFlow sample * Added graph to TensorFlow sample * Added session to TensorFlow sample * added description to readme, cleaned up tensorflow.def, used lambda for deallocator, call Status.delete on error --- samples/tensorflow/HelloTensorflow.kt | 228 ++++++++++++++++++++++++++ samples/tensorflow/README.md | 21 +++ samples/tensorflow/build.sh | 23 +++ samples/tensorflow/tensorflow.def | 2 + 4 files changed, 274 insertions(+) create mode 100644 samples/tensorflow/HelloTensorflow.kt create mode 100644 samples/tensorflow/README.md create mode 100755 samples/tensorflow/build.sh create mode 100644 samples/tensorflow/tensorflow.def diff --git a/samples/tensorflow/HelloTensorflow.kt b/samples/tensorflow/HelloTensorflow.kt new file mode 100644 index 00000000000..84fae1d6e27 --- /dev/null +++ b/samples/tensorflow/HelloTensorflow.kt @@ -0,0 +1,228 @@ +import kotlinx.cinterop.* +import tensorflow.* + +typealias Status = CPointer +typealias Operation = CPointer +typealias Tensor = CPointer + +val Status.isOk: Boolean get() = TF_GetCode(this) == TF_OK +val Status.errorMessage: String get() = TF_Message(this)!!.toKString() +fun Status.delete() = TF_DeleteStatus(this) +fun Status.validate() { + try { + if (!isOk) { + throw Error("Status is not ok: $errorMessage") + } + } finally { + delete() + } +} + +inline fun statusValidated(block: (Status) -> T): T { + val status = TF_NewStatus()!! + val result = block(status) + status.validate() + return result +} + +fun scalarTensor(value: Int): Tensor { + val data = nativeHeap.allocArray(1) + data[0] = value + + return TF_NewTensor(TF_INT32, + dims = null, num_dims = 0, + data = data, len = IntVar.size, + deallocator = staticCFunction { dataToFree, _, _ -> nativeHeap.free(dataToFree!!.reinterpret()) }, + deallocator_arg = null)!! +} + +val Tensor.scalarIntValue: Int get() { + if (TF_INT32 != TF_TensorType(this) || IntVar.size != TF_TensorByteSize(this)) { + throw Error("Tensor is not of type int.") + } + if (0 != TF_NumDims(this)) { + throw Error("Tensor is not scalar.") + } + + return TF_TensorData(this)!!.reinterpret().pointed.value +} + + +class Graph { + val tensorflowGraph = TF_NewGraph()!! + + inline fun operation(type: String, name: String, initDescription: (CPointer) -> Unit): Operation { + val description = TF_NewOperation(tensorflowGraph, type, name)!! + initDescription(description) + return statusValidated { TF_FinishOperation(description, it)!! } + } + + fun constant(value: Int, name: String = "scalarIntConstant") = operation("Const", name) { description -> + statusValidated { TF_SetAttrTensor(description, "value", scalarTensor(value), it) } + TF_SetAttrType(description, "dtype", TF_INT32) + } + + fun intInput(name: String = "input") = operation("Placeholder", name) { description -> + TF_SetAttrType(description, "dtype", TF_INT32) + } + + fun add(left: Operation, right: Operation, name: String = "add") = memScoped { + val inputs = allocArray(2) + inputs[0].apply { oper = left; index = 0 } + inputs[1].apply { oper = right; index = 0 } + + operation("AddN", name) { description -> + TF_AddInputList(description, inputs, 2) + } + } + + // TODO set unique operation names + operator fun Operation.plus(right: Operation) = add(this, right) + + inline fun withSession(block: Session.() -> T): T { + val session = Session(this) + try { + return session.block() + } finally { + session.dispose() + } + } +} + +class Session(val graph: Graph) { + private val inputs = mutableListOf() + private val inputValues = mutableListOf() + private var outputs = mutableListOf() + private val outputValues = mutableListOf() + private val targets = listOf() + + private fun createNewSession(): CPointer { + val options = TF_NewSessionOptions() + val session = statusValidated { TF_NewSession(graph.tensorflowGraph, options, it)!! } + TF_DeleteSessionOptions(options) + return session + } + + private var tensorflowSession: CPointer? = createNewSession() + + private fun clearInputValues() { + for (inputValue in inputValues) { + TF_DeleteTensor(inputValue) + } + + inputValues.clear() + } + + private fun clearOutputValues() { + for (outputValue in outputValues) { + if (outputValue != null) + TF_DeleteTensor(outputValue) + } + outputValues.clear() + } + + fun dispose() { + clearInputValues() + clearOutputValues() + clearInputs() + clearOutputs() + + if (tensorflowSession != null) { + statusValidated { TF_CloseSession(tensorflowSession, it) } + statusValidated { TF_DeleteSession(tensorflowSession, it) } + tensorflowSession = null + } + } + + private fun setInputsWithValues(inputsWithValues: List>) { + clearInputValues() + clearInputs() + for ((input, inputValue) in inputsWithValues) { + this.inputs.add(nativeHeap.alloc().apply { oper = input; index = 0 }) + inputValues.add(inputValue) + } + } + + private fun setOutputs(outputs: List) { + clearOutputValues() + clearOutputs() + this.outputs = outputs.map { nativeHeap.alloc().apply { oper = it; index = 0 } }.toMutableList() + } + + private fun clearOutputs() { + this.outputs.forEach { nativeHeap.free(it) } + this.outputs.clear() + } + + private fun clearInputs() { + this.inputs.forEach { nativeHeap.free(it) } + this.inputs.clear() + } + + operator fun invoke(outputs: List, inputsWithValues: List> = listOf()): List { + setInputsWithValues(inputsWithValues) + setOutputs(outputs) + + return invoke() + } + + operator fun invoke(output: Operation, inputsWithValues: List> = listOf()) = + invoke(listOf(output), inputsWithValues).single()!! + + operator fun invoke(): List { + if (inputs.size != inputValues.size) { + throw Error("Call SetInputs() before Run()") + } + clearOutputValues() + + val inputsCArray = if (inputs.any()) nativeHeap.allocArray(inputs.size) else null + + inputs.forEachIndexed { i, input -> + inputsCArray!![i].apply { + oper = input.oper + index = input.index + } + } + + val outputsCArray = if (outputs.any()) nativeHeap.allocArray(outputs.size) else null + + outputs.forEachIndexed { i, output -> + outputsCArray!![i].apply { + oper = output.oper + index = output.index + } + } + + memScoped { + val outputValuesCArray = allocArrayOfPointersTo(outputs.map { null }) + + statusValidated { + TF_SessionRun(tensorflowSession, null, + inputsCArray, inputValues.toCValues(), inputs.size, + outputsCArray, outputValuesCArray, outputs.size, + targets.toCValues(), targets.size, + null, it) + } + + for (index in outputs.indices) { + outputValues.add(outputValuesCArray[index]) + } + } + + clearInputValues() + + return outputValues + } +} + +fun main(args: Array) { + println("Hello, TensorFlow ${TF_Version()!!.toKString()}!") + + val result = Graph().run { + val input = intInput() + + withSession { invoke(input + constant(2), inputsWithValues = listOf(input to scalarTensor(3))).scalarIntValue } + } + + println("3 + 2 is $result.") +} \ No newline at end of file diff --git a/samples/tensorflow/README.md b/samples/tensorflow/README.md new file mode 100644 index 00000000000..5e9db763f5d --- /dev/null +++ b/samples/tensorflow/README.md @@ -0,0 +1,21 @@ +# TensorFlow demo + +Small Hello World calculation on the [TensorFlow](https://www.tensorflow.org/) backend, +arranging simple operations into a graph and running it on a session. +Like other [TensorFlow clients](https://www.tensorflow.org/extend/language_bindings) +(e. g. for [Python](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/python/client)), +this example is built on top of the +[TensorFlow C API](https://github.com/tensorflow/tensorflow/blob/r1.1/tensorflow/c/c_api.h), +showing how a TensorFlow client in Kotlin/Native could look like. + +##Installation + +[Install TensorFlow for C](https://www.tensorflow.org/versions/r1.1/install/install_c) into `/usr/local`. + +Compile: + + ./build.sh + +Run: + + ./HelloTensorflow.kexe \ No newline at end of file diff --git a/samples/tensorflow/build.sh b/samples/tensorflow/build.sh new file mode 100755 index 00000000000..c7b14a117ee --- /dev/null +++ b/samples/tensorflow/build.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +PATH=../../dist/bin:../../bin:$PATH +DIR=. + +if [ x$TARGET == x ]; then +case "$OSTYPE" in + darwin*) TARGET=macbook ;; + linux*) TARGET=linux ;; + *) echo "unknown: $OSTYPE" && exit 1;; +esac +fi + +var=CFLAGS_${TARGET} +CFLAGS=${!var} +var=LINKER_ARGS_${TARGET} +LINKER_ARGS=${!var} +var=COMPILER_ARGS_${TARGET} +COMPILER_ARGS=${!var} # add -opt for an optimized build. + +cinterop -def $DIR/tensorflow.def -copt "$CFLAGS" -target $TARGET -o tensorflow.kt.bc || exit 1 +konanc $COMPILER_ARGS -target $TARGET $DIR/HelloTensorflow.kt -library tensorflow.kt.bc -o HelloTensorflow.kexe \ + -linkerArgs "-L/usr/local/lib -ltensorflow" || exit 1 diff --git a/samples/tensorflow/tensorflow.def b/samples/tensorflow/tensorflow.def new file mode 100644 index 00000000000..c2114b83511 --- /dev/null +++ b/samples/tensorflow/tensorflow.def @@ -0,0 +1,2 @@ +headers = tensorflow/c/c_api.h +compilerOpts = -I/usr/local/include -L/usr/local/lib \ No newline at end of file