Skip to content

Commit

Permalink
Merge pull request #147 from majianjia/dev-0.4.x
Browse files Browse the repository at this point in the history
Dev 0.4.x Add reshape layer.
  • Loading branch information
majianjia authored Nov 29, 2021
2 parents 6f93f13 + e4d4e59 commit 354f755
Show file tree
Hide file tree
Showing 11 changed files with 547 additions and 59 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ Please check [examples](https://github.com/majianjia/nnom/tree/master/examples)
| Lambda |lambda_s()| Lambda() |single input / single output anonymous operation|
| Batch Normalization |N/A| N/A| This layer is merged to the last Conv by the script|
| Flatten|flatten_s()| Flatten()| |
| Reshape (New!)|reshape_s()| N/A| |
| SoftMax|softmax_s()| SoftMax()| Softmax only has layer API|
| Activation|N/A| Activation()|A layer instance for activation|
| Input/Output |input_s()/output_s()| Input()/Output()| |
Expand Down
66 changes: 64 additions & 2 deletions docs/guide_development.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,71 @@
# Development Guide

Currently, it is not yet a "guide". At least, it provides some further information.

-----

## Must READ

**This section shows the basic and necessary info for development with NNoM.** Please do read carefuly before you start the development.

**Why this is important:**

Firstly, the aim of NNoM is narrow, only focusing the low-resources platform. Therefore, the ops it supports will always be a subset of comprehensive tools (e.g. Tensorflow, Pytorch).
Which means you must train your model in a restricted configurations/forms to be able to use NNoM to run.

Secondly, NNoM is different from other inteference lib which only act as an parser of existing protobuf or model file.
NNoM instead has its own representative of a model, which doesn't need runtime memory allocation or runtime parsing, brings maximum performance.
(less than 1us (avg) switch time between layers' backend ops)

It is always recommended that **the developer who trains keras model is the same who deploy the model to NNoM**, to manipulate between the configuration and pefromance.

### Supported layer
As said, NNoM only support a subset layers of the Keras, please refer to [the list in the main page](index.md) for details.


### The Restriction

**No support for an activation embedded with layer's configuration.** i.e. `x = Conv2D(..., activation='relu')(x)`.
The below form are supported.
~~~
x = Conv2D(..., activation='relu')(x)
x = ReLU()(x)
~~~

**Do not change the name of a layer.** The current script is still using name to recognise a layer's type. (Using type() is not stable in Keras.)
Changing names will lead to unable to recognise layer.


#### For layers:
Agains, for all layer, **do not change the default name** or if you know how to keep the keyword.

#### Regular Convolution
Including `Conv1D`, `Conv2D`, `DepthwiseConv1D`, `DepthwiseConv2D`

**Supported config:** `filters`, `kernel_size` ,`dilation_rate` ,`stride_size` , `padding`, `depth_multiplier`.
Unrelated configs are not affecting: `bias/weight's constrains/regularlizer/inits.`

**Restriction:** `use_bias` must be `True`

Examples that works:
~~~
x = Conv2D(16, dilation_rate=(1,1), kernel_size=(5, 5), strides=(1, 1), padding="valid")(x)
x = DepthwiseConv2D(depth_multiplier=2, dilation_rate=(2,2), kernel_size=(3, 3), padding="same")(x)
~~~

#### Seperatable Conv

No support for generic Seperatable Conv. Please should you use `Depthwise Conv` following by an regular `Conv` (Pointwise Conv) instead.

Example that works:
~~~
x = DepthwiseConv2D(depth_multiplier=2, kernel_size=(3, 3), padding="same")(x)
x = Conv2D(16, kernel_size=(1, 1), strides=(1, 1), padding="same")(x)
~~~






## Frequent Questions and Answers (Q&A)

### What is NNoM different from others?
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Check [Porting and optimising Guide](Porting_and_Optimisation_Guide.md) for deta
| Lambda |lambda_s()| Lambda() |single input / single output anonymous operation|
| Batch Normalization |N/A| N/A| This layer is merged to the last Conv by the script|
| Flatten|flatten_s()| Flatten()| |
| Reshape (New!)|reshape_s()| N/A| |
| SoftMax|softmax_s()| SoftMax()| Softmax only has layer API|
| Activation|N/A| Activation()|A layer instance for activation|
| Input/Output |input_s()/output_s()| Input()/Output()| |
Expand Down
4 changes: 2 additions & 2 deletions examples/auto_test/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ def train(model, x_train, y_train, x_test, y_test, batch_size=64, epochs=50):
shuffle=False)

save_model(model, save_dir)
del model
tf.keras.backend.clear_session()
return history

def main():
Expand Down Expand Up @@ -111,6 +109,8 @@ def main():

# train model
history = train(model, x_train, y_train, x_test.copy(), y_test.copy(), epochs=epochs)
del model
tf.keras.backend.clear_session()

# -------- generate weights.h (NNoM model) ----------
# get the best model
Expand Down
376 changes: 321 additions & 55 deletions examples/rnn-denoise/denoise_weights.h

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions inc/layers/nnom_reshape.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2018-2020
* Jianjia Ma
* [email protected]
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2020-12-07 Jianjia Ma The first version
*/

#ifndef __NNOM_RESHAPE_H__
#define __NNOM_RESHAPE_H__

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <string.h>
#include <stdbool.h>

#include "nnom.h"
#include "nnom_layers.h"
#include "nnom_local.h"
#include "nnom_tensor.h"

typedef struct _nnom_reshape_layer_t
{
nnom_layer_t super;
nnom_shape_data_t* dim;
uint8_t num_dim;

} nnom_reshape_layer_t;

typedef struct nnom_reshape_config_t
{
nnom_layer_config_t super;
nnom_shape_data_t* dim;
uint8_t num_dim;
} nnom_reshape_config_t;

// method
nnom_status_t reshape_run(nnom_layer_t *layer);
nnom_status_t reshape_build(nnom_layer_t *layer);
nnom_status_t reshape_free(nnom_layer_t *layer);

// API
nnom_layer_t *reshape_s(const nnom_reshape_config_t *config);

#ifdef __cplusplus
}
#endif

