From 3c8da6a24545f2125c280f2573aeacbe700bc806 Mon Sep 17 00:00:00 2001 From: lucasdelimanogueira Date: Thu, 16 May 2024 16:35:55 -0300 Subject: [PATCH] sum axis cpu and broadcast forward --- build/cpu.o | Bin 0 -> 10096 bytes build/cuda.cu.o | Bin 0 -> 100448 bytes build/tensor.o | Bin 0 -> 40768 bytes norch/__pycache__/tensor.cpython-38.pyc | Bin 13822 -> 14781 bytes .../__pycache__/functions.cpython-38.pyc | Bin 6122 -> 6274 bytes norch/autograd/functions.py | 6 ++ norch/csrc/cpu.cpp | 88 ++++++++++++++++-- norch/csrc/cpu.h | 3 +- norch/csrc/tensor.cpp | 66 +++++++++++-- norch/csrc/tensor.h | 2 +- norch/libtensor.so | Bin 142776 -> 142952 bytes norch/tensor.py | 87 +++++++++++++---- tests/test_autograd.py | 20 ++++ tests/test_operations.py | 31 ++++++ 14 files changed, 266 insertions(+), 37 deletions(-) create mode 100644 build/cpu.o create mode 100644 build/cuda.cu.o create mode 100644 build/tensor.o diff --git a/build/cpu.o b/build/cpu.o new file mode 100644 index 0000000000000000000000000000000000000000..7792721b854377d160348303da3404e4dfab3305 GIT binary patch literal 10096 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 literal 0 HcmV?d00001 diff --git a/build/tensor.o b/build/tensor.o new file mode 100644 index 0000000000000000000000000000000000000000..f5f937d2733b7287bdda826638b5b1d23b3a67c6 GIT binary patch literal 40768 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 E7uSiqjsO4v literal 0 HcmV?d00001 diff --git a/norch/__pycache__/tensor.cpython-38.pyc b/norch/__pycache__/tensor.cpython-38.pyc index 01f4c49fbddcfbcda8a103a3cc9dea716b6376d9..d2c4826f08c24079dbe2962ca745e3bdc22edbf9 100644 GIT binary patch delta 2303 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 delta 848 zcmY*X&rcIk5bkul+ijO_X(#L_U2QV)VmsAWV<-ahdB0j(_!{h%zy>OX0yNjT$eYm3EAm zcAH!=IJm$MegbbR#f_A37}z71>f4~hq9H1n6Of_}bCu=4gAR01$u}h8%-r9=jw|+8 z-#ldLr2jp%&_Dm6v6nfxj|x@~9HB+)v}k9bw?WvhgjUle8#33l3TJ4iDK9#iGu*)I zRJJ+H8ahew1EfctUU}D=&79(T@0OTkaj6)GFYxj$7m%u25I^d%)&V5 z32C&*u;y}uE~9kMo2y2aaqLt3PEcoP%1SCghOBaan%;zpLUwkQzq*M!_No%)aG|#K zEUkxU;TGxQnCNF_Wfxn+ei{{h3t53!-z?hh#HTnQ`B+^92QOIy0RfP?C0AJH=>Kbd3nK7aKV zG?Yj>hzI$HZW-i6q8u`mLxXY_P~I!bOG9ZtN-t5mfwD=I=SO)U%+aRhWVX!X^VON< ex$7r@!KDNT(`|eCHEo~WY=><>XI{=tnf(V;vZvhu diff --git a/norch/autograd/functions.py b/norch/autograd/functions.py index d5a2684..b114534 100644 --- a/norch/autograd/functions.py +++ b/norch/autograd/functions.py @@ -7,6 +7,12 @@ class AddBackward: def backward(self, gradient): return [gradient, gradient] +class AddBroadcastedBackward: + pass + + + + class SubBackward: def __init__(self, x, y): self.input = [x, y] diff --git a/norch/csrc/cpu.cpp b/norch/csrc/cpu.cpp index ee46dca..5a0cb29 100644 --- a/norch/csrc/cpu.cpp +++ b/norch/csrc/cpu.cpp @@ -11,6 +11,46 @@ void add_tensor_cpu(Tensor* tensor1, Tensor* tensor2, float* result_data) { } } +void add_broadcasted_tensor_cpu(Tensor* tensor1, Tensor* tensor2, float* result_data, int* broadcasted_shape) { + int max_ndim = tensor1->ndim > tensor2->ndim ? tensor1->ndim : tensor2->ndim; + + // Calculate strides for broadcasting + int* strides1 = (int*)malloc(max_ndim * sizeof(int)); + int* strides2 = (int*)malloc(max_ndim * sizeof(int)); + if (strides1 == NULL || strides2 == NULL) { + fprintf(stderr, "Memory allocation failed\n"); + exit(1); + } + + int stride1 = 1, stride2 = 1; + for (int i = max_ndim - 1; i >= 0; i--) { + int dim1 = i < tensor1->ndim ? tensor1->shape[tensor1->ndim - max_ndim + i] : 1; + int dim2 = i < tensor2->ndim ? tensor2->shape[tensor2->ndim - max_ndim + i] : 1; + strides1[i] = dim1 == broadcasted_shape[i] ? stride1 : 0; + strides2[i] = dim2 == broadcasted_shape[i] ? stride2 : 0; + stride1 *= broadcasted_shape[i]; + stride2 *= broadcasted_shape[i]; + } + + // Perform element-wise addition with broadcasting + for (int i = 0; i < tensor1->size; i++) { + int index1 = 0, index2 = 0; + int linear_index = i; + for (int j = max_ndim - 1; j >= 0; j--) { + int pos = linear_index % broadcasted_shape[j]; + linear_index /= broadcasted_shape[j]; + if (strides1[j] != 0) index1 += pos * strides1[j]; + if (strides2[j] != 0) index2 += pos * strides2[j]; + } + result_data[i] = tensor1->data[index1] + tensor2->data[index2]; + } + + // Free strides + free(strides1); + free(strides2); +} + + void sub_tensor_cpu(Tensor* tensor1, Tensor* tensor2, float* result_data) { for (int i = 0; i < tensor1->size; i++) { @@ -126,16 +166,52 @@ void log_tensor_cpu(Tensor* tensor, float* result_data) { } } -void sum_tensor_cpu(Tensor* tensor, float* result_data) { - float sum = 0.0; +void sum_tensor_cpu(Tensor* tensor, float* result_data, int axis) { + if (axis == -1) { + // Sum over all elements + float sum = 0.0; + for (int i = 0; i < tensor->size; i++) { + sum += tensor->data[i]; + } + *result_data = sum; + } else { + if (axis < 0 || axis >= tensor->ndim) { + printf("Invalid axis"); + return; + } + + int result_shape[tensor->ndim - 1]; + int result_size = 1; + int axis_stride = tensor->strides[axis]; - for (int i = 0; i < tensor->size; i++) { - sum += tensor->data[i]; + int idx = 0; + for (int i = 0; i < tensor->ndim; i++) { + if (i != axis) { + result_shape[idx++] = tensor->shape[i]; + result_size *= tensor->shape[i]; + } + } + + memset(result_data, 0, result_size * sizeof(float)); + + for (int i = 0; i < tensor->shape[axis]; i++) { + for (int j = 0; j < result_size; j++) { + int index = 0; + int remainder = j; + for (int k = tensor->ndim - 2; k >= 0; k--) { + index += (remainder % result_shape[k]) * tensor->strides[k < axis ? k : k + 1]; + remainder /= result_shape[k]; + } + result_data[j] += tensor->data[index + i * axis_stride]; + } + } } - - *result_data = sum; } + + + + void ones_like_tensor_cpu(Tensor* tensor, float* result_data) { for (int i = 0; i < tensor->size; i++) { diff --git a/norch/csrc/cpu.h b/norch/csrc/cpu.h index 9e5dc62..5e64293 100644 --- a/norch/csrc/cpu.h +++ b/norch/csrc/cpu.h @@ -4,7 +4,8 @@ #include "tensor.h" void add_tensor_cpu(Tensor* tensor1, Tensor* tensor2, float* result_data); -void sum_tensor_cpu(Tensor* tensor1, float* result_data); +void add_broadcasted_tensor_cpu(Tensor* tensor1, Tensor* tensor2, float* result_data, int* broadcasted_shape); +void sum_tensor_cpu(Tensor* tensor, float* result_data, int axis); void sub_tensor_cpu(Tensor* tensor1, Tensor* tensor2, float* result_data); void elementwise_mul_tensor_cpu(Tensor* tensor1, Tensor* tensor2, float* result_data); void scalar_div_tensor_cpu(float scalar, Tensor* tensor, float* result_data); diff --git a/norch/csrc/tensor.cpp b/norch/csrc/tensor.cpp index 837e720..28501d2 100644 --- a/norch/csrc/tensor.cpp +++ b/norch/csrc/tensor.cpp @@ -124,7 +124,44 @@ extern "C" { } } - Tensor* sum_tensor(Tensor* tensor) { + Tensor* add_broadcasted_tensor(Tensor* tensor1, Tensor* tensor2) { + + if (strcmp(tensor1->device, tensor2->device) != 0) { + fprintf(stderr, "Tensors must be on the same device: %s and %s\n", tensor1->device, tensor2->device); + exit(1); + } + + int max_ndim = tensor1->ndim > tensor2->ndim ? tensor1->ndim : tensor2->ndim; + + // Determine the broadcasted shape + int* broadcasted_shape = (int*)malloc(max_ndim * sizeof(int)); + if (broadcasted_shape == NULL) { + fprintf(stderr, "Memory allocation failed\n"); + exit(1); + } + for (int i = 0; i < max_ndim; i++) { + int dim1 = i < tensor1->ndim ? tensor1->shape[tensor1->ndim - 1 - i] : 1; + int dim2 = i < tensor2->ndim ? tensor2->shape[tensor2->ndim - 1 - i] : 1; + if (dim1 != dim2 && dim1 != 1 && dim2 != 1) { + fprintf(stderr, "Shapes are not compatible for broadcasting\n"); + exit(1); + } + broadcasted_shape[max_ndim - 1 - i] = dim1 > dim2 ? dim1 : dim2; + } + + // Allocate memory for result tensor + float* result_data = (float*)malloc(tensor1->size * sizeof(float)); + if (result_data == NULL) { + fprintf(stderr, "Memory allocation failed\n"); + exit(1); + } + + add_broadcasted_tensor_cpu(tensor1, tensor2, result_data, broadcasted_shape); + + return create_tensor(result_data, broadcasted_shape, max_ndim, tensor1->device); + } + + Tensor* sum_tensor(Tensor* tensor, int axis) { char* device = (char*)malloc(strlen(tensor->device) + 1); if (device != NULL) { @@ -133,14 +170,25 @@ extern "C" { fprintf(stderr, "Memory allocation failed\n"); exit(-1); } - int ndim = 1; - int* shape = (int*)malloc(ndim * sizeof(int)); - if (shape == NULL) { - fprintf(stderr, "Memory allocation failed\n"); - exit(1); + int ndim; + int* shape; + if (axis == -1) { + shape = (int*) malloc(sizeof(int)); + if (shape == NULL) { + fprintf(stderr, "Memory allocation failed\n"); + exit(1); + } + shape[0] = 1; + ndim = 1; + } else { + shape = (int*) malloc((tensor->ndim - 1) * sizeof(int)); + for (int i = 0, j = 0; i < tensor->ndim; ++i) { + if (i != axis) { + shape[j++] = tensor->shape[i]; + } + } + ndim = tensor->ndim - 1; } - - shape[0] = 1; if (strcmp(tensor->device, "cuda") == 0) { @@ -155,7 +203,7 @@ extern "C" { fprintf(stderr, "Memory allocation failed\n"); exit(1); } - sum_tensor_cpu(tensor, result_data); + sum_tensor_cpu(tensor, result_data, axis); return create_tensor(result_data, shape, ndim, device); } } diff --git a/norch/csrc/tensor.h b/norch/csrc/tensor.h index d45901f..5ff62d0 100644 --- a/norch/csrc/tensor.h +++ b/norch/csrc/tensor.h @@ -16,7 +16,7 @@ extern "C" { Tensor* create_tensor(float* data, int* shape, int ndim, char* device); float get_item(Tensor* tensor, int* indices); Tensor* add_tensor(Tensor* tensor1, Tensor* tensor2); - Tensor* sum_tensor(Tensor* tensor); + Tensor* sum_tensor(Tensor* tensor, int axis); Tensor* sub_tensor(Tensor* tensor1, Tensor* tensor2); Tensor* elementwise_mul_tensor(Tensor* tensor1, Tensor* tensor2); Tensor* scalar_mul_tensor(Tensor* tensor, float scalar); diff --git a/norch/libtensor.so b/norch/libtensor.so index 5dfcec63047f5168b672fb178dffd77ac0b38c74..aa08bb848b39f33912083477f58b75a773fd9af2 100755 GIT binary patch delta 27222 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 I5;fre07Jclg8%>k diff --git a/norch/tensor.py b/norch/tensor.py index 53b8416..f50edf7 100644 --- a/norch/tensor.py +++ b/norch/tensor.py @@ -223,27 +223,65 @@ class Tensor: def __add__(self, other): if isinstance(other, (int, float)): other = other * self.ones_like() + + # Function to determine if broadcasting is needed and get the broadcasted shape + def broadcast_shape(shape1, shape2): + if shape1 == shape2: + return shape1, False + + max_len = max(len(shape1), len(shape2)) + shape1 = [1] * (max_len - len(shape1)) + shape1 + shape2 = [1] * (max_len - len(shape2)) + shape2 + + broadcasted_shape = [] + for dim1, dim2 in zip(shape1, shape2): + if dim1 != dim2 and dim1 != 1 and dim2 != 1: + raise ValueError("Shapes are not compatible for broadcasting") + broadcasted_shape.append(max(dim1, dim2)) + return broadcasted_shape, True + + broadcasted_shape, needs_broadcasting = broadcast_shape(self.shape, other.shape) + + if needs_broadcasting: + # Call add_broadcasted_tensor if broadcasting is needed + Tensor._C.add_broadcasted_tensor.argtypes = [ctypes.POINTER(CTensor), ctypes.POINTER(CTensor)] + Tensor._C.add_broadcasted_tensor.restype = ctypes.POINTER(CTensor) + + result_tensor_ptr = Tensor._C.add_broadcasted_tensor(self.tensor, other.tensor) + + result_data = Tensor() + result_data.tensor = result_tensor_ptr + result_data.shape = broadcasted_shape + result_data.ndim = len(broadcasted_shape) + + result_data.device = self.device + result_data.numel = self.numel # Update this to calculate the correct number of elements if broadcasting + + result_data.requires_grad = self.requires_grad or other.requires_grad + if result_data.requires_grad: + result_data.grad_fn = AddBroadcastedBackward(self, other) - if self.shape != other.shape: - raise ValueError("Tensors must have the same shape for addition") - - Tensor._C.add_tensor.argtypes = [ctypes.POINTER(CTensor), ctypes.POINTER(CTensor)] - Tensor._C.add_tensor.restype = ctypes.POINTER(CTensor) + else: + # Call add_tensor if shapes are identical + Tensor._C.add_tensor.argtypes = [ctypes.POINTER(CTensor), ctypes.POINTER(CTensor)] + Tensor._C.add_tensor.restype = ctypes.POINTER(CTensor) - result_tensor_ptr = Tensor._C.add_tensor(self.tensor, other.tensor) + result_tensor_ptr = Tensor._C.add_tensor(self.tensor, other.tensor) - result_data = Tensor() - result_data.tensor = result_tensor_ptr - result_data.shape = self.shape.copy() - result_data.ndim = self.ndim - result_data.device = self.device - result_data.numel = self.numel + result_data = Tensor() + result_data.tensor = result_tensor_ptr + result_data.shape = self.shape.copy() + result_data.ndim = self.ndim - result_data.requires_grad = self.requires_grad or other.requires_grad - if result_data.requires_grad: - result_data.grad_fn = AddBackward(self, other) + result_data.device = self.device + result_data.numel = self.numel # Update this to calculate the correct number of elements if broadcasting + + result_data.requires_grad = self.requires_grad or other.requires_grad + if result_data.requires_grad: + result_data.grad_fn = AddBackward(self, other) return result_data + def __radd__(self, other): if isinstance(other, (int, float)): @@ -555,24 +593,33 @@ class Tensor: return result_data - def sum(self): - Tensor._C.sum_tensor.argtypes = [ctypes.POINTER(CTensor)] + def sum(self, axis=-1): + Tensor._C.sum_tensor.argtypes = [ctypes.POINTER(CTensor), ctypes.c_int] Tensor._C.sum_tensor.restype = ctypes.POINTER(CTensor) - result_tensor_ptr = Tensor._C.sum_tensor(self.tensor) + result_tensor_ptr = Tensor._C.sum_tensor(self.tensor, axis) result_data = Tensor() result_data.tensor = result_tensor_ptr - result_data.shape = [1] - result_data.ndim = 1 + + if axis == -1: + result_data.shape = [1] + result_data.ndim = 1 + else: + result_data.shape = self.shape[:axis] + self.shape[axis+1:] + result_data.ndim = len(result_data.shape) + result_data.device = self.device result_data.numel = 1 + for s in result_data.shape: + result_data.numel *= s result_data.requires_grad = self.requires_grad if result_data.requires_grad: result_data.grad_fn = SumBackward(self) return result_data + def sin(self): Tensor._C.sin_tensor.argtypes = [ctypes.POINTER(CTensor)] diff --git a/tests/test_autograd.py b/tests/test_autograd.py index a042ce6..4dc44eb 100644 --- a/tests/test_autograd.py +++ b/tests/test_autograd.py @@ -31,6 +31,26 @@ class TestTensorAutograd(unittest.TestCase): self.assertTrue(utils.compare_torch(norch_tensor1_grad, torch_tensor1_grad)) self.assertTrue(utils.compare_torch(norch_tensor2_grad, torch_tensor2_grad)) + def test_broadcasting_addition_autograd(self): + """ + Test autograd for broadcasting addition: tensor1 + tensor2 + """ + norch_tensor1 = norch.Tensor([[[1., 2, 3], [4, 5, 6]]], requires_grad=True).to(self.device) # Shape (1, 2, 3) + norch_tensor2 = norch.Tensor([1.5, -1, 0], requires_grad=True).to(self.device) # Shape (3) + norch_result = (norch_tensor1 + norch_tensor2).sum() + norch_result.backward() + norch_tensor1_grad = utils.to_torch(norch_tensor1.grad) + norch_tensor2_grad = utils.to_torch(norch_tensor2.grad) + + torch_tensor1 = torch.tensor([[[1., 2, 3], [4, 5, 6]]], requires_grad=True).to(self.device) # Shape (1, 2, 3) + torch_tensor2 = torch.tensor([1.5, -1, 0], requires_grad=True).to(self.device) # Shape (3) + torch_result = (torch_tensor1 + torch_tensor2).sum() + torch_result.backward() + torch_tensor1_grad = torch_tensor1.grad + torch_tensor2_grad = torch_tensor2.grad + + self.assertTrue(utils.compare_torch(norch_tensor1_grad, torch_tensor1_grad)) + self.assertTrue(utils.compare_torch(norch_tensor2_grad, torch_tensor2_grad)) def test_subtraction(self): """ diff --git a/tests/test_operations.py b/tests/test_operations.py index e3fac6a..3eeb8b1 100644 --- a/tests/test_operations.py +++ b/tests/test_operations.py @@ -35,6 +35,22 @@ class TestTensorOperations(unittest.TestCase): self.assertTrue(utils.compare_torch(torch_result, torch_expected)) + def test_broadcasting_addition(self): + """ + Test addition of two tensors with broadcasting: tensor1 + tensor2 + """ + norch_tensor1 = norch.Tensor([[[1, 2, 3], [4, 5, 6]]]).to(self.device) # Shape (1, 2, 3) + norch_tensor2 = norch.Tensor([1, 1, 1]).to(self.device) # Shape (3) + norch_result = norch_tensor1 + norch_tensor2 + torch_result = utils.to_torch(norch_result) + + torch_tensor1 = torch.tensor([[[1, 2, 3], [4, 5, 6]]]).to(self.device) # Shape (1, 2, 3) + torch_tensor2 = torch.tensor([1, 1, 1]).to(self.device) # Shape (3) + torch_expected = torch_tensor1 + torch_tensor2 # Broadcasting addition + + self.assertTrue(utils.compare_torch(torch_result, torch_expected)) + + def test_subtraction(self): """ Test subtraction of two tensors: tensor1 - tensor2 @@ -176,6 +192,21 @@ class TestTensorOperations(unittest.TestCase): self.assertTrue(utils.compare_torch(torch_result, torch_expected)) + def test_sum_axis_no_keepdims(self): + """ + Test summation of a tensor along a specific axis without keeping the dimensions + """ + norch_tensor = norch.Tensor([[[1, 2], [3, -4]], [[5, 6], [7, 8]]]).to(self.device) + norch_result = norch_tensor.sum(axis=1) + torch_result = utils.to_torch(norch_result) + + torch_tensor = torch.tensor([[[1, 2], [3, -4]], [[5, 6], [7, 8]]]).to(self.device) + torch_expected = torch.sum(torch_tensor, dim=1) + + print(torch_result) + print("\n\n", torch_expected) + self.assertTrue(utils.compare_torch(torch_result, torch_expected)) + def test_transpose_T(self): """ Test transposition of a tensor: tensor.T