1+ from math import ceil , log2
2+
13from hls4ml .backends .backend import get_backend
24from hls4ml .backends .template import FunctionCallTemplate , LayerConfigTemplate
35from hls4ml .model .layers import Activation , BatchNormalization , Dense , HardActivation , ParametrizedActivation , PReLU , Softmax
@@ -55,9 +57,17 @@ def format(self, node):
5557 # The 3rd case is never used
5658 elif node .get_attr ('strategy' ).lower () == 'resource_unrolled' :
5759 params ['dense_function' ] = f'{ namespace } ::dense_resource_unrolled_{ node .index } '
60+ elif node .get_attr ('strategy' ).lower () == 'distributed_arithmetic' :
61+ # Only triggered in io_streaming mode
62+ params ['dense_function' ] = f'{ namespace } ::dense_da_wrapper_{ node .index } '
5863
5964 return self .template .format (** params )
6065
66+ def match (self , node ):
67+ if node .get_attr ('strategy' ) == 'distributed_arithmetic' :
68+ return False # DA does not use common dense template
69+ return super ().match (node )
70+
6171
6272class DenseFunctionTemplate (FunctionCallTemplate ):
6373 def __init__ (self ):
@@ -71,6 +81,11 @@ def format(self, node):
7181
7282 return self .template .format (** params )
7383
84+ def match (self , node ):
85+ if node .get_attr ('strategy' ) == 'distributed_arithmetic' :
86+ return False # DA does not use common dense template
87+ return super ().match (node )
88+
7489
7590# BatchNormalization templates
7691
@@ -152,13 +167,22 @@ def format(self, node):
152167
153168softmax_config_template = """struct {type}_config{index} : nnet::activ_config {{
154169 static const unsigned n_in = {n_in};
155- static const unsigned table_size = {table_size};
170+ static const unsigned n_slice = {n_slice};
171+ static const unsigned n_outer = {n_outer};
172+ static const unsigned n_inner = {n_inner};
173+ static const unsigned parallelization_factor = {parallelization_factor};
174+ static const unsigned exp_table_size = {exp_table_size};
175+ static const unsigned inv_table_size = {inv_table_size};
156176 static const unsigned io_type = nnet::{iotype};
157177 static const unsigned reuse_factor = {reuse};
158178 static const unsigned axis = {axis};
159179 static const nnet::softmax_implementation implementation = nnet::softmax_implementation::{implementation};
180+ static constexpr float exp_scale = {exp_scale};
160181 typedef {exp_table_t.name} exp_table_t;
161182 typedef {inv_table_t.name} inv_table_t;
183+ typedef {accum_t.name} accum_t;
184+ typedef {inv_inp_t.name} inv_inp_t;
185+ typedef {inp_norm_t_str} inp_norm_t;
162186}};\n """
163187
164188activ_function_template = 'nnet::{activation}<{input_t}, {output_t}, {config}>({input}, {output});'
@@ -210,10 +234,66 @@ def __init__(self):
210234 super (ActivationConfigTemplate , self ).__init__ (Softmax ) # Skip ActivationConfigTemplate's __init__
211235 self .template = softmax_config_template
212236
237+ def format (self , node ):
238+ params = self ._default_config_params (node )
239+ params ['type' ] = node .get_attr ('activation' )
240+ params .setdefault ('exp_table_size' , params ['table_size' ])
241+ params .setdefault ('inv_table_size' , params ['table_size' ])
242+ params .setdefault ('n_inner' , 1 )
243+ params .setdefault ('n_outer' , 1 )
244+ params .setdefault ('exp_scale' , 1.0 )
245+ params .setdefault ('parallelization_factor' , - 1 )
246+
247+ n_slice = params ['n_in' ] // params ['n_inner' ] // params ['n_outer' ] # type: ignore
248+ params ['n_slice' ] = n_slice
249+
250+ if params ['accum_t' ].name == 'model_default_t' : # type: ignore
251+ scale = ceil (log2 (n_slice ))
252+ exp_table_t = node .attributes ['exp_table_t' ].precision
253+ signed , width , integers = exp_table_t .signed , exp_table_t .width , exp_table_t .integer
254+ params ['accum_t_str' ] = f'ap_{ "" if signed else "u" } fixed<{ width + scale } , { integers + scale } >'
255+ else :
256+ params ['accum_t_str' ] = params ['accum_t' ].name # type: ignore
257+ if params ['inv_inp_t' ].name == 'model_default_t' : # type: ignore
258+ params ['inv_inp_t' ] = params ['exp_table_t' ]
259+
260+ if params ['implementation' ] == 'stable' :
261+ if 'inp_norm_t' not in params :
262+ # Only used in stable (max-normalized) implementation
263+ input_t = node .get_input_variable ().type .precision
264+ width , iwidth , signed = input_t .width , input_t .integer , input_t .signed # noqa: F841
265+ width , iwidth = width - signed , iwidth - signed
266+ if signed :
267+ # Fix table size if too large
268+ exp_table_size = params ['inv_table_size' ]
269+ params ['exp_table_size' ] = str (min (int (exp_table_size ), 2 ** width ))
270+ params ['inp_norm_t_str' ] = f'ap_ufixed<{ width } , { iwidth } >'
271+ else :
272+ params ['inp_norm_t_str' ] = params ['inp_norm_t' ].name # type: ignore
273+ else :
274+ params ['inp_norm_t_str' ] = 'ap_fixed<1,0>'
275+
276+ return self .template .format (** params )
277+
278+
279+ class SoftmaxFunctionTemplate (FunctionCallTemplate ):
280+ def __init__ (self ):
281+ super ().__init__ (Softmax , include_header = activ_include_list )
282+ self .template = activ_function_template
283+
284+ def format (self , node ):
285+ params = self ._default_function_params (node )
286+ use_multidim = node .get_attr ('n_inner' , 1 ) > 1 or node .get_attr ('n_outer' , 1 ) > 1
287+ use_multidim = use_multidim and node .model .config .get_config_value ('IOType' ) == 'io_parallel'
288+ params ['activation' ] = 'softmax' if not use_multidim else 'softmax_multidim'
289+ params ['config' ] = f'softmax_config{ node .index } '
290+
291+ return self .template .format (** params )
292+
213293
214294class ActivationFunctionTemplate (FunctionCallTemplate ):
215295 def __init__ (self ):
216- super ().__init__ ((Activation , HardActivation , Softmax ), include_header = activ_include_list )
296+ super ().__init__ ((Activation , HardActivation ), include_header = activ_include_list )
217297 self .template = activ_function_template
218298
219299 def format (self , node ):
0 commit comments