diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst index 091f88e619..72d677d196 100644 --- a/docs/api/configuration.rst +++ b/docs/api/configuration.rst @@ -9,6 +9,7 @@ We currently support two ways of setting hls4ml's model configuration. This page .. contents:: \ +The Python API approach is recommended for most users as there are more utilities to help create the configuration dictionaries. **NOTE:** @@ -16,8 +17,10 @@ We currently support two ways of setting hls4ml's model configuration. This page * One important part of ``hls4ml`` to remember is that the user is responsible for the format of the inputs. There is no automatic formatting or normalization so this must be done in the training. -* +.. + * For developers, you might also want to checkout this section: `Detailed configuration in converted hls codes <#detailed-configuration-in-converted-hls-codes>`_. + *Broken link* ---- @@ -31,11 +34,26 @@ Using hls4ml, you can quickly generate a simple configuration dictionary from a import hls4ml config = hls4ml.utils.config_from_keras_model(model, granularity='model') -For more advanced and detailed configuration, you can also set them through the created dictionary. For example, to change the reuse factor: +This python dictionary can be edited as needed. A more advanced configuration can be generated by, for example: + +.. code-block:: python + + import hls4ml + config = hls4ml.utils.config_from_keras_model( + model, + granularity='name', + default_precision='fixed<16,6>', + backend='Vitis') + +This will include per-layer configuration based on the model. Including the backend is recommended because some configation options depend on the backend. Note, the precisions at the +higher granularites usually default to 'auto', which means that ``hls4ml`` will try to set it automatically. Note that higher granularity settings take precendence +over model-level settings. See :py:class:`~hls4ml.utils.config.config_from_keras_model` for more information on the various options. + +One can override specific values before using the configuration: .. code-block:: python - config['Model']['ReuseFactor'] = 2 + config['LayerName']['fc1']['ReuseFactor'] = 2 Or to set the precision of a specific layer's weight: @@ -45,6 +63,20 @@ Or to set the precision of a specific layer's weight: To better understand how the configuration hierachy works, refer to the next section for more details. +Finally, one then uses the configuration to create an hls model: + +.. code-block:: python + + hls_model = hls4ml.converters.convert_from_keras_model( + model, + hls_config=config, + output_dir="my_project_dir", + io_type='io_stream', + backend='Vitis' + ) + +See :py:class:`~hls4ml.converters.convert_from_keras_model` for more information on the various options. + ---- 2. YAML Configuration file diff --git a/docs/setup.rst b/docs/setup.rst index f99b2f2dcb..a735281c3f 100644 --- a/docs/setup.rst +++ b/docs/setup.rst @@ -57,7 +57,7 @@ To run FPGA synthesis, installation of following tools is required: * Xilinx Vivado HLS 2018.2 to 2020.1 for synthesis for Xilinx FPGAs - * Vitis HLS 2022.1 or newer is required for synthesis for Xilinx FPGAs using the experimental ``Vitis`` backend. + * Vitis HLS 2022.2 or newer is required for synthesis for Xilinx FPGAs using the ``Vitis`` backend. * Intel Quartus 20.1 to 21.4 for the synthesis for Intel FPGAs diff --git a/docs/status.rst b/docs/status.rst index e4cac5e735..4ff4d33282 100644 --- a/docs/status.rst +++ b/docs/status.rst @@ -81,7 +81,7 @@ Other feature notes: * ``hls4ml`` is tested on Linux, and supports * Vivado HLS versions 2018.2 to 2020.1 * Intel HLS versions 20.1 to 21.4 - * Vitis HLS versions 2020.2 to 2022.2 (experimentally) + * Vitis HLS versions 2022.2 to 2024.1 * Windows and macOS are not supported * BDT support has moved to the `Conifer `__ package diff --git a/hls4ml/backends/catapult/catapult_backend.py b/hls4ml/backends/catapult/catapult_backend.py index 0583e80dab..d939e1f30b 100644 --- a/hls4ml/backends/catapult/catapult_backend.py +++ b/hls4ml/backends/catapult/catapult_backend.py @@ -110,6 +110,7 @@ def _register_flows(self): 'catapult:inplace_stream_flatten', 'catapult:skip_softmax', 'catapult:fix_softmax_table_size', + 'infer_precision_types', ] optimization_flow = register_flow('optimize', optimization_passes, requires=[init_flow], backend=self.name) diff --git a/hls4ml/model/optimizer/__init__.py b/hls4ml/model/optimizer/__init__.py index de3dffb46c..77e38b0c5b 100644 --- a/hls4ml/model/optimizer/__init__.py +++ b/hls4ml/model/optimizer/__init__.py @@ -33,9 +33,8 @@ register_flow( 'convert', [ - 'seperable_to_depthwise_and_conv', # has to be before precision inference - 'infer_precision_types', 'channels_last_converter', + 'seperable_to_depthwise_and_conv', 'remove_transpose_before_flatten', 'remove_nop_transpose', 'remove_single_channel_transpose', @@ -45,19 +44,17 @@ 'qkeras_factorize_alpha', 'extract_ternary_threshold', 'fuse_consecutive_batch_normalization', + 'fuse_batch_normalization', 'replace_multidimensional_dense_with_conv', 'enforce_proxy_model_embedded_config', + 'eliminate_linear_activation', + # many of the above optimzers need to be done before this + 'infer_precision_types', ], ) # TODO Maybe not all QKeras optmizers belong here? register_flow( 'optimize', - [ - 'eliminate_linear_activation', - 'fuse_consecutive_batch_normalization', - 'fuse_batch_normalization', - 'infer_precision_types', - 'set_precision_concat', - ], + [], requires=['convert'], ) diff --git a/hls4ml/model/optimizer/passes/infer_precision.py b/hls4ml/model/optimizer/passes/infer_precision.py index 256e8a8152..bb24f2206e 100644 --- a/hls4ml/model/optimizer/passes/infer_precision.py +++ b/hls4ml/model/optimizer/passes/infer_precision.py @@ -1,9 +1,17 @@ import math +from typing import Iterable import numpy as np from hls4ml.model.optimizer import ConfigurableOptimizerPass -from hls4ml.model.types import FixedPrecisionType, UnspecifiedPrecisionType +from hls4ml.model.types import ( + FixedPrecisionType, + IntegerPrecisionType, + PrecisionType, + RoundingMode, + SaturationMode, + UnspecifiedPrecisionType, +) # TODO: The code assumes everything is Fixed or Integer precision. Need to add checks @@ -70,6 +78,12 @@ def _infer_precision(self, node, types_to_infer): if node_class in ['Dot']: return self._infer_dot_precision(node, types_to_infer) + if node_class in ['Embedding']: + return self._infer_embedding_precision(node, types_to_infer) + + if node_class in ['SimpleRNN', 'LSTM', 'GRU']: + return self._infer_rnn_precision(node, types_to_infer) + # What about quantized activation layer? Setting it to 'auto' manually will break it here. We should prevent # this in config_from_* functions @@ -79,6 +93,20 @@ def _get_default_precision(self, node): model_config = node.model.config return model_config.backend.convert_precision_string(model_config.model_precision['default']) + def _get_maximum_precision(self, node): + model_config = node.model.config + if 'maximum' in model_config.model_precision: + return model_config.backend.convert_precision_string(model_config.model_precision['maximum']) + else: + return None + + def _all_supported_types(self, types: Iterable[PrecisionType]): + """Are all the types supported for inference--currently Integer or Fixed""" + for tp in types: + if not isinstance(tp, (IntegerPrecisionType, FixedPrecisionType)): + return False + return True + def _infer_default_type(self, node, type_name): model_config = node.model.config default_precision = model_config.backend.convert_precision_string(model_config.model_precision['default']) @@ -99,9 +127,6 @@ def _infer_common_precision(self, node, types_to_infer, n_ops): inferred_types = [] input_precision = node.get_input_variable().type.precision - input_width = input_precision.width - input_integers = input_precision.integer - input_signed = input_precision.signed if 'weight_t' in types_to_infer: weight_quantizer = node.get_attr('weight_quantizer', None) @@ -113,10 +138,6 @@ def _infer_common_precision(self, node, types_to_infer, n_ops): node.weights['weight'].update_precision(node.types['weight_t'].precision) inferred_types.append('weight_t') - weight_width = node.types['weight_t'].precision.width - weight_integers = node.types['weight_t'].precision.integer - weight_signed = node.types['weight_t'].precision.signed - if 'bias_t' in types_to_infer: bias_quantizer = node.get_attr('bias_quantizer', None) if bias_quantizer is not None: @@ -127,25 +148,42 @@ def _infer_common_precision(self, node, types_to_infer, n_ops): node.weights['bias'].update_precision(node.types['bias_t'].precision) inferred_types.append('bias_t') - bias_width = node.types['bias_t'].precision.width - bias_integers = node.types['bias_t'].precision.integer - bias_signed = node.types['bias_t'].precision.signed - no_bias = node.weights['bias'].nonzeros == 0 and self.infer_no_bias # no bias + if self._all_supported_types((input_precision, node.types['weight_t'].precision, node.types['bias_t'].precision)): + input_width = input_precision.width + input_integers = input_precision.integer + input_signed = input_precision.signed - # using math.ceil instead of np.ceil because it returns an int - bitwidth = weight_width + input_width + math.ceil(np.log2(n_ops)) - integers = weight_integers + input_integers + math.ceil(np.log2(n_ops)) - signed = weight_signed or input_signed + weight_width = node.types['weight_t'].precision.width + weight_integers = node.types['weight_t'].precision.integer + weight_signed = node.types['weight_t'].precision.signed - frac = bitwidth - integers + bias_width = node.types['bias_t'].precision.width + bias_integers = node.types['bias_t'].precision.integer + bias_signed = node.types['bias_t'].precision.signed + no_bias = node.weights['bias'].nonzeros == 0 and self.infer_no_bias # no bias + + # using math.ceil instead of np.ceil because it returns an int + bitwidth = weight_width + input_width + math.ceil(np.log2(n_ops)) + integers = weight_integers + input_integers + math.ceil(np.log2(n_ops)) + signed = weight_signed or input_signed + + frac = bitwidth - integers - if not no_bias: - integers = max(integers + (bias_signed and not signed), bias_integers + (signed and not bias_signed)) + 1 - bitwidth = integers + max(frac, bias_width - bias_integers) - signed = signed or bias_signed + if not no_bias: + integers = max(integers + (bias_signed and not signed), bias_integers + (signed and not bias_signed)) + 1 + bitwidth = integers + max(frac, bias_width - bias_integers) + signed = signed or bias_signed - # Note: this is guaranteed to not overflow or need rounding, so it's sufficient to use the simpler form. - new_type = FixedPrecisionType(bitwidth, integers, signed) + # if max_precision is specified, limit the size to be less than max precisoin + max_precision = self._get_maximum_precision(node) + if max_precision is not None: + bitwidth = min(bitwidth, max_precision.width) + integers = min(integers, max_precision.integer) + + # Note: this is guaranteed to not overflow or need rounding, so it's sufficient to use the simpler form. + new_type = FixedPrecisionType(bitwidth, integers, signed) + else: + new_type = self._get_default_precision(node) if 'accum_t' in types_to_infer: node.types['accum_t'].name = node.name + '_accum_t' @@ -173,6 +211,8 @@ def _infer_depthconv_precision(self, node, types_to_infer): n_ops = node.get_attr('filt_height', 1) * node.get_attr('filt_width') return self._infer_common_precision(node, types_to_infer, n_ops) + # This function should generally not be called because we split sepconv to depthwise and regular (pointwise). + # It has not been updated. def _infer_sepconv_precision(self, node, types_to_infer): inferred_types = [] @@ -272,24 +312,35 @@ def _infer_bn_precision(self, node, types_to_infer): scale_precision = node.types['scale_t'].precision bias_precision = node.types['bias_t'].precision - after_scale_signed = scale_precision.signed or input_precision.signed - after_scale_width = input_precision.width + scale_precision.width - after_scale_integer = input_precision.integer + scale_precision.integer + if self._all_supported_types((input_precision, scale_precision, bias_precision)): + + after_scale_signed = scale_precision.signed or input_precision.signed + after_scale_width = input_precision.width + scale_precision.width + after_scale_integer = input_precision.integer + scale_precision.integer - out_precision_signed = after_scale_signed or bias_precision.signed - out_precision_integer = ( - max( - after_scale_integer + (bias_precision.signed and not after_scale_signed), - bias_precision.integer + (after_scale_signed and not bias_precision.signed), + out_precision_signed = after_scale_signed or bias_precision.signed + out_precision_integer = ( + max( + after_scale_integer + (bias_precision.signed and not after_scale_signed), + bias_precision.integer + (after_scale_signed and not bias_precision.signed), + ) + + 1 + ) + out_precision_width = out_precision_integer + max( + after_scale_width - after_scale_integer, bias_precision.fractional ) - + 1 - ) - out_precision_width = out_precision_integer + max( - after_scale_width - after_scale_integer, bias_precision.fractional - ) - # Note: this is guaranteed to not overflow or need rounding, so it's sufficient to use the simpler form. - out_precision = FixedPrecisionType(out_precision_width, out_precision_integer, out_precision_signed) + # if max_precision is specified, limit the size to be less than max precisoin + max_precision = self._get_maximum_precision(node) + if max_precision is not None: + out_precision_width = min(out_precision_width, max_precision.width) + out_precision_integer = min(out_precision_integer, max_precision.integer) + + # Note: this is guaranteed to not overflow or need rounding, so it's sufficient to use the simpler form. + out_precision = FixedPrecisionType(out_precision_width, out_precision_integer, out_precision_signed) + + else: + out_precision = self._get_default_precision(node) node.types['result_t'].name = node.name + '_result_t' node.types['result_t'].precision = out_precision @@ -305,20 +356,29 @@ def _infer_pooling_precision(self, node, types_to_infer): input_precision = node.get_input_variable().type.precision pool_op = node.attributes['pool_op'].lower() - width = input_precision.width - integer = input_precision.integer - signed = input_precision.signed + if pool_op == 'max': + # This has the benefit of working for xnor types. I don't think "copy" is needed + accum_type = input_precision + + elif pool_op == 'average': + if self._all_supported_types((input_precision,)): + width = input_precision.width + integer = input_precision.integer + signed = input_precision.signed + + pool_size = node.get_attr('pool_height', 1) * node.get_attr('pool_width') + extra_bits = int(np.ceil(np.log2(pool_size))) + + # for now ignore max precision in this case + accum_type = FixedPrecisionType( + width=width + extra_bits * 2, integer=integer + extra_bits, signed=signed + ) + else: + accum_type = self._get_default_precision(node) - pool_size = node.get_attr('pool_height', 1) * node.get_attr('pool_width') - if pool_op == 'average': - extra_bits = int(np.ceil(np.log2(pool_size))) - elif pool_op == 'max': - extra_bits = 0 else: raise ValueError(f'Unknown pooling operation: {pool_op}') - accum_type = FixedPrecisionType(width=width + extra_bits * 2, integer=integer + extra_bits, signed=signed) - node.types['accum_t'].name = node.name + '_accum_t' node.types['accum_t'].precision = accum_type @@ -338,22 +398,76 @@ def _infer_merge_precision(self, node, types_to_infer): op = node.get_attr('op').lower() if op in ('add', 'subtract', 'average'): - new_signed = input_1.signed or input_2.signed or op == 'subtract' - new_int = ( - max( - input_1.integer + (input_2.signed and not input_1.signed), - input_2.integer + (input_1.signed and not input_2.signed), + if self._all_supported_types((input_1, input_2)): + new_signed = input_1.signed or input_2.signed or op == 'subtract' + new_int = ( + max( + input_1.integer + (input_2.signed and not input_1.signed), + input_2.integer + (input_1.signed and not input_2.signed), + ) + + 1 ) - + 1 - ) - new_width = new_int + max(input_1.fractional, input_2.fractional) - out_precision = FixedPrecisionType(new_width, new_int, new_signed) + new_width = new_int + max(input_1.fractional, input_2.fractional) + max_precision = self._get_maximum_precision(node) + if max_precision is not None: + new_width = min(new_width, max_precision.width) + new_int = min(new_int, max_precision.integer) + out_precision = FixedPrecisionType(new_width, new_int, new_signed) + else: + out_precision = self._get_default_precision(node) elif op == 'multiply': - new_signed = input_1.signed or input_2.signed - new_int = input_1.integer + input_2.integer - new_width = input_1.width + input_2.width - out_precision = FixedPrecisionType(new_width, new_int, new_signed) + if self._all_supported_types((input_1, input_2)): + new_signed = input_1.signed or input_2.signed + new_int = input_1.integer + input_2.integer + new_width = input_1.width + input_2.width + # if max_precision is specified, limit the size to be less than max precisoin + max_precision = self._get_maximum_precision(node) + if max_precision is not None: + new_width = min(new_width, max_precision.width) + new_int = min(new_int, max_precision.integer) + out_precision = FixedPrecisionType(new_width, new_int, new_signed) + else: + out_precision = self._get_default_precision(node) elif op in ('maximum', 'minimum'): + if input_1 == input_2: + # can handle binary and potentially others + out_precision = input_1 # I assume copy is not necessary + elif self._all_supported_types((input_1, input_2)): + new_signed = input_1.signed or input_2.signed + + input_1_integer = input_1.integer + input_2_integer = input_2.integer + + # add one to integer if unsigned while new is signed + if new_signed and not input_1.signed: + input_1_integer += 1 + if new_signed and not input_2.signed: + input_2_integer += 1 + + new_width = max(input_1.fractional, input_2.fractional) + max(input_1_integer, input_2_integer) + new_int = max(input_1_integer, input_2_integer) + out_precision = FixedPrecisionType(new_width, new_int, new_signed) + else: + out_precision = self._get_default_precision(node) + else: + print(f'Warning: not propagating weights for type {op}') + out_precision = self._get_default_precision(node) + + node.types['result_t'].name = node.name + '_result_t' + node.types['result_t'].precision = out_precision + + return ['result_t'] + + def _infer_cat_precision(self, node, types_to_infer): + assert 'result_t' in types_to_infer and len(types_to_infer) == 1 + + input_1 = node.get_input_variable(node.inputs[0]).type.precision + input_2 = node.get_input_variable(node.inputs[1]).type.precision + + if input_1 == input_2: + # can handle binary and potentially others + out_precision = input_1 # I assume copy is not necessary + elif self._all_supported_types((input_1, input_2)): new_signed = input_1.signed or input_2.signed input_1_integer = input_1.integer @@ -367,9 +481,20 @@ def _infer_merge_precision(self, node, types_to_infer): new_width = max(input_1.fractional, input_2.fractional) + max(input_1_integer, input_2_integer) new_int = max(input_1_integer, input_2_integer) - out_precision = FixedPrecisionType(new_width, new_int, new_signed) + + # if max_precision is specified, limit the size to be less than max precisoin + max_precision = self._get_maximum_precision(node) + if max_precision is not None: + new_width = min(new_width, max_precision.width) + new_int = min(new_int, max_precision.integer) + + # some logic copied from former SetPrecisionConcat optimizer + newrmode = input_1.rounding_mode if input_1.rounding_mode != RoundingMode.TRN else input_2.rounding_mode + newsmode = input_1.saturation_mode if input_1.saturation_mode != SaturationMode.WRAP else input_2.saturation_mode + newsbits = input_1.saturation_bits if input_1.saturation_bits != 0 else input_2.saturation_bits + + out_precision = FixedPrecisionType(new_width, new_int, new_signed, newrmode, newsmode, newsbits) else: - print(f'Warning: not propagating weights for type {op}') out_precision = self._get_default_precision(node) node.types['result_t'].name = node.name + '_result_t' @@ -377,46 +502,58 @@ def _infer_merge_precision(self, node, types_to_infer): return ['result_t'] - def _infer_cat_precision(self, node, types_to_infer): + def _infer_dot_precision(self, node, types_to_infer): assert 'result_t' in types_to_infer and len(types_to_infer) == 1 input_1 = node.get_input_variable(node.inputs[0]).type.precision input_2 = node.get_input_variable(node.inputs[1]).type.precision - new_signed = input_1.signed or input_2.signed - - input_1_integer = input_1.integer - input_2_integer = input_2.integer + if self._all_supported_types((input_1, input_2)): + n_in = node.get_input_variable(node.inputs[0]).shape[0] - # add one to integer if unsigned while new is signed - if new_signed and not input_1.signed: - input_1_integer += 1 - if new_signed and not input_2.signed: - input_2_integer += 1 + new_signed = input_1.signed or input_2.signed + new_width = input_1.width + input_2.width + math.ceil(np.log2(n_in)) + new_int = input_1.integer + input_2.integer + math.ceil(np.log2(n_in)) - new_width = max(input_1.fractional, input_2.fractional) + max(input_1_integer, input_2_integer) - new_int = max(input_1_integer, input_2_integer) + # if max_precision is specified, limit the size to be less than max precisoin + max_precision = self._get_maximum_precision(node) + if max_precision is not None: + new_width = min(new_width, max_precision.width) + new_int = min(new_int, max_precision.integer) - out_precision = FixedPrecisionType(new_width, new_int, new_signed) + out_precision = FixedPrecisionType(new_width, new_int, new_signed) + else: + out_precision = self._get_default_precision(node) node.types['result_t'].name = node.name + '_result_t' node.types['result_t'].precision = out_precision return ['result_t'] - def _infer_dot_precision(self, node, types_to_infer): - assert 'result_t' in types_to_infer and len(types_to_infer) == 1 + def _infer_embedding_precision(self, node, types_to_infer): + inferred_types = [] - input_1 = node.get_input_variable(node.inputs[0]).type.precision - input_2 = node.get_input_variable(node.inputs[1]).type.precision + if 'embeddings_t' in types_to_infer: + self._infer_default_type(node, 'embeddings_t') + node.weights['embeddings'].update_precision(node.types['embeddings_t'].precision) + inferred_types.append('embeddings_t') - n_in = node.get_input_variable(node.inputs[0]).shape[0] + if 'result_t' in types_to_infer: + out_precision = self._get_default_precision(node) + node.types['result_t'].name = node.name + '_result_t' + node.types['result_t'].precision = out_precision + inferred_types.append('result_t') - new_signed = input_1.signed or input_2.signed - new_width = input_1.width + input_2.width + math.ceil(np.log2(n_in)) - new_int = input_1.integer + input_2.integer + math.ceil(np.log2(n_in)) + return inferred_types - out_precision = FixedPrecisionType(new_width, new_int, new_signed) - node.types['result_t'].name = node.name + '_result_t' - node.types['result_t'].precision = out_precision + # TODO: This is just a placeholder + def _infer_rnn_precision(self, node, types_to_infer): + inferred_types = [] - return ['result_t'] + # for now just do the weights and leave the rest for the default catch + for weightvar in ('weight', 'bias', 'recurrent_weight', 'recurrent_bias'): + if f'{weightvar}_t' in types_to_infer: + self._infer_default_type(node, f'{weightvar}_t') + node.weights[weightvar].update_precision(node.types[f'{weightvar}_t'].precision) + inferred_types.append(f'{weightvar}_t') + + return inferred_types diff --git a/hls4ml/model/optimizer/passes/precision_merge.py b/hls4ml/model/optimizer/passes/precision_merge.py deleted file mode 100644 index 9e79b11000..0000000000 --- a/hls4ml/model/optimizer/passes/precision_merge.py +++ /dev/null @@ -1,40 +0,0 @@ -from hls4ml.model.optimizer import OptimizerPass -from hls4ml.model.types import FixedPrecisionType, RoundingMode, SaturationMode - - -def get_concat_type(itype1, itype2): - newwidth = max(itype1.width, itype2.width) - newint = max(itype1.integer, itype2.integer) - if itype1.signed ^ itype2.signed: # XOR - newint += 1 - newwidth += 1 - newrmode = itype1.rounding_mode if itype1.rounding_mode != RoundingMode.TRN else itype2.rounding_mode - newsmode = itype1.saturation_mode if itype1.saturation_mode != SaturationMode.WRAP else itype2.saturation_mode - newsbits = itype1.saturation_bits if itype1.saturation_bits != 0 else itype2.saturation_bits - - newtype = FixedPrecisionType(newwidth, newint, itype1.signed or itype2.signed, newrmode, newsmode, newsbits) - return newtype - - -class SetPrecisionConcat(OptimizerPass): - def match(self, node): - if node.__class__.__name__ == 'Concatenate': - otype = node.get_output_variable().type.precision - itype1 = node.get_input_variable(node.inputs[0]).type.precision - itype2 = node.get_input_variable(node.inputs[1]).type.precision - if isinstance(otype, FixedPrecisionType) and otype != get_concat_type(itype1, itype2): - return True - return False - - def transform(self, model, node): - """ - Set concat output precision - """ - otype = node.get_output_variable().type.precision - itype1 = node.get_input_variable(node.inputs[0]).type.precision - itype2 = node.get_input_variable(node.inputs[1]).type.precision - newtype = get_concat_type(itype1, itype2) - print(f"Found {node.name} in the model, optimizing {otype} to {newtype}...") - node.get_output_variable().type.precision = newtype - - return True diff --git a/hls4ml/utils/config.py b/hls4ml/utils/config.py index 6cba033de2..5cd17d02e9 100644 --- a/hls4ml/utils/config.py +++ b/hls4ml/utils/config.py @@ -112,7 +112,7 @@ def _get_precision_from_quantizer(quantizer): def config_from_keras_model( - model, granularity='model', backend=None, default_precision='fixed<16,6>', default_reuse_factor=1 + model, granularity='model', backend=None, default_precision='fixed<16,6>', default_reuse_factor=1, max_precision=None ): """Create an HLS conversion config given the Keras model. @@ -132,8 +132,11 @@ def config_from_keras_model( will generate config keys for every layer separately, allowing for highly specific configuration tweaks. backend(str, optional): Name of the backend to use - default_precision (str, optional): Default precision to use. Defaults to 'fixed<16,6>'. + default_precision (str, optional): Default precision to use. Defaults to 'fixed<16,6>'. Note, this must + be an explicit precision: 'auto' is not allowed. default_reuse_factor (int, optional): Default reuse factor. Defaults to 1. + max_precision (str or None, optional): Maximum width precision to use. Defaults to None, meaning no maximum. + Note: Only integer and fixed precisions are supported Raises: Exception: If Keras model has layers not supported by hls4ml. @@ -182,9 +185,11 @@ def make_layer_config(layer): if name.endswith('_t'): name = name[:-2] if attr.default is None: - precision_cfg[name] = default_precision + precision_cfg[name] = 'auto' else: precision_cfg[name] = str(attr.default) + elif attr.name == 'reuse_factor': + layer_config[attr.config_name] = default_reuse_factor else: if attr.default is not None: layer_config[attr.config_name] = attr.default @@ -238,7 +243,10 @@ def make_layer_config(layer): config = {} model_config = {} - model_config['Precision'] = default_precision + model_config['Precision'] = {} + model_config['Precision']['default'] = default_precision + if max_precision is not None: + model_config['Precision']['maximum'] = max_precision model_config['ReuseFactor'] = default_reuse_factor model_config['Strategy'] = 'Latency' model_config['BramFactor'] = 1_000_000_000 diff --git a/test/pytest/test_batchnorm.py b/test/pytest/test_batchnorm.py index 727d2ee574..15774fa395 100644 --- a/test/pytest/test_batchnorm.py +++ b/test/pytest/test_batchnorm.py @@ -36,7 +36,9 @@ def test_batchnorm(model, data, backend, io_type): center = model.layers[0].center scale = model.layers[0].scale - config = hls4ml.utils.config_from_keras_model(model, default_precision=default_precision, granularity='name') + config = hls4ml.utils.config_from_keras_model( + model, default_precision=default_precision, granularity='name', backend=backend + ) output_dir = str(test_root_path / f'hls4mlprj_batchnorm_{backend}_{io_type}_center{center}_scale{scale}') hls_model = hls4ml.converters.convert_from_keras_model( model, backend=backend, hls_config=config, io_type=io_type, output_dir=output_dir diff --git a/test/pytest/test_batchnorm_pytorch.py b/test/pytest/test_batchnorm_pytorch.py index fd4efdf326..137aee8a1e 100644 --- a/test/pytest/test_batchnorm_pytorch.py +++ b/test/pytest/test_batchnorm_pytorch.py @@ -40,7 +40,7 @@ def test_batchnorm(data, backend, io_type): default_precision = 'ac_fixed<32, 1, true>' if backend == 'Quartus' else 'ac_fixed<32, 1>' config = hls4ml.utils.config_from_pytorch_model( - model, (in_shape,), default_precision=default_precision, granularity='name' + model, (in_shape,), default_precision=default_precision, granularity='name', backend=backend ) output_dir = str(test_root_path / f'hls4mlprj_batchnorm_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_pytorch_model( diff --git a/test/pytest/test_binary_cnn.py b/test/pytest/test_binary_cnn.py index 40af056df9..c1fa1b1551 100644 --- a/test/pytest/test_binary_cnn.py +++ b/test/pytest/test_binary_cnn.py @@ -66,7 +66,9 @@ def test_binary_cnn(backend, io_type, strategy): model2.summary() - hls_config = hls4ml.utils.config_from_keras_model(model2, granularity='name', default_precision='fixed<32,12>') + hls_config = hls4ml.utils.config_from_keras_model( + model2, granularity='name', default_precision='fixed<32,12>', backend=backend + ) hls_config['Model']['Strategy'] = strategy # hls_config['LayerName']['q_dense_7_softmax']['Implementation'] = 'legacy' diff --git a/test/pytest/test_causalpadding.py b/test/pytest/test_causalpadding.py index c076c99987..d91da35fac 100644 --- a/test/pytest/test_causalpadding.py +++ b/test/pytest/test_causalpadding.py @@ -23,7 +23,9 @@ def test_causalpadding(io_type, backend): data = np.expand_dims(data, axis=0) data = np.expand_dims(data, axis=-1) - config = hls4ml.utils.config_from_keras_model(model, default_precision='ap_fixed<32,16>', granularity='name') + config = hls4ml.utils.config_from_keras_model( + model, default_precision='ap_fixed<32,16>', granularity='name', backend=backend + ) odir = str(test_root_path / f'hls4mlprj_validpadding_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_keras_model( model, hls_config=config, io_type=io_type, output_dir=odir, backend=backend diff --git a/test/pytest/test_clone_flatten.py b/test/pytest/test_clone_flatten.py index 5f631d027f..d819af54e7 100644 --- a/test/pytest/test_clone_flatten.py +++ b/test/pytest/test_clone_flatten.py @@ -31,9 +31,7 @@ def keras_model(): @pytest.mark.parametrize('backend', ['Vivado', 'Quartus', 'Catapult']) def hls_model(keras_model, backend, io_type): hls_config = hls4ml.utils.config_from_keras_model( - keras_model, - default_precision='ap_int<6>', - granularity='name', + keras_model, default_precision='ap_int<6>', granularity='name', backend=backend ) output_dir = str(test_root_path / f'hls4mlprj_clone_flatten_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_keras_model( diff --git a/test/pytest/test_cnn_mnist_qkeras.py b/test/pytest/test_cnn_mnist_qkeras.py index b4c28c70d1..38489b5865 100644 --- a/test/pytest/test_cnn_mnist_qkeras.py +++ b/test/pytest/test_cnn_mnist_qkeras.py @@ -58,7 +58,7 @@ def mnist_model(): ) def hls_model(mnist_model, backend, io_type, strategy): keras_model = mnist_model - hls_config = hls4ml.utils.config_from_keras_model(keras_model, granularity='name') + hls_config = hls4ml.utils.config_from_keras_model(keras_model, granularity='name', backend=backend) hls_config['Model']['Strategy'] = strategy hls_config['LayerName']['softmax']['Strategy'] = 'Stable' output_dir = str(test_root_path / f'hls4mlprj_cnn_mnist_qkeras_{backend}_{io_type}_{strategy}') diff --git a/test/pytest/test_embed.py b/test/pytest/test_embed.py index a27fc45b93..c045629a40 100644 --- a/test/pytest/test_embed.py +++ b/test/pytest/test_embed.py @@ -28,7 +28,9 @@ def keras_model(): @pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult']) @pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) def hls_model(keras_model, backend, io_type): - hls_config = hls4ml.utils.config_from_keras_model(keras_model, default_precision='ap_fixed<16,6>', granularity='name') + hls_config = hls4ml.utils.config_from_keras_model( + keras_model, default_precision='ap_fixed<16,6>', granularity='name', backend=backend + ) hls_config['LayerName']['embedding_input']['Precision']['result'] = 'ap_uint<4>' out_dir = str(test_root_path / 'hls4mlprj_embed_{}_{}').format(backend, io_type) hls_model = hls4ml.converters.convert_from_keras_model( diff --git a/test/pytest/test_garnet.py b/test/pytest/test_garnet.py index 67ddf77182..62bc82a8c0 100644 --- a/test/pytest/test_garnet.py +++ b/test/pytest/test_garnet.py @@ -33,7 +33,7 @@ def garnet_models(): model = Model(inputs=inputs, outputs=outputs) model.summary() - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend='Vivado') config['Model'] = {} config['Model']['ReuseFactor'] = 1 config['Model']['Strategy'] = 'Latency' @@ -68,7 +68,7 @@ def garnet_stack_models(): model = Model(inputs=inputs, outputs=outputs) model.summary() - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend='Vivado') config['Model'] = {} config['Model']['ReuseFactor'] = 1 config['Model']['Strategy'] = 'Latency' diff --git a/test/pytest/test_globalpooling.py b/test/pytest/test_globalpooling.py index b99f0d8212..d0b635595a 100644 --- a/test/pytest/test_globalpooling.py +++ b/test/pytest/test_globalpooling.py @@ -53,7 +53,9 @@ def keras_model_1d(request): def test_global_pool1d(backend, keras_model_1d, data_1d, io_type): model, model_type, keepdims = keras_model_1d - config = hls4ml.utils.config_from_keras_model(model, default_precision='ap_fixed<32,9>', granularity='name') + config = hls4ml.utils.config_from_keras_model( + model, default_precision='ap_fixed<32,9>', granularity='name', backend=backend + ) hls_model = hls4ml.converters.convert_from_keras_model( model, @@ -108,7 +110,9 @@ def keras_model_2d(request): def test_global_pool2d(backend, keras_model_2d, data_2d, io_type): model, model_type, keepdims = keras_model_2d - config = hls4ml.utils.config_from_keras_model(model, default_precision='ap_fixed<32,9>', granularity='name') + config = hls4ml.utils.config_from_keras_model( + model, default_precision='ap_fixed<32,9>', granularity='name', backend=backend + ) hls_model = hls4ml.converters.convert_from_keras_model( model, diff --git a/test/pytest/test_keras_api.py b/test/pytest/test_keras_api.py index b9f2d35f1a..6f00b2ec00 100644 --- a/test/pytest/test_keras_api.py +++ b/test/pytest/test_keras_api.py @@ -310,7 +310,9 @@ def test_depthwise2d(backend, io_type): model.add(DepthwiseConv2D(kernel_size=(3, 3), input_shape=(32, 32, 3))) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name', default_precision='fixed<32,12>') + config = hls4ml.utils.config_from_keras_model( + model, granularity='name', default_precision='fixed<32,12>', backend=backend + ) output_dir = str(test_root_path / f'hls4mlprj_keras_api_depthwiseconv2d_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_keras_model( model, hls_config=config, output_dir=output_dir, backend=backend, io_type=io_type @@ -336,7 +338,7 @@ def test_depthwise1d(backend, io_type): model.add(DepthwiseConv1D(kernel_size=3, input_shape=(32, 3))) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend=backend) output_dir = str(test_root_path / f'hls4mlprj_keras_api_depthwiseconv1d_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_keras_model( model, hls_config=config, output_dir=output_dir, backend=backend, io_type=io_type diff --git a/test/pytest/test_optimization/test_attributes.py b/test/pytest/test_optimization/test_attributes.py index 2669321e09..3ba8d08d14 100644 --- a/test/pytest/test_optimization/test_attributes.py +++ b/test/pytest/test_optimization/test_attributes.py @@ -38,6 +38,12 @@ def test_attributes(): cfg['Model']['Strategy'] = strategy cfg['LayerName']['dense']['ReuseFactor'] = 1 + # optimization doesn't yet support auto precision + for layer in cfg['LayerName'].values(): + for key, prec in layer['Precision'].items(): + if prec == 'auto': + layer['Precision'][key] = default_precision + # Verify correct information for every layer model_attributes = get_attributes_from_keras_model_and_hls4ml_config(model, cfg) assert len(model_attributes) == 4 diff --git a/test/pytest/test_pointwiseconv.py b/test/pytest/test_pointwiseconv.py index 060b9877de..d7f9281b38 100644 --- a/test/pytest/test_pointwiseconv.py +++ b/test/pytest/test_pointwiseconv.py @@ -154,7 +154,7 @@ def test_pointwise_config(strategy): model.compile(optimizer='adam', loss='mse') - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend='Vivado') config['Model']['Strategy'] = strategy config['LayerName']['conv2d_1x1']['Strategy'] = strategy # Will fail if the strategy is not lowercase output_dir = str(test_root_path / f'hls4mlprj_pointwise2d_config_{strategy}') diff --git a/test/pytest/test_pooling.py b/test/pytest/test_pooling.py index d7de80a5a7..7a10cd2733 100644 --- a/test/pytest/test_pooling.py +++ b/test/pytest/test_pooling.py @@ -53,7 +53,9 @@ def keras_model_1d(request): def test_pool1d(backend, keras_model_1d, data_1d, io_type): model, model_type, padding = keras_model_1d - config = hls4ml.utils.config_from_keras_model(model, default_precision='ap_fixed<32,9>', granularity='name') + config = hls4ml.utils.config_from_keras_model( + model, default_precision='ap_fixed<32,9>', granularity='name', backend=backend + ) hls_model = hls4ml.converters.convert_from_keras_model( model, @@ -108,7 +110,9 @@ def keras_model_2d(request): def test_pool2d(backend, keras_model_2d, data_2d, io_type): model, model_type, padding = keras_model_2d - config = hls4ml.utils.config_from_keras_model(model, default_precision='ap_fixed<32,9>', granularity='name') + config = hls4ml.utils.config_from_keras_model( + model, default_precision='ap_fixed<32,9>', granularity='name', backend=backend + ) hls_model = hls4ml.converters.convert_from_keras_model( model, diff --git a/test/pytest/test_qkeras.py b/test/pytest/test_qkeras.py index 45d015807b..a6cdaabcac 100644 --- a/test/pytest/test_qkeras.py +++ b/test/pytest/test_qkeras.py @@ -77,7 +77,7 @@ def convert(load_jettagging_model, strategy): ''' model = load_jettagging_model - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend='Vivado') config['Model']['Strategy'] = strategy config['LayerName']['softmax']['exp_table_t'] = 'ap_fixed<18,8>' config['LayerName']['softmax']['inv_table_t'] = 'ap_fixed<18,4>' @@ -156,7 +156,7 @@ def test_single_dense_activation_exact(randX_100_16, bits, alpha, backend, io_ty model.add(QActivation(activation=quantized_relu(bits, 0), name='relu1')) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend=backend) output_dir = str(test_root_path / f'hls4mlprj_qkeras_single_dense_activation_exact_{bits}_{alpha}_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_keras_model( model, hls_config=config, output_dir=output_dir, backend=backend, io_type=io_type @@ -205,7 +205,7 @@ def test_quantizer_special(randX_1000_1, quantizer, backend, io_type): model.add(QActivation(input_shape=(1,), activation=quantizer, name='quantizer')) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend=backend) output_dir = str( test_root_path / f'hls4mlprj_qkeras_quantizer_{quantizer.__class__.__name__}_{quantizer.bits}_{backend}_{io_type}' ) @@ -289,7 +289,7 @@ def test_quantizer(randX_1000_1, quantizer, backend, io_type): model.add(QActivation(input_shape=(1,), activation=quantizer, name='quantizer')) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend=backend) output_dir = str( test_root_path / 'hls4mlprj_qkeras_quantizer_{}_{}_{}_{}_{}'.format( @@ -328,7 +328,7 @@ def test_relu_negative_slope(randX_1000_1, quantizer, backend, io_type): model.add(QActivation(input_shape=(1,), activation=quantizer, name='quantizer')) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend=backend) output_dir = str( test_root_path / 'hls4mlprj_qkeras_leaky_relu_{}_{}_neg_slope_{}_{}_{}'.format( @@ -373,7 +373,7 @@ def test_qactivation_kwarg(randX_100_10, activation_quantizer, weight_quantizer) )(inputs) model = Model(inputs, outputs) - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend='Vivado') out_dir = str(test_root_path / f'hls4mlprj_qactivation_kwarg_{activation_quantizer}') @@ -418,7 +418,9 @@ def test_quantizer_parsing(randX_100_10, backend, io_type): ) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name', default_precision='fixed<24,8>') + config = hls4ml.utils.config_from_keras_model( + model, granularity='name', default_precision='fixed<24,8>', backend=backend + ) output_dir = str(test_root_path / f'hls4mlprj_qkeras_quant_parse_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_keras_model( model, hls_config=config, output_dir=output_dir, backend=backend, io_type=io_type @@ -459,7 +461,9 @@ def test_qconv2dbn(randX_100_8_8_1, backend, io_type): ) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name', default_precision='fixed<24,8>') + config = hls4ml.utils.config_from_keras_model( + model, granularity='name', default_precision='fixed<24,8>', backend=backend + ) output_dir = str(test_root_path / f'hls4mlprj_qkeras_qconv2dbn_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_keras_model( model, hls_config=config, output_dir=output_dir, backend=backend, io_type=io_type @@ -500,7 +504,9 @@ def test_qdepthwiseconv2d(randX_10_32_32_3, backend, io_type): ) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name', default_precision='fixed<24,8>') + config = hls4ml.utils.config_from_keras_model( + model, granularity='name', default_precision='fixed<24,8>', backend=backend + ) output_dir = str(test_root_path / f'hls4mlprj_qkeras_qdepthwiseconv2d_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_keras_model( model, hls_config=config, output_dir=output_dir, backend=backend, io_type=io_type @@ -538,7 +544,7 @@ def test_quantised_po2_bit_width(backend, io_type, strategy): y_keras = keras_model.predict(X) hls_config = hls4ml.utils.config_from_keras_model( - keras_model, granularity='name', default_precision='ap_fixed<64, 32>', default_reuse_factor=1 + keras_model, granularity='name', default_precision='ap_fixed<64, 32>', default_reuse_factor=1, backend=backend ) hls_config['Model']['Strategy'] = strategy output_dir = str(test_root_path / f'hls4mlprj_qkeras_quantised_po2_{backend}_{io_type}_{strategy}') @@ -573,7 +579,9 @@ def test_qsimplernn(backend): ) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name', default_precision="ap_fixed<16,1>") + config = hls4ml.utils.config_from_keras_model( + model, granularity='name', default_precision="ap_fixed<16,1>", backend=backend + ) output_dir = str(test_root_path / f'hls4mlprj_qkeras_qsimplernn_{backend}') hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir, backend=backend) hls_model.compile() @@ -607,7 +615,9 @@ def test_qlstm(backend): ) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name', default_precision="ap_fixed<8,1>") + config = hls4ml.utils.config_from_keras_model( + model, granularity='name', default_precision="ap_fixed<8,1>", backend=backend + ) output_dir = str(test_root_path / f'hls4mlprj_qkeras_qsimplernn_{backend}') hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir, backend=backend) hls_model.compile() @@ -642,7 +652,9 @@ def test_qgru(backend): ) model.compile() - config = hls4ml.utils.config_from_keras_model(model, granularity='name', default_precision="ap_fixed<8,1>") + config = hls4ml.utils.config_from_keras_model( + model, granularity='name', default_precision="ap_fixed<8,1>", backend=backend + ) output_dir = str(test_root_path / f'hls4mlprj_qkeras_qsimplernn_{backend}') hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir, backend=backend) hls_model.compile() diff --git a/test/pytest/test_rnn.py b/test/pytest/test_rnn.py index 3e6e978011..dc991f7f55 100644 --- a/test/pytest/test_rnn.py +++ b/test/pytest/test_rnn.py @@ -25,7 +25,7 @@ def test_rnn_parsing(rnn_layer, return_sequences): model = Model(model_input, model_output) model.compile(optimizer='adam', loss='mse') - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend='Vivado') prj_name = f'hls4mlprj_rnn_{rnn_layer.__class__.__name__.lower()}_seq_{int(return_sequences)}' output_dir = str(test_root_path / prj_name) hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir) @@ -90,7 +90,7 @@ def test_rnn_accuracy(rnn_layer, return_sequences, backend, io_type, strategy, s input_shape = (12, 8) X = np.random.rand(50, *input_shape) - 0.5 - layer_name = rnn_layer.__class__.__name__.lower() + layer_name = rnn_layer.__name__ keras_model = Sequential() keras_model.add( rnn_layer( @@ -111,8 +111,9 @@ def test_rnn_accuracy(rnn_layer, return_sequences, backend, io_type, strategy, s ) hls_config['LayerName'][layer_name]['static'] = static hls_config['LayerName'][layer_name]['Strategy'] = strategy - prj_name = 'hls4mlprj_rnn_accuracy_{}_static_{}_ret_seq_{}_{}_{}_{}'.format( - rnn_layer.__class__.__name__.lower(), int(static), int(return_sequences), backend, io_type, strategy + prj_name = ( + f'hls4mlprj_rnn_accuracy_{layer_name}_static_{int(static)}_ret_seq_{int(return_sequences)}_' + f'{backend}_{io_type}_{strategy}' ) output_dir = str(test_root_path / prj_name) diff --git a/test/pytest/test_softmax.py b/test/pytest/test_softmax.py index 19c9042465..048b6832ee 100644 --- a/test/pytest/test_softmax.py +++ b/test/pytest/test_softmax.py @@ -41,7 +41,7 @@ def test_softmax(backend, strategy, generate_data, input_bits, input_shape, tabl table_type = f'fixed<{table_bits}, RND, SAT>' - cfg = hls4ml.utils.config_from_keras_model(model, granularity='name') + cfg = hls4ml.utils.config_from_keras_model(model, granularity='name', backend=backend) cfg['LayerName']['softmax']['Strategy'] = strategy cfg['LayerName']['softmax']['inv_table_t'] = table_type cfg['LayerName']['softmax']['exp_table_t'] = table_type @@ -74,7 +74,7 @@ def test_softmax_skipped(backend, io_type): model = tf.keras.models.Sequential([dense, softmax]) model.compile() - cfg = hls4ml.utils.config_from_keras_model(model, granularity='name') + cfg = hls4ml.utils.config_from_keras_model(model, granularity='name', backend=backend) cfg['LayerName']['softmax']['skip'] = True odir = str(test_root_path / 'hls4mlprj_softmax_skipped_{}_{}').format(backend, io_type) diff --git a/test/pytest/test_softsign.py b/test/pytest/test_softsign.py index 31a2a1c2cf..f0089438a4 100644 --- a/test/pytest/test_softsign.py +++ b/test/pytest/test_softsign.py @@ -19,7 +19,7 @@ def test_softsign(backend, input_shape, io_type): model.add(tf.keras.layers.Activation(input_shape=input_shape, activation='softsign', name='softsign')) model.compile() - cfg = hls4ml.utils.config_from_keras_model(model, granularity='name', default_precision='fixed<20,4>') + cfg = hls4ml.utils.config_from_keras_model(model, granularity='name', default_precision='fixed<20,4>', backend=backend) # Since softsign implementation is lookup-based increasing the precision and size of the table helps with accuracy cfg['LayerName']['softsign']['table_t'] = 'fixed<20,4>' cfg['LayerName']['softsign']['table_size'] = 2048 diff --git a/test/pytest/test_trace.py b/test/pytest/test_trace.py index 14e218fd1c..b01cfcd010 100644 --- a/test/pytest/test_trace.py +++ b/test/pytest/test_trace.py @@ -39,11 +39,11 @@ def test_trace(backend, activation): keras_prediction = model.predict(X_input) - config = hls4ml.utils.config_from_keras_model(model, granularity='name') + config = hls4ml.utils.config_from_keras_model(model, granularity='name', backend=backend) for layer in config['LayerName'].keys(): config['LayerName'][layer]['Trace'] = True - output_dir = str(test_root_path / f'hls4mlprj_trace_{backend}') + output_dir = str(test_root_path / f'hls4mlprj_trace_{backend}_{activation}') hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir, backend=backend) diff --git a/test/pytest/test_transpose_concat.py b/test/pytest/test_transpose_concat.py index db3e03125f..7447545d2f 100644 --- a/test/pytest/test_transpose_concat.py +++ b/test/pytest/test_transpose_concat.py @@ -32,7 +32,7 @@ def keras_model(): @pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus']) def hls_model(keras_model, backend, io_type): hls_config = hls4ml.utils.config_from_keras_model( - keras_model, default_precision='ap_fixed<16,3,AP_RND_CONV,AP_SAT>', granularity='name' + keras_model, default_precision='ap_fixed<16,3,AP_RND_CONV,AP_SAT>', granularity='name', backend=backend ) hls_config['LayerName']['relu']['Precision'] = 'ap_ufixed<17,3>' output_dir = str(test_root_path / f'hls4mlprj_transpose_{backend}_{io_type}') diff --git a/test/pytest/test_upsampling.py b/test/pytest/test_upsampling.py index 9051d582bd..c81be76933 100644 --- a/test/pytest/test_upsampling.py +++ b/test/pytest/test_upsampling.py @@ -56,7 +56,9 @@ def test_upsampling(keras_model_1d, keras_model_2d, data_1d, data_2d, model_type model = keras_model_2d data = data_2d - config = hls4ml.utils.config_from_keras_model(model, default_precision='ap_fixed<32,1>', granularity='name') + config = hls4ml.utils.config_from_keras_model( + model, default_precision='ap_fixed<32,1>', granularity='name', backend=backend + ) odir = str(test_root_path / f'hls4mlprj_upsampling_{model_type}_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_keras_model( model, hls_config=config, io_type=io_type, output_dir=odir, backend=backend diff --git a/test/pytest/test_zeropadding.py b/test/pytest/test_zeropadding.py index 95f7d79a7d..6a22a22472 100644 --- a/test/pytest/test_zeropadding.py +++ b/test/pytest/test_zeropadding.py @@ -60,7 +60,9 @@ def test_zeropadding(keras_model_1d, keras_model_2d, data_1d, data_2d, model_typ model = keras_model_2d data = data_2d - config = hls4ml.utils.config_from_keras_model(model, default_precision='ap_fixed<32,1>', granularity='name') + config = hls4ml.utils.config_from_keras_model( + model, default_precision='ap_fixed<32,1>', granularity='name', backend=backend + ) odir = str(test_root_path / f'hls4mlprj_zeropadding_{model_type}_{backend}_{io_type}') hls_model = hls4ml.converters.convert_from_keras_model( model, hls_config=config, io_type=io_type, output_dir=odir, backend=backend