Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for keras RNN #329

Closed

Conversation

chaitanyaPaikara
Copy link

@chaitanyaPaikara chaitanyaPaikara commented Apr 29, 2021

Working keras-converter and RNN template code for LSTM layer, will update with GRU code later. Review @vloncar

Comment on lines 74 to 100
rnn_mult1_config_template = """struct config{index}_mult1 : nnet::dense_config {{
static const unsigned n_in = {n_in};
static const unsigned n_out = {n_out};
static const unsigned io_type = nnet::{iotype};
static const unsigned strategy = nnet::{strategy};
static const unsigned reuse_factor = {reuse};
typedef {accum_t} accum_t;
typedef {bias_t} bias_t;
typedef {weight_t} weight_t;
template<class x_T, class y_T, class res_T>
using product = nnet::product::{product_type}<x_T, y_T, res_T>;
}};\n"""


rnn_mult2_config_template = """struct config{index}_mult2 : nnet::dense_config {{
static const unsigned n_in = {n_in};
static const unsigned n_out = {n_out};
static const unsigned io_type = nnet::{iotype};
static const unsigned strategy = nnet::{strategy};
static const unsigned reuse_factor = {reuse};
typedef {accum_t} accum_t;
typedef {bias_t} bias_t;
typedef {weight_t} weight_t;
template<class x_T, class y_T, class res_T>
using product = nnet::product::{product_type}<x_T, y_T, res_T>;
}};\n"""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the contents of these are the same as the Dense config, do we need to define new templates? Can you just pick up the existing Dense template?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reusing the Dense config templates and removed theses new definitions of mult configs

Comment on lines 35 to 37
// Activation enum
enum activ_type {activ_relu = 0, activ_sigmoid, activ_tanh, activ_softmax};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should find a nicer mechanism to enable multiple activation functions from the RNN. One method could be to make classes for the activation functions, and then include in the RNN layer config the object that corresponds to the appropriate activation.

The aim would be to eliminate the if (CONFIG_T::activation_type == activation_relu) { relu<...>(...) } constructions in the HLS and replace it with a single line like:
CONFIG_T::template activation<...>::activation(...)

It's similar to what we do now with the nnet::mult - classes here, config here, and call to product here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Followed the same method used for nnet::mult and passing objects for appropriate activation now.

