Skip to content

Commit

Permalink
ADD: update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
T-K-233 committed Oct 15, 2024
1 parent 9b25b5f commit bb10d19
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 23 deletions.
45 changes: 34 additions & 11 deletions docs/Tensor-Basics.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
# Tensor Basics

Tensor types are resolved dynamically, such that the API is generic, does not include multiple struct definitions, and enables multiple types within a single program. That is, there is one Tensor type. The tensor may have doubles (DTYPE_F64), float (DTYPE_F32), ints, etc. This design makes it easy to write generic code.
> **Note**: Different from PyTorch, the attributes of the tensors are static, and are determined at compile time. Hence, tensor with different datatypes and dimensions are defined with separate struct definitions. That is, there is more than one Tensor type.
>
> This way, the runtime kernel library only needs to handle the shape information of the tensors, reducing the typechecking and broadcasting overhead.
The underlying fundamental operators will be statically typed, and hence the tensor-level API will dynamically determine which fundamental operator to use to do the computation.

## Attributes of a Tensor

Tensor attributes describe their dimension, shape, number of elements, and datatype.

In Baremetal-NN, the dimension and datatype of the tensor are static. Tensor with different shapes and datatypes are defined with different struct definitions.

```c
Tensor1D_F32 tensor; // this defines a 1D tensor with float datatype

Tensor2D_F16 tensor; // this defines a 2D tensor with half-precision floating-point (fp16) datatype
```

The maximum dimension supported is 4. These 4D tensors are used in 2D convolutions and attention layers.

The shape of the tensor is defined by an array of integers, which is a list of the lengths of each dimension. For example, a 2x3 tensor has a shape of `(size_t []){2, 3}`.

```c
Tensor *tensor = nn_rand(2, (size_t []){ 3, 4 }, DTYPE_F32);
Tensor2D_F32 tensor = {
.shape = (size_t []){2, 3},
.data = NULL,
};

printf("Shape of tensor: (%d, %d)", tensor.shape[0], tensor.shape[1]);

printf("Datatype of tensor: %s\n", nn_get_datatype_name(tensor->dtype));
printf("Dimension of tensor: %d\n", tensor->ndim);
printf("Shape of tensor: (%d, %d)\n", tensor->shape[0], tensor->shape[1]);
printf("Number of elements: %d\n", tensor->size);
// alternatively, we can use the helper function to print the shape of the tensor
nn_print_shape(2, tensor.shape);
```
## Tensor Element in Memory
Expand All @@ -29,12 +45,19 @@ If the data of the tensor is already allocated in memory, that memory can be vie
```c
float data[] = { 1, 2, 3,
4, 5, 6 };
Tensor *tensor = nn_tensor(2, (const size_t[]){2, 3}, DTYPE_F32, data);
Tensor tensor = {
.shape = (size_t []){2, 3},
.data = data,
};
```


## Zero-dimensional Tensors as Scalars

A scalar is represented by a Tensor object that is zero-dimensional. These Tensors hold a single value and they can be references to a single element in a larger Tensor. They can be used anywhere a Tensor is expected.
A scalar is represented by a Tensor object that is zero-dimensional. The `.data` field of Tensors hold a single value, instead of a pointer to an array of values. Additionally, they do not have a `.shape` field.

```c
Tensor0D_F32 scalar = {
.data = 42
};
```

When creating such zero-dimensional tensor, the shape will be a NULL pointer, but the size will be set to 1 and a single element worth of memory will be allocated as the data buffer.
15 changes: 3 additions & 12 deletions docs/Tensor-Creation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Tensors can be initialized in various ways. A set of factory functions are avail
A *factory function* is a function that produces a new tensor. There are many factory functions available, which differ in the way they initialize a new tensor before returning it. All factory functions adhere to the following general "schema”:

```c
Tensor *NN_<function-name>(<ndim>, <shape>, <datatype>, <tensor-options>)
Tensor *nn_{creation-function}_{ndim}d_{datatype}({shape}, {options});
```
### Available Factory Functions
Expand All @@ -21,15 +21,15 @@ Returns a tensor with uninitialized values or preallocated buffer.
When passing NULL as the data buffer, the method will allocate a new chunk of uninitialized data chunk.
```c
Tensor *tensor = nn_tensor(2, (size_t []){ 2, 2 }, DTYPE_F32, NULL);
Tensor *tensor = nn_tensor2d_f32((size_t []){ 2, 2 }, NULL);
```

Alternatively, tensor be created directly from an existing data buffer.

```c
// data = [[1, 2], [3, 4]]
float data[] = { 1, 2, 3, 4 };
Tensor *tensor = nn_tensor(2, (size_t []){ 2, 2 }, DTYPE_F32, data);
Tensor *tensor = nn_tensor2d_f32((size_t []){ 2, 2 }, data);
```
#### nn_zeros()
Expand All @@ -47,12 +47,3 @@ Returns a tensor filled with a single value.
#### nn_rand()
Returns a tensor filled with values drawn from a uniform distribution on [0, 1).

#### nn_randint()

Returns a tensor with integers randomly drawn from an interval.

#### nn_arange()

Returns a tensor with a sequence of integers.

60 changes: 60 additions & 0 deletions nn/nn.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,66 @@ static inline uint8_t float_equal(float golden, float actual, float rel_err) {
return (fabs(actual - golden) < rel_err) || (fabs((actual - golden) / actual) < rel_err);
}


/**
* nn_tensor0d_f16
*
* Creates a 0D tensor with type F16.
*
* @param data The data to store in the tensor.
*/
Tensor0D_F16 *nn_tensor0d_f16(float16_t data);

/**
* nn_tensor0d_f32
*
* Creates a 0D tensor with type F32.
*
* @param data The data to store in the tensor.
*/
Tensor0D_F32 *nn_tensor0d_f32(float data);

/**
* nn_tensor1d_f16
*
* Creates a 1D tensor with type F16.
*
* @param shape The shape of the tensor.
* @param data The data to store in the tensor.
*/
Tensor1D_F16 *nn_tensor1d_f16(size_t shape[1], const float16_t *data);

/**
* nn_tensor1d_f32
*
* Creates a 1D tensor with type F32.
*
* @param shape The shape of the tensor.
* @param data The data to store in the tensor.
*/
Tensor1D_F32 *nn_tensor1d_f32(size_t shape[1], const float *data);

/**
* nn_tensor2d_f16
*
* Creates a 2D tensor with type F16.
*
* @param shape The shape of the tensor.
* @param data The data to store in the tensor.
*/
Tensor2D_F16 *nn_tensor2d_f16(size_t shape[2], const float16_t *data);

/**
* nn_tensor2d_f32
*
* Creates a 2D tensor with type F32.
*
* @param shape The shape of the tensor.
* @param data The data to store in the tensor.
*/
Tensor2D_F32 *nn_tensor2d_f32(size_t shape[2], const float *data);


/**
* nn_print_u8
*
Expand Down

0 comments on commit bb10d19

Please sign in to comment.