From 0cb2c3f0da47d7e9038efa77aebbb5ec34f75607 Mon Sep 17 00:00:00 2001 From: rgrit91 Date: Sat, 9 Jan 2021 17:13:38 +0000 Subject: [PATCH] Dropping support for buffered NDStructures --- .../kscience/kmath/structures/NDStructure.kt | 91 ++++----- kmath-torch/README.md | 26 +-- kmath-torch/build.gradle.kts | 1 + kmath-torch/ctorch/include/ctorch.h | 59 +++--- kmath-torch/ctorch/include/utils.hh | 60 +++--- kmath-torch/ctorch/src/ctorch.cc | 183 +++++++++--------- .../kmath/torch/TestTorchTensorAlgebraGPU.kt | 13 ++ .../kmath/torch/TestTorchTensorGPU.kt | 27 ++- .../kscience/kmath/torch/TestUtilsGPU.kt | 33 ++++ .../src/nativeInterop/cinterop/libctorch.def | 2 +- .../kscience.kmath.torch/TorchDevice.kt | 18 ++ .../kscience.kmath.torch/TorchTensor.kt | 93 +++++++++ .../TorchTensorAlgebra.kt | 66 +++++++ .../torch => kscience.kmath.torch}/Utils.kt | 4 +- .../kscience/kmath/torch/TorchMemoryHolder.kt | 18 -- .../kscience/kmath/torch/TorchTensor.kt | 119 ------------ .../kmath/torch/TorchTensorAlgebra.kt | 65 ------- .../kscience/kmath/torch/TorchTensorBuffer.kt | 98 ---------- .../kmath/torch/TorchTensorStrides.kt | 55 ------ .../kscience/kmath/torch/TestTorchTensor.kt | 51 ++--- .../kmath/torch/TestTorchTensorAlgebra.kt | 74 ++++--- .../kotlin/kscience/kmath/torch/TestUtils.kt | 28 ++- 22 files changed, 508 insertions(+), 676 deletions(-) create mode 100644 kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestTorchTensorAlgebraGPU.kt create mode 100644 kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestUtilsGPU.kt create mode 100644 kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchDevice.kt create mode 100644 kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchTensor.kt create mode 100644 kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchTensorAlgebra.kt rename kmath-torch/src/nativeMain/kotlin/{kscience/kmath/torch => kscience.kmath.torch}/Utils.kt (91%) delete mode 100644 kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchMemoryHolder.kt delete mode 100644 kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensor.kt delete mode 100644 kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorAlgebra.kt delete mode 100644 kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorBuffer.kt delete mode 100644 kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorStrides.kt diff --git a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt index fe580b031..7ce5a9cbe 100644 --- a/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt +++ b/kmath-core/src/commonMain/kotlin/kscience/kmath/structures/NDStructure.kt @@ -148,28 +148,23 @@ public interface Strides { /** * Array strides */ - public val strides: IntArray - - /** - * The size of linear buffer to accommodate all elements of ND-structure corresponding to strides - */ - public val linearSize: Int + public val strides: List /** * Get linear index from multidimensional index */ - public fun offset(index: IntArray): Int = index.mapIndexed { i, value -> - if (value < 0 || value >= this.shape[i]) - throw IndexOutOfBoundsException("Index $value out of shape bounds: (0,${this.shape[i]})") - - value * strides[i] - }.sum() + public fun offset(index: IntArray): Int /** * Get multidimensional from linear */ public fun index(offset: Int): IntArray + /** + * The size of linear buffer to accommodate all elements of ND-structure corresponding to strides + */ + public val linearSize: Int + // TODO introduce a fast way to calculate index of the next element? /** @@ -188,7 +183,7 @@ public class DefaultStrides private constructor(override val shape: IntArray) : /** * Strides for memory access */ - override val strides: IntArray by lazy { + override val strides: List by lazy { sequence { var current = 1 yield(1) @@ -197,9 +192,16 @@ public class DefaultStrides private constructor(override val shape: IntArray) : current *= it yield(current) } - }.toList().toIntArray() + }.toList() } + override fun offset(index: IntArray): Int = index.mapIndexed { i, value -> + if (value < 0 || value >= this.shape[i]) + throw IndexOutOfBoundsException("Index $value out of shape bounds: (0,${this.shape[i]})") + + value * strides[i] + }.sum() + override fun index(offset: Int): IntArray { val res = IntArray(shape.size) var current = offset @@ -239,19 +241,17 @@ public class DefaultStrides private constructor(override val shape: IntArray) : * Trait for [NDStructure] over [Buffer]. * * @param T the type of items - * @param BufferImpl implementation of [Buffer]. */ -public abstract class NDBufferTrait, out StridesImpl: Strides> : - NDStructure { +public abstract class NDBuffer : NDStructure { /** * The underlying buffer. */ - public abstract val buffer: BufferImpl + public abstract val buffer: Buffer /** * The strides to access elements of [Buffer] by linear indices. */ - public abstract val strides: StridesImpl + public abstract val strides: Strides override operator fun get(index: IntArray): T = buffer[strides.offset(index)] @@ -259,10 +259,6 @@ public abstract class NDBufferTrait, out StridesIm override fun elements(): Sequence> = strides.indices().map { it to this[it] } - public fun checkStridesBufferCompatibility(): Unit = require(strides.linearSize == buffer.size) { - "Expected buffer side of ${strides.linearSize}, but found ${buffer.size}" - } - override fun hashCode(): Int { var result = strides.hashCode() result = 31 * result + buffer.hashCode() @@ -286,45 +282,21 @@ public abstract class NDBufferTrait, out StridesIm } return "NDBuffer(shape=${shape.contentToString()}, buffer=$bufferRepr)" } + + } -/** - * Trait for [MutableNDStructure] over [MutableBuffer]. - * - * @param T the type of items - * @param MutableBufferImpl implementation of [MutableBuffer]. - */ -public abstract class MutableNDBufferTrait, out StridesImpl: Strides> : - NDBufferTrait(), MutableNDStructure { - override fun hashCode(): Int = 0 - override fun equals(other: Any?): Boolean = false - override operator fun set(index: IntArray, value: T): Unit = - buffer.set(strides.offset(index), value) -} - -/** - * Default representation of [NDStructure] over [Buffer]. - * - * @param T the type of items. - */ -public abstract class NDBuffer : NDBufferTrait, Strides>() - -/** - * Default representation of [MutableNDStructure] over [MutableBuffer]. - * - * @param T the type of items. - */ -public abstract class MutableNDBuffer : MutableNDBufferTrait, Strides>() - /** * Boxing generic [NDStructure] */ public class BufferNDStructure( override val strides: Strides, override val buffer: Buffer, -) : NDBuffer() { +) : NDBuffer() { init { - checkStridesBufferCompatibility() + if (strides.linearSize != buffer.size) { + error("Expected buffer side of ${strides.linearSize}, but found ${buffer.size}") + } } } @@ -344,15 +316,20 @@ public inline fun NDStructure.mapToBuffer( } /** - * Boxing generic [MutableNDStructure]. + * Mutable ND buffer based on linear [MutableBuffer]. */ public class MutableBufferNDStructure( override val strides: Strides, override val buffer: MutableBuffer, -) : MutableNDBuffer() { +) : NDBuffer(), MutableNDStructure { + init { - checkStridesBufferCompatibility() + require(strides.linearSize == buffer.size) { + "Expected buffer side of ${strides.linearSize}, but found ${buffer.size}" + } } + + override operator fun set(index: IntArray, value: T): Unit = buffer.set(strides.offset(index), value) } public inline fun NDStructure.combine( @@ -361,4 +338,4 @@ public inline fun NDStructure.combine( ): NDStructure { require(shape.contentEquals(struct.shape)) { "Shape mismatch in structure combination" } return NDStructure.auto(shape) { block(this[it], struct[it]) } -} +} \ No newline at end of file diff --git a/kmath-torch/README.md b/kmath-torch/README.md index 0cc12914d..c46870946 100644 --- a/kmath-torch/README.md +++ b/kmath-torch/README.md @@ -78,28 +78,20 @@ pluginManagement { Tensors implement the buffer protocol over `MutableNDStructure`. They can only be instantiated through provided factory methods and require scoping: ```kotlin -memScoped { - val intTensor: TorchTensorInt = TorchTensor.copyFromIntArray( - scope = this, - array = (1..24).toList().toIntArray(), - shape = intArrayOf(3, 2, 4) - ) - println(intTensor) +TorchTensorRealAlgebra { - val floatTensor: TorchTensorFloat = TorchTensor.copyFromFloatArray( - scope = this, - array = (1..10).map { it + 50f }.toList().toFloatArray(), - shape = intArrayOf(10) + val realTensor: TorchTensorReal = copyFromArray( + array = (1..10).map { it + 50.0 }.toList().toDoubleArray(), + shape = intArrayOf(2,5) ) - println(floatTensor) + println(realTensor) - val gpuFloatTensor: TorchTensorFloatGPU = TorchTensor.copyFromFloatArrayToGPU( - scope = this, - array = (1..8).map { it * 2f }.toList().toFloatArray(), + val gpuRealTensor: TorchTensorReal = copyFromArray( + array = (1..8).map { it * 2.5 }.toList().toDoubleArray(), shape = intArrayOf(2, 2, 2), - device = 0 + device = TorchDevice.TorchCUDA(0) ) - println(gpuFloatTensor) + println(gpuRealTensor) } ``` diff --git a/kmath-torch/build.gradle.kts b/kmath-torch/build.gradle.kts index 7ddbda497..8f75a4140 100644 --- a/kmath-torch/build.gradle.kts +++ b/kmath-torch/build.gradle.kts @@ -145,6 +145,7 @@ kotlin { } val nativeGPUTest by creating { dependsOn(nativeMain) + dependsOn(nativeTest) } diff --git a/kmath-torch/ctorch/include/ctorch.h b/kmath-torch/ctorch/include/ctorch.h index 793c2115e..553997d89 100644 --- a/kmath-torch/ctorch/include/ctorch.h +++ b/kmath-torch/ctorch/include/ctorch.h @@ -18,49 +18,46 @@ extern "C" void set_seed(int seed); - TorchTensorHandle copy_from_blob_double(double *data, int *shape, int dim); - TorchTensorHandle copy_from_blob_float(float *data, int *shape, int dim); - TorchTensorHandle copy_from_blob_long(long *data, int *shape, int dim); - TorchTensorHandle copy_from_blob_int(int *data, int *shape, int dim); - TorchTensorHandle copy_from_blob_to_gpu_double(double *data, int *shape, int dim, int device); - TorchTensorHandle copy_from_blob_to_gpu_float(float *data, int *shape, int dim, int device); - TorchTensorHandle copy_from_blob_to_gpu_long(long *data, int *shape, int dim, int device); - TorchTensorHandle copy_from_blob_to_gpu_int(int *data, int *shape, int dim, int device); - + TorchTensorHandle copy_from_blob_double(double *data, int *shape, int dim, int device); + TorchTensorHandle copy_from_blob_float(float *data, int *shape, int dim, int device); + TorchTensorHandle copy_from_blob_long(long *data, int *shape, int dim, int device); + TorchTensorHandle copy_from_blob_int(int *data, int *shape, int dim, int device); TorchTensorHandle copy_tensor(TorchTensorHandle tensor_handle); + TorchTensorHandle copy_to_device(TorchTensorHandle tensor_handle, int device); - double *get_data_double(TorchTensorHandle tensor_handle); - float *get_data_float(TorchTensorHandle tensor_handle); - long *get_data_long(TorchTensorHandle tensor_handle); - int *get_data_int(TorchTensorHandle tensor_handle); - - int get_numel(TorchTensorHandle tensor_handle); + double get_item_double(TorchTensorHandle tensor_handle); + float get_item_float(TorchTensorHandle tensor_handle); + long get_item_long(TorchTensorHandle tensor_handle); + int get_item_int(TorchTensorHandle tensor_handle); + int get_dim(TorchTensorHandle tensor_handle); - int *get_shape(TorchTensorHandle tensor_handle); - int *get_strides(TorchTensorHandle tensor_handle); + int get_numel(TorchTensorHandle tensor_handle); + int get_shape_at(TorchTensorHandle tensor_handle, int d); + int get_stride_at(TorchTensorHandle tensor_handle, int d); + int get_device(TorchTensorHandle tensor_handle); char *tensor_to_string(TorchTensorHandle tensor_handle); - - void dispose_int_array(int *ptr); void dispose_char(char *ptr); void dispose_tensor(TorchTensorHandle tensor_handle); - // Workaround for GPU tensors - double get_at_offset_double(TorchTensorHandle tensor_handle, int offset); - float get_at_offset_float(TorchTensorHandle tensor_handle, int offset); - long get_at_offset_long(TorchTensorHandle tensor_handle, int offset); - int get_at_offset_int(TorchTensorHandle tensor_handle, int offset); - void set_at_offset_double(TorchTensorHandle tensor_handle, int offset, double value); - void set_at_offset_float(TorchTensorHandle tensor_handle, int offset, float value); - void set_at_offset_long(TorchTensorHandle tensor_handle, int offset, long value); - void set_at_offset_int(TorchTensorHandle tensor_handle, int offset, int value); + + double get_double(TorchTensorHandle tensor_handle, int* index); + float get_float(TorchTensorHandle tensor_handle, int* index); + long get_long(TorchTensorHandle tensor_handle, int* index); + int get_int(TorchTensorHandle tensor_handle, int* index); + void set_double(TorchTensorHandle tensor_handle, int* index, double value); + void set_float(TorchTensorHandle tensor_handle, int* index, float value); + void set_long(TorchTensorHandle tensor_handle, int* index, long value); + void set_int(TorchTensorHandle tensor_handle, int* index, int value); - TorchTensorHandle copy_to_cpu(TorchTensorHandle tensor_handle); - TorchTensorHandle copy_to_gpu(TorchTensorHandle tensor_handle, int device); - TorchTensorHandle randn_float(int* shape, int shape_size); + TorchTensorHandle randn_double(int* shape, int shape_size, int device); + TorchTensorHandle rand_double(int* shape, int shape_size, int device); + TorchTensorHandle randn_float(int* shape, int shape_size, int device); + TorchTensorHandle rand_float(int* shape, int shape_size, int device); TorchTensorHandle matmul(TorchTensorHandle lhs, TorchTensorHandle rhs); + void matmul_assign(TorchTensorHandle lhs, TorchTensorHandle rhs); #ifdef __cplusplus } diff --git a/kmath-torch/ctorch/include/utils.hh b/kmath-torch/ctorch/include/utils.hh index 9950499a5..d375438d4 100644 --- a/kmath-torch/ctorch/include/utils.hh +++ b/kmath-torch/ctorch/include/utils.hh @@ -33,6 +33,16 @@ namespace ctorch return *static_cast(tensor_handle); } + inline int device_to_int(const torch::Tensor &tensor) + { + return (tensor.device().type() == torch::kCPU) ? 0 : 1 + tensor.device().index(); + } + + inline torch::Device int_to_device(int device_int) + { + return (device_int == 0) ? torch::kCPU : torch::Device(torch::kCUDA, device_int - 1); + } + inline std::vector to_vec_int(int *arr, int arr_size) { auto vec = std::vector(arr_size); @@ -40,46 +50,34 @@ namespace ctorch return vec; } + inline std::vector to_index(int *arr, int arr_size) + { + std::vector index; + for (int i = 0; i < arr_size; i++) + { + index.emplace_back(arr[i]); + } + return index; + } + template inline torch::Tensor copy_from_blob(Dtype *data, std::vector shape, torch::Device device) { return torch::from_blob(data, shape, dtype()).to(torch::TensorOptions().layout(torch::kStrided).device(device), false, true); } - inline int *to_dynamic_ints(const c10::IntArrayRef &arr) + template + inline NumType get(const TorchTensorHandle &tensor_handle, int *index) { - size_t n = arr.size(); - int *res = (int *)malloc(sizeof(int) * n); - for (size_t i = 0; i < n; i++) - { - res[i] = arr[i]; - } - return res; - } - - inline std::vector offset_to_index(int offset, const c10::IntArrayRef &strides) - { - std::vector index; - for (const auto &stride : strides) - { - index.emplace_back(offset / stride); - offset %= stride; - } - return index; + auto ten = ctorch::cast(tensor_handle); + return ten.index(to_index(index, ten.dim())).item(); } template - inline NumType get_at_offset(const TorchTensorHandle &tensor_handle, int offset) + inline void set(TorchTensorHandle &tensor_handle, int *index, NumType value) { auto ten = ctorch::cast(tensor_handle); - return ten.index(ctorch::offset_to_index(offset, ten.strides())).item(); - } - - template - inline void set_at_offset(TorchTensorHandle &tensor_handle, int offset, NumType value) - { - auto ten = ctorch::cast(tensor_handle); - ten.index(offset_to_index(offset, ten.strides())) = value; + ten.index(to_index(index, ten.dim())) = value; } template @@ -88,4 +86,10 @@ namespace ctorch return torch::randn(shape, torch::TensorOptions().dtype(dtype()).layout(torch::kStrided).device(device)); } + template + inline torch::Tensor rand(std::vector shape, torch::Device device) + { + return torch::rand(shape, torch::TensorOptions().dtype(dtype()).layout(torch::kStrided).device(device)); + } + } // namespace ctorch diff --git a/kmath-torch/ctorch/src/ctorch.cc b/kmath-torch/ctorch/src/ctorch.cc index 752784703..dec64c354 100644 --- a/kmath-torch/ctorch/src/ctorch.cc +++ b/kmath-torch/ctorch/src/ctorch.cc @@ -25,80 +25,50 @@ void set_seed(int seed) torch::manual_seed(seed); } -TorchTensorHandle copy_from_blob_double(double *data, int *shape, int dim) -{ - return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), torch::kCPU)); -} -TorchTensorHandle copy_from_blob_float(float *data, int *shape, int dim) -{ - return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), torch::kCPU)); -} -TorchTensorHandle copy_from_blob_long(long *data, int *shape, int dim) -{ - return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), torch::kCPU)); -} -TorchTensorHandle copy_from_blob_int(int *data, int *shape, int dim) -{ - return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), torch::kCPU)); -} - -TorchTensorHandle copy_from_blob_to_gpu_double(double *data, int *shape, int dim, int device) -{ - return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), torch::Device(torch::kCUDA, device))); -} -TorchTensorHandle copy_from_blob_to_gpu_float(float *data, int *shape, int dim, int device) -{ - return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), torch::Device(torch::kCUDA, device))); -} -TorchTensorHandle copy_from_blob_to_gpu_long(long *data, int *shape, int dim, int device) -{ - return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), torch::Device(torch::kCUDA, device))); -} -TorchTensorHandle copy_from_blob_to_gpu_int(int *data, int *shape, int dim, int device) -{ - return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), torch::Device(torch::kCUDA, device))); -} - -TorchTensorHandle copy_tensor(TorchTensorHandle tensor_handle) -{ - return new torch::Tensor(ctorch::cast(tensor_handle).clone()); -} - -double *get_data_double(TorchTensorHandle tensor_handle) -{ - return ctorch::cast(tensor_handle).data_ptr(); -} -float *get_data_float(TorchTensorHandle tensor_handle) -{ - return ctorch::cast(tensor_handle).data_ptr(); -} -long *get_data_long(TorchTensorHandle tensor_handle) -{ - return ctorch::cast(tensor_handle).data_ptr(); -} -int *get_data_int(TorchTensorHandle tensor_handle) -{ - return ctorch::cast(tensor_handle).data_ptr(); -} - -int get_numel(TorchTensorHandle tensor_handle) -{ - return ctorch::cast(tensor_handle).numel(); -} - int get_dim(TorchTensorHandle tensor_handle) { return ctorch::cast(tensor_handle).dim(); } - -int *get_shape(TorchTensorHandle tensor_handle) +int get_numel(TorchTensorHandle tensor_handle) { - return ctorch::to_dynamic_ints(ctorch::cast(tensor_handle).sizes()); + return ctorch::cast(tensor_handle).numel(); +} +int get_shape_at(TorchTensorHandle tensor_handle, int d) +{ + return ctorch::cast(tensor_handle).size(d); +} +int get_stride_at(TorchTensorHandle tensor_handle, int d) +{ + return ctorch::cast(tensor_handle).stride(d); +} +int get_device(TorchTensorHandle tensor_handle) +{ + return ctorch::device_to_int(ctorch::cast(tensor_handle)); } -int *get_strides(TorchTensorHandle tensor_handle) +TorchTensorHandle copy_from_blob_double(double *data, int *shape, int dim, int device) { - return ctorch::to_dynamic_ints(ctorch::cast(tensor_handle).strides()); + return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), ctorch::int_to_device(device))); +} +TorchTensorHandle copy_from_blob_float(float *data, int *shape, int dim, int device) +{ + return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), ctorch::int_to_device(device))); +} +TorchTensorHandle copy_from_blob_long(long *data, int *shape, int dim, int device) +{ + return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), ctorch::int_to_device(device))); +} +TorchTensorHandle copy_from_blob_int(int *data, int *shape, int dim, int device) +{ + return new torch::Tensor(ctorch::copy_from_blob(data, ctorch::to_vec_int(shape, dim), ctorch::int_to_device(device))); +} +TorchTensorHandle copy_tensor(TorchTensorHandle tensor_handle) +{ + return new torch::Tensor(ctorch::cast(tensor_handle).clone()); +} +TorchTensorHandle copy_to_device(TorchTensorHandle tensor_handle, int device) +{ + return new torch::Tensor(ctorch::cast(tensor_handle).to(ctorch::int_to_device(device), false, true)); } char *tensor_to_string(TorchTensorHandle tensor_handle) @@ -110,68 +80,89 @@ char *tensor_to_string(TorchTensorHandle tensor_handle) std::strcpy(crep, rep.c_str()); return crep; } - -void dispose_int_array(int *ptr) -{ - free(ptr); -} - void dispose_char(char *ptr) { free(ptr); } - void dispose_tensor(TorchTensorHandle tensor_handle) { delete static_cast(tensor_handle); } -double get_at_offset_double(TorchTensorHandle tensor_handle, int offset) +double get_double(TorchTensorHandle tensor_handle, int *index) { - return ctorch::get_at_offset(tensor_handle, offset); + return ctorch::get(tensor_handle, index); } -float get_at_offset_float(TorchTensorHandle tensor_handle, int offset) +float get_float(TorchTensorHandle tensor_handle, int *index) { - return ctorch::get_at_offset(tensor_handle, offset); + return ctorch::get(tensor_handle, index); } -long get_at_offset_long(TorchTensorHandle tensor_handle, int offset) +long get_long(TorchTensorHandle tensor_handle, int *index) { - return ctorch::get_at_offset(tensor_handle, offset); + return ctorch::get(tensor_handle, index); } -int get_at_offset_int(TorchTensorHandle tensor_handle, int offset) +int get_int(TorchTensorHandle tensor_handle, int *index) { - return ctorch::get_at_offset(tensor_handle, offset); + return ctorch::get(tensor_handle, index); } -void set_at_offset_double(TorchTensorHandle tensor_handle, int offset, double value) +void set_double(TorchTensorHandle tensor_handle, int *index, double value) { - ctorch::set_at_offset(tensor_handle, offset, value); + ctorch::set(tensor_handle, index, value); } -void set_at_offset_float(TorchTensorHandle tensor_handle, int offset, float value) +void set_float(TorchTensorHandle tensor_handle, int *index, float value) { - ctorch::set_at_offset(tensor_handle, offset, value); + ctorch::set(tensor_handle, index, value); } -void set_at_offset_long(TorchTensorHandle tensor_handle, int offset, long value) +void set_long(TorchTensorHandle tensor_handle, int *index, long value) { - ctorch::set_at_offset(tensor_handle, offset, value); + ctorch::set(tensor_handle, index, value); } -void set_at_offset_int(TorchTensorHandle tensor_handle, int offset, int value) +void set_int(TorchTensorHandle tensor_handle, int *index, int value) { - ctorch::set_at_offset(tensor_handle, offset, value); + ctorch::set(tensor_handle, index, value); } -TorchTensorHandle copy_to_cpu(TorchTensorHandle tensor_handle) +double get_item_double(TorchTensorHandle tensor_handle) { - return new torch::Tensor(ctorch::cast(tensor_handle).to(torch::kCPU,false, true)); + return ctorch::cast(tensor_handle).item(); } -TorchTensorHandle copy_to_gpu(TorchTensorHandle tensor_handle, int device) +float get_item_float(TorchTensorHandle tensor_handle) { - return new torch::Tensor(ctorch::cast(tensor_handle).to(torch::Device(torch::kCUDA, device),false, true)); + return ctorch::cast(tensor_handle).item(); +} +long get_item_long(TorchTensorHandle tensor_handle) +{ + return ctorch::cast(tensor_handle).item(); +} +int get_item_int(TorchTensorHandle tensor_handle) +{ + return ctorch::cast(tensor_handle).item(); } -TorchTensorHandle randn_float(int* shape, int shape_size){ - return new torch::Tensor(ctorch::randn(ctorch::to_vec_int(shape, shape_size), torch::kCPU)); +TorchTensorHandle randn_double(int *shape, int shape_size, int device) +{ + return new torch::Tensor(ctorch::randn(ctorch::to_vec_int(shape, shape_size), ctorch::int_to_device(device))); +} +TorchTensorHandle rand_double(int *shape, int shape_size, int device) +{ + return new torch::Tensor(ctorch::rand(ctorch::to_vec_int(shape, shape_size), ctorch::int_to_device(device))); +} +TorchTensorHandle randn_float(int *shape, int shape_size, int device) +{ + return new torch::Tensor(ctorch::randn(ctorch::to_vec_int(shape, shape_size), ctorch::int_to_device(device))); +} +TorchTensorHandle rand_float(int *shape, int shape_size, int device) +{ + return new torch::Tensor(ctorch::rand(ctorch::to_vec_int(shape, shape_size), ctorch::int_to_device(device))); } -TorchTensorHandle matmul(TorchTensorHandle lhs, TorchTensorHandle rhs){ +TorchTensorHandle matmul(TorchTensorHandle lhs, TorchTensorHandle rhs) +{ return new torch::Tensor(torch::matmul(ctorch::cast(lhs), ctorch::cast(rhs))); +} + +void matmul_assign(TorchTensorHandle lhs, TorchTensorHandle rhs) +{ + auto lhs_tensor = ctorch::cast(lhs); + lhs_tensor = lhs_tensor.matmul(ctorch::cast(rhs)); } \ No newline at end of file diff --git a/kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestTorchTensorAlgebraGPU.kt b/kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestTorchTensorAlgebraGPU.kt new file mode 100644 index 000000000..625529631 --- /dev/null +++ b/kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestTorchTensorAlgebraGPU.kt @@ -0,0 +1,13 @@ +package kscience.kmath.torch + +import kotlin.test.* + + +class TestTorchTensorAlgebraGPU { + + @Test + fun testScalarProduct() = testingScalarProduct(device = TorchDevice.TorchCUDA(0)) + + @Test + fun testMatrixMultiplication() = testingMatrixMultiplication(device = TorchDevice.TorchCUDA(0)) +} diff --git a/kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestTorchTensorGPU.kt b/kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestTorchTensorGPU.kt index 20a7b9439..d984993a1 100644 --- a/kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestTorchTensorGPU.kt +++ b/kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestTorchTensorGPU.kt @@ -1,25 +1,22 @@ package kscience.kmath.torch -import kscience.kmath.structures.asBuffer - -import kotlinx.cinterop.memScoped import kotlin.test.* class TestTorchTensorGPU { @Test - fun cudaAvailability() { - assertTrue(cudaAvailable()) - } + fun testCopyFromArray() = testingCopyFromArray(TorchDevice.TorchCUDA(0)) @Test - fun floatGPUTensorLayout() = memScoped { - val array = (1..8).map { it * 2f }.toList().toFloatArray() - val shape = intArrayOf(2, 2, 2) - val tensor = TorchTensor.copyFromFloatArrayToGPU(this, array, shape, 0) - tensor.elements().forEach { - assertEquals(tensor[it.first], it.second) - } - assertTrue(tensor.asBuffer().contentEquals(array.asBuffer())) + fun testCopyToDevice() = TorchTensorRealAlgebra { + setSeed(SEED) + val normalCpu = randNormal(intArrayOf(2, 3)) + val normalGpu = normalCpu.copyToDevice(TorchDevice.TorchCUDA(0)) + assertTrue(normalCpu.copyToArray() contentEquals normalGpu.copyToArray()) + + val uniformGpu = randUniform(intArrayOf(3,2),TorchDevice.TorchCUDA(0)) + val uniformCpu = uniformGpu.copyToDevice(TorchDevice.TorchCPU) + assertTrue(uniformGpu.copyToArray() contentEquals uniformCpu.copyToArray()) } -} \ No newline at end of file + +} diff --git a/kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestUtilsGPU.kt b/kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestUtilsGPU.kt new file mode 100644 index 000000000..f8ecb5554 --- /dev/null +++ b/kmath-torch/src/nativeGPUTest/kotlin/kscience/kmath/torch/TestUtilsGPU.kt @@ -0,0 +1,33 @@ +package kscience.kmath.torch + +import kotlin.test.* + + +internal class TestUtilsGPU { + + @Test + fun testCudaAvailable() { + assertTrue(cudaAvailable()) + } + + @Test + fun testSetSeed() = testingSetSeed(TorchDevice.TorchCUDA(0)) + + @Test + fun testReadmeFactory() = TorchTensorRealAlgebra { + + val realTensor: TorchTensorReal = copyFromArray( + array = (1..10).map { it + 50.0 }.toList().toDoubleArray(), + shape = intArrayOf(2,5) + ) + println(realTensor) + + val gpuRealTensor: TorchTensorReal = copyFromArray( + array = (1..8).map { it * 2.5 }.toList().toDoubleArray(), + shape = intArrayOf(2, 2, 2), + device = TorchDevice.TorchCUDA(0) + ) + println(gpuRealTensor) + } + +} diff --git a/kmath-torch/src/nativeInterop/cinterop/libctorch.def b/kmath-torch/src/nativeInterop/cinterop/libctorch.def index 638a361fc..2439a8c7b 100644 --- a/kmath-torch/src/nativeInterop/cinterop/libctorch.def +++ b/kmath-torch/src/nativeInterop/cinterop/libctorch.def @@ -1,2 +1,2 @@ -package=ctorch +package=kscience.kmath.ctorch headers=ctorch.h \ No newline at end of file diff --git a/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchDevice.kt b/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchDevice.kt new file mode 100644 index 000000000..cb9afe9ac --- /dev/null +++ b/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchDevice.kt @@ -0,0 +1,18 @@ +package kscience.kmath.torch + + +public sealed class TorchDevice { + public object TorchCPU: TorchDevice() + public data class TorchCUDA(val index: Int): TorchDevice() + public fun toInt(): Int { + when(this) { + is TorchCPU -> return 0 + is TorchCUDA -> return this.index + 1 + } + } + public companion object { + public fun fromInt(deviceInt: Int): TorchDevice { + return if (deviceInt == 0) TorchCPU else TorchCUDA(deviceInt-1) + } + } +} \ No newline at end of file diff --git a/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchTensor.kt b/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchTensor.kt new file mode 100644 index 000000000..f8e992a6f --- /dev/null +++ b/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchTensor.kt @@ -0,0 +1,93 @@ +package kscience.kmath.torch + +import kscience.kmath.structures.MutableNDStructure + +import kotlinx.cinterop.* +import kscience.kmath.ctorch.* + + +public sealed class TorchTensor constructor( + internal val scope: DeferScope, + internal val tensorHandle: COpaquePointer +) : MutableNDStructure { + init { + scope.defer(::close) + } + private fun close(): Unit = dispose_tensor(tensorHandle) + + protected abstract fun item(): T + internal abstract fun wrap(outScope: DeferScope, outTensorHandle: COpaquePointer): TorchTensor + + override val dimension: Int get() = get_dim(tensorHandle) + override val shape: IntArray + get() = (1..dimension).map{get_shape_at(tensorHandle, it-1)}.toIntArray() + public val strides: IntArray + get() = (1..dimension).map{get_stride_at(tensorHandle, it-1)}.toIntArray() + public val size: Int get() = get_numel(tensorHandle) + public val device: TorchDevice get() = TorchDevice.fromInt(get_device(tensorHandle)) + + override fun equals(other: Any?): Boolean = false + override fun hashCode(): Int = 0 + override fun toString(): String { + val nativeStringRepresentation: CPointer = tensor_to_string(tensorHandle)!! + val stringRepresentation = nativeStringRepresentation.toKString() + dispose_char(nativeStringRepresentation) + return stringRepresentation + } + + override fun elements(): Sequence> { + if (dimension == 0) { + return emptySequence() + } + val indices = (1..size).asSequence().map { indexFromOffset(it - 1, strides, dimension) } + return indices.map { it to get(it) } + } + + public fun value(): T { + check(dimension == 0) { + "This tensor has shape ${shape.toList()}" + } + return item() + } + + public fun copy(): TorchTensor = + wrap( + outScope = scope, + outTensorHandle = copy_tensor(tensorHandle)!! + ) + + public fun copyToDevice(device: TorchDevice): TorchTensor = + wrap( + outScope = scope, + outTensorHandle = copy_to_device(tensorHandle, device.toInt())!! + ) + +} + +public class TorchTensorReal internal constructor( + scope: DeferScope, + tensorHandle: COpaquePointer +) : TorchTensor(scope, tensorHandle) { + override fun item(): Double = get_item_double(tensorHandle) + override fun wrap(outScope: DeferScope, outTensorHandle: COpaquePointer + ): TorchTensorReal = TorchTensorReal(scope = outScope, tensorHandle = outTensorHandle) + + override fun get(index: IntArray): Double = get_double(tensorHandle, index.toCValues()) + override fun set(index: IntArray, value: Double) { + set_double(tensorHandle, index.toCValues(), value) + } +} + + +private inline fun indexFromOffset(offset: Int, strides: IntArray, nDim: Int): IntArray { + val res = IntArray(nDim) + var current = offset + var strideIndex = 0 + + while (strideIndex < nDim) { + res[strideIndex] = (current / strides[strideIndex]) + current %= strides[strideIndex] + strideIndex++ + } + return res +} diff --git a/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchTensorAlgebra.kt b/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchTensorAlgebra.kt new file mode 100644 index 000000000..3324912bd --- /dev/null +++ b/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/TorchTensorAlgebra.kt @@ -0,0 +1,66 @@ +package kscience.kmath.torch + +import kotlinx.cinterop.* +import kscience.kmath.ctorch.* + +public sealed class TorchTensorAlgebra constructor( + internal val scope: DeferScope +) { + internal abstract fun wrap(tensorHandle: COpaquePointer): TorchTensor + public abstract fun copyFromArray( + array: PrimitiveArrayType, + shape: IntArray, + device: TorchDevice = TorchDevice.TorchCPU + ): TorchTensor + + public abstract fun TorchTensor.copyToArray(): PrimitiveArrayType + + public infix fun TorchTensor.dot(other: TorchTensor): TorchTensor = + wrap(matmul(this.tensorHandle, other.tensorHandle)!!) + + public infix fun TorchTensor.dotAssign(other: TorchTensor): Unit { + matmul_assign(this.tensorHandle, other.tensorHandle) + } +} + +public sealed class TorchTensorFieldAlgebra(scope: DeferScope) : + TorchTensorAlgebra(scope) { + public abstract fun randNormal(shape: IntArray, device: TorchDevice = TorchDevice.TorchCPU): TorchTensor + public abstract fun randUniform(shape: IntArray, device: TorchDevice = TorchDevice.TorchCPU): TorchTensor +} + +public class TorchTensorRealAlgebra(scope: DeferScope) : TorchTensorFieldAlgebra(scope) { + override fun wrap(tensorHandle: COpaquePointer): TorchTensorReal = + TorchTensorReal(scope = scope, tensorHandle = tensorHandle) + + override fun TorchTensor.copyToArray(): DoubleArray = + this.elements().map { it.second }.toList().toDoubleArray() + + override fun copyFromArray( + array: DoubleArray, + shape: IntArray, + device: TorchDevice + ): TorchTensorReal = + TorchTensorReal( + scope = scope, + tensorHandle = copy_from_blob_double( + array.toCValues(), + shape.toCValues(), + shape.size, + device.toInt() + )!! + ) + + override fun randNormal(shape: IntArray, device: TorchDevice): TorchTensorReal = TorchTensorReal( + scope = scope, + tensorHandle = randn_double(shape.toCValues(), shape.size, device.toInt())!! + ) + + override fun randUniform(shape: IntArray, device: TorchDevice): TorchTensorReal = TorchTensorReal( + scope = scope, + tensorHandle = rand_double(shape.toCValues(), shape.size, device.toInt())!! + ) +} + +public fun TorchTensorRealAlgebra(block: TorchTensorRealAlgebra.() -> R): R = + memScoped { TorchTensorRealAlgebra(this).block() } \ No newline at end of file diff --git a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/Utils.kt b/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/Utils.kt similarity index 91% rename from kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/Utils.kt rename to kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/Utils.kt index 086231687..bc58beca6 100644 --- a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/Utils.kt +++ b/kmath-torch/src/nativeMain/kotlin/kscience.kmath.torch/Utils.kt @@ -1,7 +1,7 @@ package kscience.kmath.torch import kotlinx.cinterop.* -import ctorch.* +import kscience.kmath.ctorch.* public fun getNumThreads(): Int { return get_num_threads() @@ -17,4 +17,4 @@ public fun cudaAvailable(): Boolean { public fun setSeed(seed: Int): Unit { set_seed(seed) -} \ No newline at end of file +} diff --git a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchMemoryHolder.kt b/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchMemoryHolder.kt deleted file mode 100644 index 140315f5e..000000000 --- a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchMemoryHolder.kt +++ /dev/null @@ -1,18 +0,0 @@ -package kscience.kmath.torch - -import kotlinx.cinterop.* -import ctorch.* - -public abstract class TorchMemoryHolder internal constructor( - internal val scope: DeferScope, - internal var tensorHandle: COpaquePointer? -){ - init { - scope.defer(::close) - } - - protected fun close() { - dispose_tensor(tensorHandle) - tensorHandle = null - } -} \ No newline at end of file diff --git a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensor.kt b/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensor.kt deleted file mode 100644 index fc94e62c4..000000000 --- a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensor.kt +++ /dev/null @@ -1,119 +0,0 @@ -package kscience.kmath.torch - -import kscience.kmath.structures.* - -import kotlinx.cinterop.* -import ctorch.* - -public sealed class TorchTensor> : - MutableNDBufferTrait() { - - public fun asBuffer(): MutableBuffer = buffer - - public companion object { - public fun copyFromFloatArray(scope: DeferScope, array: FloatArray, shape: IntArray): TorchTensorFloat { - val tensorHandle: COpaquePointer = copy_from_blob_float( - array.toCValues(), shape.toCValues(), shape.size - )!! - return TorchTensorFloat( - scope = scope, - tensorHandle = tensorHandle, - strides = populateStridesFromNative(tensorHandle, rawShape = shape) - ) - } - - public fun copyFromIntArray(scope: DeferScope, array: IntArray, shape: IntArray): TorchTensorInt { - val tensorHandle: COpaquePointer = copy_from_blob_int( - array.toCValues(), shape.toCValues(), shape.size - )!! - return TorchTensorInt( - scope = scope, - tensorHandle = tensorHandle, - strides = populateStridesFromNative(tensorHandle, rawShape = shape) - ) - } - - public fun copyFromFloatArrayToGPU( - scope: DeferScope, - array: FloatArray, - shape: IntArray, - device: Int - ): TorchTensorFloatGPU { - val tensorHandle: COpaquePointer = copy_from_blob_to_gpu_float( - array.toCValues(), shape.toCValues(), shape.size, device - )!! - return TorchTensorFloatGPU( - scope = scope, - tensorHandle = tensorHandle, - strides = populateStridesFromNative(tensorHandle, rawShape = shape) - ) - } - } - - override fun toString(): String { - val nativeStringRepresentation: CPointer = tensor_to_string(buffer.tensorHandle!!)!! - val stringRepresentation = nativeStringRepresentation.toKString() - dispose_char(nativeStringRepresentation) - return stringRepresentation - } - - protected abstract fun wrap( - outScope: DeferScope, - outTensorHandle: COpaquePointer, - outStrides: TorchTensorStrides - ): TorchTensor - - public fun copy(): TorchTensor = wrap( - outScope = buffer.scope, - outTensorHandle = copy_tensor(buffer.tensorHandle!!)!!, - outStrides = strides - ) - -} - -public class TorchTensorFloat internal constructor( - scope: DeferScope, - tensorHandle: COpaquePointer, - override val strides: TorchTensorStrides -) : TorchTensor() { - override val buffer: TorchTensorBufferFloat = TorchTensorBufferFloat(scope, tensorHandle) - override fun wrap( - outScope: DeferScope, - outTensorHandle: COpaquePointer, - outStrides: TorchTensorStrides - ): TorchTensorFloat = TorchTensorFloat( - scope = outScope, tensorHandle = outTensorHandle, strides = outStrides - ) -} - -public class TorchTensorInt internal constructor( - scope: DeferScope, - tensorHandle: COpaquePointer, - override val strides: TorchTensorStrides -) : TorchTensor() { - override val buffer: TorchTensorBufferInt = TorchTensorBufferInt(scope, tensorHandle) - override fun wrap( - outScope: DeferScope, - outTensorHandle: COpaquePointer, - outStrides: TorchTensorStrides - ): TorchTensorInt = TorchTensorInt( - scope = outScope, tensorHandle = outTensorHandle, strides = outStrides - ) -} - -public class TorchTensorFloatGPU internal constructor( - scope: DeferScope, - tensorHandle: COpaquePointer, - override val strides: TorchTensorStrides -) : TorchTensor() { - override val buffer: TorchTensorBufferFloatGPU = TorchTensorBufferFloatGPU(scope, tensorHandle) - override fun wrap( - outScope: DeferScope, - outTensorHandle: COpaquePointer, - outStrides: TorchTensorStrides - ): TorchTensorFloatGPU = - TorchTensorFloatGPU( - scope = outScope, tensorHandle = outTensorHandle, strides = outStrides - ) -} - diff --git a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorAlgebra.kt b/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorAlgebra.kt deleted file mode 100644 index 27d18c275..000000000 --- a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorAlgebra.kt +++ /dev/null @@ -1,65 +0,0 @@ -package kscience.kmath.torch - -import kotlinx.cinterop.* -import ctorch.* - - -public sealed class TorchTensorAlgebra< - T, - TorchTensorBufferImpl : TorchTensorBuffer, - PrimitiveArrayType> -constructor( - internal val scope: DeferScope -) { - - protected abstract fun wrap( - outTensorHandle: COpaquePointer, - outStrides: TorchTensorStrides - ): TorchTensor - - public infix fun TorchTensor.swap(other: TorchTensor): Unit { - check(this.shape contentEquals other.shape) { - "Attempt to swap tensors with different shapes" - } - this.buffer.tensorHandle = other.buffer.tensorHandle.also { - other.buffer.tensorHandle = this.buffer.tensorHandle - } - } - - public abstract fun copyFromArray(array: PrimitiveArrayType, shape: IntArray): TorchTensor - - public infix fun TorchTensor.dot(other: TorchTensor): - TorchTensor { - val resultHandle = matmul(this.buffer.tensorHandle, other.buffer.tensorHandle)!! - val strides = populateStridesFromNative(tensorHandle = resultHandle) - return wrap(resultHandle, strides) - } -} - - -public sealed class TorchTensorField, PrimitiveArrayType> -constructor(scope: DeferScope) : TorchTensorAlgebra(scope) { - public abstract fun randn(shape: IntArray): TorchTensor -} - - -public class TorchTensorFloatAlgebra(scope: DeferScope) : - TorchTensorField(scope) { - override fun wrap( - outTensorHandle: COpaquePointer, - outStrides: TorchTensorStrides - ): TorchTensorFloat = TorchTensorFloat(scope = scope, tensorHandle = outTensorHandle, strides = outStrides) - - override fun randn(shape: IntArray): TorchTensor { - val tensorHandle = randn_float(shape.toCValues(), shape.size)!! - val strides = populateStridesFromNative(tensorHandle = tensorHandle, rawShape = shape) - return wrap(tensorHandle, strides) - } - - override fun copyFromArray(array: FloatArray, shape: IntArray): TorchTensorFloat = - TorchTensor.copyFromFloatArray(scope, array, shape) -} - - -public fun TorchTensorFloatAlgebra(block: TorchTensorFloatAlgebra.() -> R): R = - memScoped { TorchTensorFloatAlgebra(this).block() } \ No newline at end of file diff --git a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorBuffer.kt b/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorBuffer.kt deleted file mode 100644 index 44d6b8dd6..000000000 --- a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorBuffer.kt +++ /dev/null @@ -1,98 +0,0 @@ -package kscience.kmath.torch - -import kscience.kmath.structures.MutableBuffer - -import kotlinx.cinterop.* -import ctorch.* - -public sealed class TorchTensorBuffer constructor( - scope: DeferScope, - tensorHandle: COpaquePointer? -) : MutableBuffer, TorchMemoryHolder(scope, tensorHandle) { - - override val size: Int - get(){ - return get_numel(tensorHandle!!) - } - - internal abstract fun wrap(outScope: DeferScope, outTensorHandle: COpaquePointer): TorchTensorBuffer - - override fun copy(): TorchTensorBuffer = wrap( - outScope = scope, - outTensorHandle = copy_tensor(tensorHandle!!)!! - ) -} - -public class TorchTensorBufferFloat internal constructor( - scope: DeferScope, - tensorHandle: COpaquePointer -) : TorchTensorBuffer(scope, tensorHandle) { - - private val tensorData: CPointer - get(){ - return get_data_float(tensorHandle!!)!! - } - - override operator fun get(index: Int): Float = tensorData[index] - - override operator fun set(index: Int, value: Float) { - tensorData[index] = value - } - - override operator fun iterator(): Iterator = (1..size).map { tensorData[it - 1] }.iterator() - - override fun wrap(outScope: DeferScope, outTensorHandle: COpaquePointer) = TorchTensorBufferFloat( - scope = outScope, - tensorHandle = outTensorHandle - ) -} - - -public class TorchTensorBufferInt internal constructor( - scope: DeferScope, - tensorHandle: COpaquePointer -) : TorchTensorBuffer(scope, tensorHandle) { - - private val tensorData: CPointer - get(){ - return get_data_int(tensorHandle!!)!! - } - - override operator fun get(index: Int): Int = tensorData[index] - - override operator fun set(index: Int, value: Int) { - tensorData[index] = value - } - - override operator fun iterator(): Iterator = (1..size).map { tensorData[it - 1] }.iterator() - - override fun wrap(outScope: DeferScope, outTensorHandle: COpaquePointer) = TorchTensorBufferInt( - scope = outScope, - tensorHandle = outTensorHandle - ) -} - -public class TorchTensorBufferFloatGPU internal constructor( - scope: DeferScope, - tensorHandle: COpaquePointer -) : TorchTensorBuffer(scope, tensorHandle) { - - override operator fun get(index: Int): Float = get_at_offset_float(tensorHandle!!, index) - - override operator fun set(index: Int, value: Float) { - set_at_offset_float(tensorHandle!!, index, value) - } - - override operator fun iterator(): Iterator { - val cpuCopy = copy_to_cpu(tensorHandle!!)!! - val tensorCpuData = get_data_float(cpuCopy)!! - val iteratorResult = (1..size).map { tensorCpuData[it - 1] }.iterator() - dispose_tensor(cpuCopy) - return iteratorResult - } - - override fun wrap(outScope: DeferScope, outTensorHandle: COpaquePointer) = TorchTensorBufferFloatGPU( - scope = outScope, - tensorHandle = outTensorHandle - ) -} diff --git a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorStrides.kt b/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorStrides.kt deleted file mode 100644 index f9602073b..000000000 --- a/kmath-torch/src/nativeMain/kotlin/kscience/kmath/torch/TorchTensorStrides.kt +++ /dev/null @@ -1,55 +0,0 @@ -package kscience.kmath.torch - -import kscience.kmath.structures.Strides - -import kotlinx.cinterop.* -import ctorch.* - -public class TorchTensorStrides internal constructor( - override val shape: IntArray, - override val strides: IntArray, - override val linearSize: Int -) : Strides { - override fun index(offset: Int): IntArray { - val nDim = shape.size - val res = IntArray(nDim) - var current = offset - var strideIndex = 0 - - while (strideIndex < nDim) { - res[strideIndex] = (current / strides[strideIndex]) - current %= strides[strideIndex] - strideIndex++ - } - return res - } -} - - -private inline fun intPointerToArrayAndClean(ptr: CPointer, nDim: Int): IntArray { - val res: IntArray = (1 .. nDim).map{ptr[it-1]}.toIntArray() - dispose_int_array(ptr) - return res -} - -private inline fun getShapeFromNative(tensorHandle: COpaquePointer, nDim: Int): IntArray{ - return intPointerToArrayAndClean(get_shape(tensorHandle)!!, nDim) -} - -private inline fun getStridesFromNative(tensorHandle: COpaquePointer, nDim: Int): IntArray{ - return intPointerToArrayAndClean(get_strides(tensorHandle)!!, nDim) -} - -internal inline fun populateStridesFromNative( - tensorHandle: COpaquePointer, - rawShape: IntArray? = null, - rawStrides: IntArray? = null, - rawLinearSize: Int? = null -): TorchTensorStrides { - val nDim = rawShape?.size?: rawStrides?.size?: get_dim(tensorHandle) - return TorchTensorStrides( - shape = rawShape?: getShapeFromNative(tensorHandle, nDim), - strides = rawStrides?: getStridesFromNative(tensorHandle, nDim), - linearSize = rawLinearSize?: get_numel(tensorHandle) - ) -} \ No newline at end of file diff --git a/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestTorchTensor.kt b/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestTorchTensor.kt index 7636ea99a..c8185e6b8 100644 --- a/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestTorchTensor.kt +++ b/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestTorchTensor.kt @@ -1,45 +1,22 @@ package kscience.kmath.torch -import kscience.kmath.structures.asBuffer - -import kotlinx.cinterop.memScoped import kotlin.test.* +internal fun testingCopyFromArray(device: TorchDevice = TorchDevice.TorchCPU): Unit { + TorchTensorRealAlgebra { + val array = (1..24).map { 10.0 * it * it }.toDoubleArray() + val shape = intArrayOf(2, 3, 4) + val tensor = copyFromArray(array, shape = shape, device = device) + val copyOfTensor = tensor.copy() + tensor[intArrayOf(0, 0)] = 0.1 + assertTrue(copyOfTensor.copyToArray() contentEquals array) + assertEquals(0.1, tensor[intArrayOf(0, 0)]) + } +} -internal class TestTorchTensor { + +class TestTorchTensor { @Test - fun intTensorLayout() = memScoped { - val array = (1..24).toList().toIntArray() - val shape = intArrayOf(4, 6) - val tensor = TorchTensor.copyFromIntArray(scope = this, array = array, shape = shape) - tensor.elements().forEach { - assertEquals(tensor[it.first], it.second) - } - assertTrue(tensor.asBuffer().contentEquals(array.asBuffer())) - } - - @Test - fun floatTensorLayout() = memScoped { - val array = (1..10).map { it + 50f }.toList().toFloatArray() - val shape = intArrayOf(10) - val tensor = TorchTensor.copyFromFloatArray(this, array, shape) - tensor.elements().forEach { - assertEquals(tensor[it.first], it.second) - } - assertTrue(tensor.asBuffer().contentEquals(array.asBuffer())) - } - - @Test - fun mutableStructure() = memScoped { - val array = (1..10).map { 1f * it }.toList().toFloatArray() - val shape = intArrayOf(10) - val tensor = TorchTensor.copyFromFloatArray(this, array, shape) - val tensorCopy = tensor.copy() - - tensor[intArrayOf(0)] = 99f - assertEquals(99f, tensor[intArrayOf(0)]) - assertEquals(1f, tensorCopy[intArrayOf(0)]) - } - + fun testCopyFromArray() = testingCopyFromArray() } \ No newline at end of file diff --git a/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestTorchTensorAlgebra.kt b/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestTorchTensorAlgebra.kt index 346739d63..b49c6f53e 100644 --- a/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestTorchTensorAlgebra.kt +++ b/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestTorchTensorAlgebra.kt @@ -1,36 +1,52 @@ package kscience.kmath.torch - +import kscience.kmath.linear.RealMatrixContext +import kscience.kmath.operations.invoke +import kscience.kmath.structures.Matrix +import kotlin.math.abs import kotlin.test.* -import kotlin.time.measureTime - -class TestTorchTensorAlgebra { - - @Test - fun swappingTensors() = TorchTensorFloatAlgebra { - val tensorA = copyFromArray(floatArrayOf(1f, 2f, 3f), intArrayOf(3)) - val tensorB = tensorA.copy() - val tensorC = copyFromArray(floatArrayOf(4f, 5f, 6f), intArrayOf(3)) - tensorA swap tensorC - assertTrue(tensorB.asBuffer().contentEquals(tensorC.asBuffer())) +internal fun testingScalarProduct(device: TorchDevice = TorchDevice.TorchCPU): Unit { + TorchTensorRealAlgebra { + val lhs = randUniform(shape = intArrayOf(10), device = device) + val rhs = randUniform(shape = intArrayOf(10), device = device) + val product = lhs dot rhs + var expected = 0.0 + lhs.elements().forEach { + expected += it.second * rhs[it.first] + } + assertTrue(abs(expected - product.value()) < TOLERANCE) } - - @Test - fun dotOperation() = TorchTensorFloatAlgebra { - setSeed(987654) - var tensorA = randn(intArrayOf(1000, 1000)) - val tensorB = randn(intArrayOf(1000, 1000)) - measureTime { - repeat(100) { - TorchTensorFloatAlgebra { - tensorA swap (tensorA dot tensorB) - } - } - }.also(::println) - assertTrue(tensorA.shape contentEquals tensorB.shape) - } - - } +internal fun testingMatrixMultiplication(device: TorchDevice = TorchDevice.TorchCPU): Unit { + TorchTensorRealAlgebra { + setSeed(SEED) + + val lhsTensor = randNormal(shape = intArrayOf(20, 20), device = device) + val rhsTensor = randNormal(shape = intArrayOf(20, 20), device = device) + val product = lhsTensor dot rhsTensor + + val expected: Matrix = RealMatrixContext { + val lhs = produce(20, 20) { i, j -> lhsTensor[intArrayOf(i, j)] } + val rhs = produce(20, 20) { i, j -> rhsTensor[intArrayOf(i, j)] } + lhs dot rhs + } + + var error: Double = 0.0 + product.elements().forEach { + error += abs(expected[it.first] - it.second) + } + assertTrue(error < TOLERANCE) + } +} + +internal class TestTorchTensorAlgebra { + + @Test + fun testScalarProduct() = testingScalarProduct() + + @Test + fun testMatrixMultiplication() = testingMatrixMultiplication() + +} \ No newline at end of file diff --git a/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestUtils.kt b/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestUtils.kt index e15c8dcec..843037933 100644 --- a/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestUtils.kt +++ b/kmath-torch/src/nativeTest/kotlin/kscience/kmath/torch/TestUtils.kt @@ -3,19 +3,31 @@ package kscience.kmath.torch import kotlin.test.* +internal val SEED = 987654 +internal val TOLERANCE = 1e-6 + +internal fun testingSetSeed(device: TorchDevice = TorchDevice.TorchCPU): Unit { + TorchTensorRealAlgebra { + setSeed(SEED) + val normal = randNormal(IntArray(0), device = device).value() + val uniform = randUniform(IntArray(0), device = device).value() + setSeed(SEED) + val nextNormal = randNormal(IntArray(0), device = device).value() + val nextUniform = randUniform(IntArray(0), device = device).value() + assertEquals(normal, nextNormal) + assertEquals(uniform, nextUniform) + } +} + + internal class TestUtils { @Test - fun settingTorchThreadsCount() { + fun testSetNumThreads() { val numThreads = 2 setNumThreads(numThreads) assertEquals(numThreads, getNumThreads()) } + @Test - fun seedSetting() = TorchTensorFloatAlgebra { - setSeed(987654) - val tensorA = randn(intArrayOf(2,3)) - setSeed(987654) - val tensorB = randn(intArrayOf(2,3)) - assertTrue(tensorA.asBuffer().contentEquals(tensorB.asBuffer())) - } + fun testSetSeed() = testingSetSeed() } \ No newline at end of file