#endif /* __NNOM_CONV2D_H__ */
2 changes: 2 additions & 0 deletions inc/nnom.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ typedef enum
NNOM_GLOBAL_SUMPOOL,
NNOM_UPSAMPLE,
NNOM_FLATTEN,
NNOM_RESHAPE,
NNOM_LAMBDA,
NNOM_CONCAT,
NNOM_ADD,
Expand Down Expand Up @@ -129,6 +130,7 @@ typedef enum
"GL_SumPool", \
"UpSample", \
"Flatten", \
"Reshape", \
"Lambda", \
"Concat", \
"Add", \
Expand Down
3 changes: 3 additions & 0 deletions inc/nnom_layers.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ nnom_layer_io_t *io_init(void *owner_layer, nnom_layer_io_t *io);
#include "layers/nnom_dense.h"
#include "layers/nnom_dw_conv2d.h"
#include "layers/nnom_flatten.h"
#include "layers/nnom_reshape.h"
#include "layers/nnom_global_pool.h"
#include "layers/nnom_input.h"
#include "layers/nnom_lambda.h"
Expand Down Expand Up @@ -144,12 +145,14 @@ nnom_status_t sumpool_build(nnom_layer_t* layer);
nnom_status_t global_pool_build(nnom_layer_t* layer);

nnom_status_t flatten_build(nnom_layer_t* layer);
nnom_status_t reshape_build(nnom_layer_t* layer);
nnom_status_t concat_build(nnom_layer_t* layer);

// run
nnom_status_t input_run(nnom_layer_t* layer);
nnom_status_t output_run(nnom_layer_t* layer);
nnom_status_t flatten_run(nnom_layer_t* layer);
nnom_status_t reshape_run(nnom_layer_t* layer);
nnom_status_t default_run(nnom_layer_t* layer); // simply copy data from input to output

nnom_status_t dw_conv2d_run(nnom_layer_t* layer);
Expand Down
15 changes: 15 additions & 0 deletions scripts/gen_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,21 @@ def gen_flatten_config(layer):
c = c.replace('<base_config>', gen_base_config(layer))
return c

def gen_reshape_config(layer):
c = '''
const nnom_shape_data_t <layer_name>_targeted_shape[] = <shape>;
const nnom_reshape_config_t <layer_name>_config = {
.super = <base_config>,
.dim = (nnom_shape_data_t*)<layer_name>_targeted_shape,
.num_dim = <num_dim>
};
'''
c = c.replace('<layer_name>', layer.name)
c = c.replace('<base_config>', gen_base_config(layer))
c = c.replace('<shape>', to_cstyle(layer.output_shape[1:]))
c = c.replace('<num_dim>', str(len(layer.output_shape[1:])))
return c

def gen_concat_config(layer):
c = '''
const nnom_concat_config_t <layer_name>_config = {
Expand Down
5 changes: 5 additions & 0 deletions scripts/nnom.py
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,8 @@ def gen_weight_tensor(w, per_axis):
fp.write(gen_softmax_config(layer))
elif (type(layer) in [Flatten]):
fp.write(gen_flatten_config(layer))
elif (type(layer) in [Reshape]):
fp.write(gen_reshape_config(layer))
elif (type(layer) in [Concatenate]):
fp.write(gen_concat_config(layer))
elif (type(layer) in [Lambda]):
Expand Down Expand Up @@ -1000,6 +1002,9 @@ def gen_weight_tensor(w, per_axis):
elif ('flatten' in layer.name): # flatten is needed in CHW backend but not needed in HWC
inp = layer_name_from_tensor(layer.input)
fp.write('\tlayer[{0}] = model.hook(flatten_s(&{1}_config), layer[{2}]);\n'.format(id, layer.name, LI[inp][0]))
elif ('reshape' in layer.name): # flatten is needed in CHW backend but not needed in HWC
inp = layer_name_from_tensor(layer.input)
fp.write('\tlayer[{0}] = model.hook(reshape_s(&{1}_config), layer[{2}]);\n'.format(id, layer.name, LI[inp][0]))
elif ('concatenate' in layer.name):
inps = [layer_name_from_tensor(input) for input in layer.input]
inX = ''
Expand Down
77 changes: 77 additions & 0 deletions src/layers/nnom_reshape.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2018-2020
* Jianjia Ma
* [email protected]
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2020-12-07 Jianjia Ma The first version
*/

#include <stdint.h>
#include <string.h>
#include <stdbool.h>

#include "nnom.h"
#include "nnom_local.h"
#include "nnom_layers.h"
#include "layers/nnom_reshape.h"


nnom_layer_t *reshape_s(const nnom_reshape_config_t *config)
{
nnom_reshape_layer_t *layer;
nnom_layer_io_t *in, *out;

// allocate a block memory for all the sub handles and shifts.
size_t mem_size = sizeof(nnom_reshape_layer_t) + sizeof(nnom_layer_io_t) * 2 ;
layer = nnom_mem(mem_size);
if (layer == NULL)
return NULL;

// distribut the memory to sub handles.
in = (void *)((uint8_t*)layer + sizeof(nnom_reshape_layer_t));
out = (void *)((uint8_t*)in + sizeof(nnom_layer_io_t));

// set type in layer parent
layer->super.type = NNOM_RESHAPE;
layer->super.run = reshape_run;
layer->super.build = reshape_build;
// set buf state
in->type = NNOM_TENSOR_BUF_TEMP;
out->type = NNOM_TENSOR_BUF_NULL;

// config
//nnom_memcpy(layer->dim, config->dim, config->num_dim * sizeof(nnom_shape_data_t));
layer->super.config = config;
layer->dim = config->dim; // temporary use the config directly. (not preferable.)
layer->num_dim = config->num_dim;

// put in & out on the layer.
layer->super.in = io_init(layer, in);
layer->super.out = io_init(layer, out);

return (nnom_layer_t *)layer;
}

nnom_status_t reshape_build(nnom_layer_t *layer)
{
nnom_reshape_layer_t *cl = (nnom_reshape_layer_t *)layer;

// get the tensor from last layer's output
layer->in->tensor = layer->in->hook.io->tensor;

// create new tensor for output
layer->out->tensor = new_tensor(NNOM_QTYPE_PER_TENSOR, layer->in->tensor->num_dim, tensor_get_num_channel(layer->in->tensor));
tensor_set_attr(layer->out->tensor, layer->in->tensor->q_dec, layer->in->tensor->q_offset, cl->dim, cl->num_dim, 8);

return NN_SUCCESS;
}

nnom_status_t reshape_run(nnom_layer_t *layer)
{
return NN_SUCCESS;
}

0 comments on commit 354f755

Please sign in to comment.