|
| 1 | +from hls4ml.backends.backend import get_backend |
| 2 | +from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate |
| 3 | +from hls4ml.model.layers import EinsumDense |
| 4 | +from hls4ml.utils.transpose_utils import transpose_config_gen |
| 5 | + |
| 6 | +from .reshaping_templates import transpose_config_template |
| 7 | + |
| 8 | +# Shared Dense template |
| 9 | + |
| 10 | +dense_config_template = '''struct config{index}_dense : nnet::dense_config {{ |
| 11 | + static const unsigned n_in = {n_in}; |
| 12 | + static const unsigned n_out = {n_out}; |
| 13 | + static const unsigned reuse_factor = {reuse}; |
| 14 | + static const unsigned strategy = nnet::{strategy}; |
| 15 | + static const unsigned n_zeros = {nzeros}; |
| 16 | + static const unsigned multiplier_limit = DIV_ROUNDUP(n_in * n_out, reuse_factor) - n_zeros / reuse_factor; |
| 17 | + typedef {accum_t.name} accum_t; |
| 18 | + typedef {bias_t.name} bias_t; |
| 19 | + typedef {weight_t.name} weight_t; |
| 20 | + template<class data_T, class res_T, class CONFIG_T> |
| 21 | + using kernel = nnet::{dense_function}<data_T, res_T, CONFIG_T>; |
| 22 | + template<class x_T, class y_T> |
| 23 | + using product = nnet::product::{product_type}<x_T, y_T>; |
| 24 | +}};\n''' |
| 25 | + |
| 26 | +# EinsumDense template |
| 27 | + |
| 28 | +einsum_dense_config_template = ''' |
| 29 | +struct config{index} {{ |
| 30 | + typedef config{index}_tpose_inp tpose_inp_conf; |
| 31 | + typedef config{index}_tpose_out tpose_out_conf; |
| 32 | + {kernel_config}; |
| 33 | +
|
| 34 | + typedef {accum_t.name} accum_t; |
| 35 | + typedef {bias_t.name} bias_t; |
| 36 | +
|
| 37 | + // Layer Sizes |
| 38 | + static const unsigned n_free_data = {n_free_data}; |
| 39 | + static const unsigned n_free_kernel = {n_free_kernel}; |
| 40 | + static const unsigned n_contract = {n_contract}; |
| 41 | + static const unsigned n_inplace = {n_inplace}; |
| 42 | +
|
| 43 | + // Resource reuse info |
| 44 | + static const unsigned io_type = nnet::{iotype}; |
| 45 | + static const unsigned strategy = nnet::{strategy}; |
| 46 | + static const unsigned reuse_factor = {reuse_factor}; |
| 47 | + static const unsigned parallelization_factor = {parallelization_factor}; // Only useful when n_inplace > 1 |
| 48 | +}}; |
| 49 | +''' |
| 50 | + |
| 51 | +einsum_dense_function_template = 'nnet::einsum_dense<{input_t}, {output_t}, {config}>({input}, {output}, {w}, {b});' |
| 52 | +einsum_dense_da_function_template = 'nnet::einsum_dense<{input_t}, {output_t}, {config}>({input}, {output}, {b});' |
| 53 | + |
| 54 | +einsum_dense_include_list = ['nnet_utils/nnet_einsum_dense.h', 'nnet_utils/nnet_dense.h'] |
| 55 | + |
| 56 | + |
| 57 | +class EinsumDenseConfigTemplate(LayerConfigTemplate): |
| 58 | + def __init__(self): |
| 59 | + super().__init__(EinsumDense) |
| 60 | + self.template = einsum_dense_config_template |
| 61 | + self.dense_template = dense_config_template |
| 62 | + |
| 63 | + def dense_config(self, node: EinsumDense): |
| 64 | + dense_params = self._default_config_params(node) |
| 65 | + strategy = node.attributes['strategy'] |
| 66 | + dense_params['strategy'] = strategy |
| 67 | + dense_params['n_in'] = node.attributes['n_contract'] |
| 68 | + dense_params['n_out'] = node.attributes['n_free_kernel'] |
| 69 | + if node.attributes['n_inplace'] == 1: |
| 70 | + dense_params['nzeros'] = node.get_weights('weight').nzeros # type: ignore |
| 71 | + else: |
| 72 | + dense_params['nzeros'] = '-1; // Not making sense when kernels are switching' |
| 73 | + dense_params['product_type'] = get_backend('vivado').product_type( |
| 74 | + node.get_input_variable().type.precision, node.get_weights('weight').type.precision # type: ignore |
| 75 | + ) |
| 76 | + |
| 77 | + dense_params['dense_function'] = 'DenseLatency' # Latency only for now |
| 78 | + |
| 79 | + dense_config = self.dense_template.format(**dense_params) |
| 80 | + return dense_config |
| 81 | + |
| 82 | + def format(self, node: EinsumDense): |
| 83 | + default_params = self._default_config_params(node) |
| 84 | + |
| 85 | + strategy = node.attributes['strategy'] |
| 86 | + io_type = node.model.config.get_config_value('IOType') |
| 87 | + |
| 88 | + assert io_type == 'io_parallel', 'EinsumDense layer only supports io_parallel and distributed_arithmetic' |
| 89 | + |
| 90 | + # EinsumDense config |
| 91 | + params = default_params.copy() |
| 92 | + params['strategy'] = strategy |
| 93 | + params['n_free_data'] = node.attributes['n_free_data'] |
| 94 | + params['n_free_kernel'] = node.attributes['n_free_kernel'] |
| 95 | + params['n_contract'] = node.attributes['n_contract'] |
| 96 | + params['n_inplace'] = node.attributes['n_inplace'] |
| 97 | + if strategy.lower() == 'latency': |
| 98 | + params['kernel_config'] = f'typedef config{node.index}_dense dense_conf' |
| 99 | + else: |
| 100 | + assert strategy.lower() == 'distributed_arithmetic', 'EinsumDense layer only supports Latency strategy for now' |
| 101 | + inp_t = node.get_input_variable().type.name |
| 102 | + result_t = node.get_output_variable().type.name |
| 103 | + index = node.index |
| 104 | + conf = f'constexpr static auto da_kernel = nnet::einsum_dense{index}_da_kernel<{inp_t}, {result_t}>' |
| 105 | + params['kernel_config'] = conf |
| 106 | + pf = node.attributes['parallelization_factor'] |
| 107 | + if pf < 0: |
| 108 | + pf = params['n_inplace'] |
| 109 | + params['parallelization_factor'] = pf |
| 110 | + |
| 111 | + einsum_conf = self.template.format(**params) |
| 112 | + |
| 113 | + # inp/out transpose config |
| 114 | + inp_shape = node.attributes['inp_shape'] |
| 115 | + out_interpert_shape = node.attributes['out_interpert_shape'] |
| 116 | + inp_tpose_idxs = node.attributes['inp_tpose_idxs'] |
| 117 | + out_tpose_idxs = node.attributes['out_tpose_idxs'] |
| 118 | + tpose_inp_conf_name = f'config{node.index}_tpose_inp' |
| 119 | + tpose_out_conf_name = f'config{node.index}_tpose_out' |
| 120 | + |
| 121 | + conf = transpose_config_gen(tpose_inp_conf_name, inp_shape, inp_tpose_idxs) |
| 122 | + inp_tpose_conf = transpose_config_template.format(**conf) |
| 123 | + conf = transpose_config_gen(tpose_out_conf_name, out_interpert_shape, out_tpose_idxs) |
| 124 | + out_tpose_conf = transpose_config_template.format(**conf) |
| 125 | + |
| 126 | + if strategy.lower() == 'distributed_arithmetic': |
| 127 | + return '\n\n'.join((inp_tpose_conf, out_tpose_conf, einsum_conf)) |
| 128 | + |
| 129 | + dense_config = self.dense_config(node) |
| 130 | + return '\n\n'.join((inp_tpose_conf, out_tpose_conf, dense_config, einsum_conf)) |
| 131 | + |
| 132 | + |
| 133 | +class EinsumDenseFunctionTemplate(FunctionCallTemplate): |
| 134 | + def __init__(self): |
| 135 | + super().__init__(EinsumDense, include_header=einsum_dense_include_list) |
| 136 | + self.template = einsum_dense_function_template |
| 137 | + |
| 138 | + def format(self, node): |
| 139 | + params = self._default_function_params(node) |
| 140 | + params['b'] = node.get_weights('bias').name |
| 141 | + |
| 142 | + strategy = node.attributes['strategy'] |
| 143 | + if strategy == 'distributed_arithmetic': |
| 144 | + return einsum_dense_da_function_template.format(**params) |
| 145 | + |
| 146 | + params['w'] = node.get_weights('weight').name |
| 147 | + return einsum_dense_function_template.format(**params) |
0 commit comments