|
1 | 1 | from hls4ml.backends.backend import get_backend |
2 | 2 | from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate |
3 | | -from hls4ml.model.layers import GRU, LSTM, TimeDistributed |
| 3 | +from hls4ml.model.layers import GRU, LSTM, Bidirectional, TimeDistributed |
4 | 4 |
|
5 | 5 | # recurrent multiplication template |
6 | 6 |
|
|
86 | 86 | static const bool pytorch_order = {pytorch}; |
87 | 87 | }};\n""" |
88 | 88 |
|
| 89 | +# Bidirectional templates |
| 90 | + |
| 91 | +single_config_template = """struct config{index} : nnet::single_layer_config {{ |
| 92 | + typedef {accum_t.name} accum_t; |
| 93 | + typedef {weight_t.name} weight_t; // Matrix |
| 94 | + typedef {recurrent_weight_t.name} recurrent_weight_t; // Matrix |
| 95 | + typedef {bias_t.name} bias_t; // Vector |
| 96 | + typedef {recurrent_bias_t.name} recurrent_bias_t; // Vector |
| 97 | + typedef {config_mult_t1} mult_config1; |
| 98 | + typedef {config_mult_t2} mult_config2; |
| 99 | + typedef {recr_act_t} ACT_CONFIG_{RECR_TYPE}; |
| 100 | + template<class x_T, class y_T, class config_T> |
| 101 | + using activation_recr = nnet::activation::{recurrent_activation}<x_T, y_T, config_T>; |
| 102 | + typedef {act_t} ACT_CONFIG_T; |
| 103 | + template<class x_T, class y_T, class config_T> |
| 104 | + using activation = nnet::activation::{activation}<x_T, y_T, config_T>; |
| 105 | + static const unsigned n_in = {n_in}; |
| 106 | + static const unsigned n_state = {n_state}; |
| 107 | + static const unsigned n_mult = {n_mult}; |
| 108 | + static const bool pytorch_order = {pytorch}; |
| 109 | +}};\n""" |
| 110 | + |
| 111 | +bidirectional_config_template = """struct config{index} : nnet::bidirectional_config {{ |
| 112 | + typedef {forward_t} FORWARD_CONFIG; |
| 113 | + template<class x_T, class y_T, typename config_T, bool backward> |
| 114 | + using RNNfunc_forward = nnet::{forward_layer}<x_T, y_T, config_T, backward>; |
| 115 | + typedef {backward_t} BACKWARD_CONFIG; |
| 116 | + template<class x_T, class y_T, typename config_T, bool backward> |
| 117 | + using RNNfunc_backward = nnet::{backward_layer}<x_T, y_T, config_T, backward>; |
| 118 | + static const unsigned n_in = {n_in}; |
| 119 | + static const unsigned n_out = {n_out}; |
| 120 | + static const unsigned n_sequence = {n_sequence}; |
| 121 | + static const unsigned n_sequence_out = {n_sequence_out}; |
| 122 | + static const unsigned io_type = nnet::{strategy}; |
| 123 | + static const unsigned reuse_factor = {reuse}; |
| 124 | + static const bool store_weights_in_bram = false; |
| 125 | + static const bool use_static = {static}; |
| 126 | + static const bool pytorch_order = {pytorch}; |
| 127 | +}};\n""" |
| 128 | + |
89 | 129 | recr_function_template = 'nnet::{recr_type}_stack<{input_t}, {output_t}, {config}>({input}, {output}, {w}, {wr}, {b}, {br});' |
90 | 130 | recr_function_template_initial_states_lstm = 'nnet::{recr_type}_stack<{input_t}, {input2_t}, {input3_t}, {output_t}, {config}>({input}, {input2}, {input3}, {output}, {w}, {wr}, {b}, {br});' # noqa: E501 |
91 | 131 | recr_function_template_initial_states_gru = 'nnet::{recr_type}_stack<{input_t}, {input2_t}, {output_t}, {config}>({input}, {input2}, {output}, {w}, {wr}, {b}, {br});' # noqa: E501 |
92 | 132 |
|
| 133 | +bidirectional_function_template = 'nnet::bidirectional_stack<{input_t}, {output_t}, {config}>({input}, {output}, {w}, {wr}, {b}, {br}, {w_b}, {wr_b}, {b_b}, {br_b});' # noqa: E501 |
| 134 | + |
93 | 135 | recr_include_list = ['nnet_utils/nnet_recurrent.h'] |
94 | 136 |
|
95 | 137 |
|
@@ -207,6 +249,153 @@ def format(self, node): |
207 | 249 | return mult_config1 + '\n' + mult_config2 + '\n' + recr_act_config + '\n' + act_config + '\n' + recr_config |
208 | 250 |
|
209 | 251 |
|
| 252 | +class BidirectionalConfigTemplate(LayerConfigTemplate): |
| 253 | + def __init__(self): |
| 254 | + super().__init__(Bidirectional) |
| 255 | + self.template = bidirectional_config_template |
| 256 | + self.layer_template = single_config_template |
| 257 | + self.act_template = activ_config_template |
| 258 | + self.recr_act_template = recr_activ_config_template |
| 259 | + self.mult1_template = recr_mult_config_template_1 |
| 260 | + self.mult2_template = recr_mult_config_template_2 |
| 261 | + |
| 262 | + def format(self, node): |
| 263 | + |
| 264 | + # ----- Bidirectional Layer Config -----# |
| 265 | + params = self._default_config_params(node) |
| 266 | + |
| 267 | + params['n_in'] = node.get_input_variable().dim_names[1] |
| 268 | + params['n_sequence'] = node.get_input_variable().dim_names[0] |
| 269 | + if node.get_attr('return_sequences'): |
| 270 | + params['n_sequence_out'] = node.get_output_variable().dim_names[0] |
| 271 | + else: |
| 272 | + params['n_sequence_out'] = 1 |
| 273 | + params['n_out'] = node.get_attr('n_out') |
| 274 | + params['strategy'] = node.get_attr('strategy') |
| 275 | + params['static'] = 'true' if node.attributes['static'] else 'false' |
| 276 | + params['pytorch'] = 'true' if node.get_attr('pytorch', False) else 'false' |
| 277 | + params['forward_t'] = f'config{node.index}_forward' |
| 278 | + params['backward_t'] = f'config{node.index}_backward' |
| 279 | + params['forward_layer'] = node.get_attr('forward_class_name').lower() + '_class' |
| 280 | + params['backward_layer'] = node.get_attr('backward_class_name').lower() + '_class' |
| 281 | + if node.attributes['static']: |
| 282 | + params['forward_layer'] += '_static' |
| 283 | + params['backward_layer'] += '_static' |
| 284 | + |
| 285 | + recr_config = self.template.format(**params) |
| 286 | + |
| 287 | + # ----- Forward and Backward Layers Config -----# |
| 288 | + result = '' |
| 289 | + for d in ['forward', 'backward']: |
| 290 | + if node.get_attr(f'{d}_class_name') == 'LSTM': |
| 291 | + n_recr_mult = 4 |
| 292 | + else: # GRU |
| 293 | + n_recr_mult = 3 |
| 294 | + |
| 295 | + # ----- Layer Config -----# |
| 296 | + layer_params = self._default_config_params(node) |
| 297 | + layer_params['n_in'] = params['n_in'] |
| 298 | + layer_params['pytorch'] = params['pytorch'] |
| 299 | + layer_params['n_state'] = node.get_attr(f'{d}_n_states') |
| 300 | + layer_params['n_mult'] = 4 |
| 301 | + if node.get_attr(f'{d}_class_name').lower() == 'gru': |
| 302 | + layer_params['n_mult'] = 3 |
| 303 | + layer_params['config_mult_t1'] = f'config{node.index}_1_{d[0]}' |
| 304 | + layer_params['config_mult_t2'] = f'config{node.index}_2_{d[0]}' |
| 305 | + layer_params['recr_act_t'] = '{}_config{}_recr'.format( |
| 306 | + node.get_attr(f'{d}_recurrent_activation'), str(node.index) + f'_{d[0]}' |
| 307 | + ) |
| 308 | + layer_params['act_t'] = '{}_config{}'.format(node.get_attr(f'{d}_activation'), str(node.index) + f'_{d[0]}') |
| 309 | + layer_params['RECR_TYPE'] = node.get_attr(f'{d}_class_name') |
| 310 | + |
| 311 | + layer_params['weight_t'] = layer_params[f'{d}_weight_t'] |
| 312 | + layer_params['recurrent_weight_t'] = layer_params[f'{d}_recurrent_weight_t'] |
| 313 | + layer_params['bias_t'] = layer_params[f'{d}_bias_t'] |
| 314 | + layer_params['recurrent_bias_t'] = layer_params[f'{d}_recurrent_bias_t'] |
| 315 | + layer_params['activation'] = layer_params[f'{d}_activation'] |
| 316 | + layer_params['recurrent_activation'] = layer_params[f'{d}_recurrent_activation'] |
| 317 | + |
| 318 | + layer_params['index'] = str(node.index) + f'_{d}' |
| 319 | + |
| 320 | + layer_config = self.layer_template.format(**layer_params) |
| 321 | + |
| 322 | + # ----- Activations Config -----# |
| 323 | + act_params = self._default_config_params(node) |
| 324 | + recr_act_params = self._default_config_params(node) |
| 325 | + |
| 326 | + act_params['type'] = node.get_attr(f'{d}_activation') |
| 327 | + recr_act_params['type'] = node.get_attr(f'{d}_recurrent_activation') |
| 328 | + act_params['index'] = str(node.index) + f'_{d[0]}' |
| 329 | + recr_act_params['index'] = str(node.index) + f'_{d[0]}' |
| 330 | + act_params['n_in'] = node.get_attr(f'{d}_n_states') |
| 331 | + recr_act_params['n_in'] = node.get_attr(f'{d}_n_states') * (n_recr_mult - 1) |
| 332 | + |
| 333 | + act_config = self.act_template.format(**act_params) |
| 334 | + recr_act_config = self.recr_act_template.format(**recr_act_params) |
| 335 | + |
| 336 | + # ----- Mult Config -----# |
| 337 | + mult_params1 = self._default_config_params(node) |
| 338 | + mult_params2 = self._default_config_params(node) |
| 339 | + |
| 340 | + mult_params1['n_in'] = node.get_input_variable().shape[1] |
| 341 | + mult_params1['n_out'] = node.get_attr(f'{d}_n_states') * n_recr_mult |
| 342 | + mult_params1['product_type'] = get_backend('vivado').product_type( |
| 343 | + node.get_input_variable().type.precision, node.get_weights(f'{d}_weight').type.precision |
| 344 | + ) |
| 345 | + mult_params1['reuse'] = params['reuse'] |
| 346 | + mult_params1['index'] = str(node.index) + f'_1_{d[0]}' |
| 347 | + mult_params1['nzeros'] = node.get_weights(f'{d}_weight').nzeros |
| 348 | + mult_params1['nonzeros'] = node.get_weights(f'{d}_weight').nonzeros |
| 349 | + |
| 350 | + mult_params1['bias_t'] = mult_params1[f'{d}_bias_t'] |
| 351 | + mult_params1['weight_t'] = mult_params1[f'{d}_weight_t'] |
| 352 | + mult_params2['recurrent_bias_t'] = mult_params2[f'{d}_recurrent_bias_t'] |
| 353 | + mult_params2['recurrent_weight_t'] = mult_params2[f'{d}_recurrent_weight_t'] |
| 354 | + |
| 355 | + namespace = params['namespace'] |
| 356 | + |
| 357 | + if node.get_attr('strategy').lower() == 'latency': |
| 358 | + mult_params1['dense_function'] = 'nnet::DenseLatency' |
| 359 | + elif node.get_attr('strategy').lower() == 'resource': |
| 360 | + if int(mult_params1[f'{d}_reuse_factor']) <= int(mult_params1['n_in']): |
| 361 | + mult_params1['dense_function'] = 'nnet::DenseResource_rf_leq_nin' |
| 362 | + else: |
| 363 | + mult_params1['dense_function'] = 'nnet::DenseResource_rf_gt_nin_rem0' |
| 364 | + # The 3rd case is never used |
| 365 | + elif node.get_attr('strategy').lower() == 'resource_unrolled': |
| 366 | + mult_params1['dense_function'] = f'{namespace}::dense_resource_unrolled_{node.index}_1' |
| 367 | + |
| 368 | + mult_params2['n_in'] = node.get_attr(f'{d}_n_states') |
| 369 | + mult_params2['n_out'] = node.get_attr(f'{d}_n_states') * n_recr_mult |
| 370 | + mult_params2['product_type'] = get_backend('vivado').product_type( |
| 371 | + node.get_input_variable().type.precision, node.get_weights(f'{d}_recurrent_weight').type.precision |
| 372 | + ) |
| 373 | + mult_params2['reuse'] = node.attributes[f'{d}_recurrent_reuse_factor'] |
| 374 | + mult_params2['index'] = str(node.index) + f'_2_{d[0]}' |
| 375 | + mult_params2['nzeros'] = node.get_weights(f'{d}_recurrent_weight').nzeros |
| 376 | + mult_params2['nonzeros'] = node.get_weights(f'{d}_recurrent_weight').nonzeros |
| 377 | + |
| 378 | + if node.get_attr('strategy').lower() == 'latency': |
| 379 | + mult_params2['dense_function'] = 'nnet::DenseLatency' |
| 380 | + elif node.get_attr('strategy').lower() == 'resource': |
| 381 | + if int(mult_params2[f'{d}_reuse_factor']) <= int(mult_params2['n_in']): |
| 382 | + mult_params2['dense_function'] = 'nnet::DenseResource_rf_leq_nin' |
| 383 | + else: |
| 384 | + mult_params2['dense_function'] = 'nnet::DenseResource_rf_gt_nin_rem0' |
| 385 | + # The 3rd case is never used |
| 386 | + elif node.get_attr('strategy').lower() == 'resource_unrolled': |
| 387 | + mult_params2['dense_function'] = f'{namespace}::dense_resource_unrolled_{node.index}_2' |
| 388 | + |
| 389 | + mult_config1 = self.mult1_template.format(**mult_params1) |
| 390 | + mult_config2 = self.mult2_template.format(**mult_params2) |
| 391 | + |
| 392 | + result += ( |
| 393 | + mult_config1 + '\n' + mult_config2 + '\n' + recr_act_config + '\n' + act_config + '\n' + layer_config + '\n' |
| 394 | + ) |
| 395 | + |
| 396 | + return result + recr_config |
| 397 | + |
| 398 | + |
210 | 399 | class RecurrentFunctionTemplate(FunctionCallTemplate): |
211 | 400 | def __init__(self): |
212 | 401 | super().__init__((LSTM, GRU), include_header=recr_include_list) |
@@ -239,6 +428,29 @@ def format(self, node): |
239 | 428 | return template.format(**params) |
240 | 429 |
|
241 | 430 |
|
| 431 | +class BidirectionalFunctionTemplate(FunctionCallTemplate): |
| 432 | + def __init__(self): |
| 433 | + super().__init__((Bidirectional), include_header=recr_include_list) |
| 434 | + |
| 435 | + def format(self, node): |
| 436 | + params = self._default_function_params(node) |
| 437 | + |
| 438 | + # TO DO: Add initial states functions for pytorch settings |
| 439 | + |
| 440 | + params['w'] = node.get_weights('forward_weight').name |
| 441 | + params['b'] = node.get_weights('forward_bias').name |
| 442 | + params['wr'] = node.get_weights('forward_recurrent_weight').name |
| 443 | + params['br'] = node.get_weights('forward_recurrent_bias').name |
| 444 | + params['w_b'] = node.get_weights('backward_weight').name |
| 445 | + params['b_b'] = node.get_weights('backward_bias').name |
| 446 | + params['wr_b'] = node.get_weights('backward_recurrent_weight').name |
| 447 | + params['br_b'] = node.get_weights('backward_recurrent_bias').name |
| 448 | + |
| 449 | + template = bidirectional_function_template |
| 450 | + |
| 451 | + return template.format(**params) |
| 452 | + |
| 453 | + |
242 | 454 | time_distributed_config_template = """struct config{index} : nnet::time_distributed_config {{ |
243 | 455 | static const unsigned dim = {dim}; |
244 | 456 |
|
|
0 commit comments