// - output = V*newstate
// - If softmax is needed on output, perform *outside* this operations
template<class data_T, class res_T, typename CONFIG_T, typename ACT_CONFIG_T>
void simple_rnn(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like these simple_rnn and simple_rnn_static methods are not accessible through the hls4ml conversion, only the LSTM and GRU. Is that correct? If so, these implementations should be removed. Or otherwise, they should be made accessible through the conversion flow.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we can remove them as people hardly use basic RNN layers.

Copy link
Contributor

@vloncar vloncar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating the previous PR to the latest code! This is a great start

layer['n_out'] = int(weights_shape[1]/div_factor)
layer['recurr_n_in']=recurrent_weights_shape[0]
layer['recurr_n_out']=recurrent_weights_shape[1]
layer['recurr_n_subout']=[recurrent_weights_shape[1]]
Copy link
Contributor

@vloncar vloncar May 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still remnants of sublayer behavior, they should be removed since we abandoned that idea.


layer = parse_default_keras_layer(keras_layer, input_names)

weights_shape = data_reader.get_weights_shape(layer['name'], 'kernel')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of inferring attributes from weights, we should extract the number of hidden units and use that to compute the shapes. Keras uses this (units), as well as pytorch and onnx (as hidden_size attribute).

Also, other LSTM layer properties should be parsed as well. See the docs for all things that can be set. Even if we don't support them all in the current HLS implementation we should be aware of them so we can provide a message to the user if something is not supported as opposed to silently ignoring them and doing something unintended.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed the same format used for other layers which were using weight_shape to figure out the shape. I've added all the architecture related properties of the RNN layer to the layer dictionary. Only properties associated with training aren't added.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about adding the parsing of all the other layer properties, could you add that @chaitanyaPaikara ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thesps, Yeah, I can add those other properties but they were all properties related to model training, so I skipped them as they were not influencing the inference implementation with hls4ml templates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return_state, go_backwards, stateful, time_major, reset_after... Preferably the conversion should check the unsupported parameters in initialize and raise exceptions in case of configuration we can't handle yet.

layer['recurr_n_subout']=[recurrent_weights_shape[1]]
layer['recurr_n_part'] = 1

output_shape = [input_shapes[0][0], layer['n_sequence_out'], layer['n_out']]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If return_sequences is set to False, which is the default, the output shape is 2D, not 3D. This will cause the issues when parsing the architecture.

@@ -118,6 +118,8 @@ def parse_default_keras_layer(keras_layer, input_names):
layer['activation'] = keras_layer['config']['activation']
if 'epsilon' in keras_layer['config']:
layer['epsilon'] = keras_layer['config']['epsilon']
if 'recurrent_activation' in keras_layer['config']:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Activations are separated out here because in hls4ml we do them separately from the layer in which they are defined. Since that is not the case for RNNs, recurrent_activation should not be parsed here, but rather in the appropriate handler.

if self.model.config.is_resource_strategy(self):
if self.model.config.backend.name == 'Vivado':
self.model.config.backend.set_closest_reuse_factor(self)
self.model.config.backend.set_closest_reuse_factor_recr(self)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this function changes the object passed to it and doesn't return values that are assigned to an object, it would be better to extend set_closest_reuse_factor and not introduce the _recr variant and add recr parameter to it.

def get_valid_reuse_factors(self, layer):
self.register_templates('GarNetStack' , garnet_stack_function_template,garnet_stack_config_template, garnet_include_list)

def get_valid_reuse_factors(self, layer, recr = False):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned this before, I think you can get away without recr parameter. Or refactor this function so that it accepts n_in and n_out parameters and move their computation to the caller

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have extended the set_closest_reuse_factor to also check for the recr and I think its more straightforward or simple to check for recr parameter and find range of reuse factors. Also, a recr is essential in the set_closest_reuse_factor as there are two matrix multiplications for regular and recurrent weights, and passing to get_closest_reuse_factor just makes it easier without much code change in the original file. If necessary, I can still refactor it to do away with the recr.

tanh<typename CONFIG_T::state_t, typename CONFIG_T::state_t, ACT_CONFIG_T>(rawstate, newstate);
}

std::cout << "Activated State: [ "; for (int ii = 0; ii < CONFIG_T::n_state; ii++) std::cout << newstate[ii] << " "; std::cout << "]" << std::endl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not useful unless we are debugging the implementation itself so it should be removed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was part of the basic RNN layer. Will be removing those functions as they are not used anyway.

) {
// Initialize the state variable -- will maintain state between function calls
# ifndef __SYNTHESIS__
std::cout << "S Pre-State: [ "; for (int ii = 0; ii < CONFIG_T::n_state; ii++) std::cout << s_newstate[ii] << " "; std::cout << "]" << std::endl;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

}

template<class data_T, class res_T, typename CONFIG_T>
void lstm_loop(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if this was called just lstm or lstm_stack and then the loop iteration should be lstm_loop

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to lstm_stack

}

template<class data_T, class res_T, typename CONFIG_T>
void gru_loop(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to gru_stack

@thesps
Copy link
Contributor

thesps commented May 11, 2021

Hi @chaitanyaPaikara, let's only mark review items as "resolved" when the code changes that address the issue are pushed please.

…ons; and removed redundant functions and template code
@chaitanyaPaikara
Copy link
Author

Hi @thesps and @vloncar, I've pushed the latest changes addressing the above issues. Please let me know once you've reviewed these changes.

@chaitanyaPaikara
Copy link
Author

Hi @vloncar and @thesps, could you please review the added changes and let me know if there are other issues.

This was referenced Aug 12, 2021
@jmduarte jmduarte mentioned this pull request Sep 10, 2021
@vloncar
Copy link
Contributor

vloncar commented Jul 21, 2022

Done in #560. Closing

@vloncar vloncar closed this Jul 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants