From 57366528887bd016910415f89bf7fcb095976af6 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Fri, 25 Oct 2024 17:42:19 -0700 Subject: [PATCH 01/16] import converter dependencies lazily --- hls4ml/converters/keras/qkeras.py | 4 ++-- hls4ml/converters/keras_to_hls.py | 4 ++-- hls4ml/converters/onnx_to_hls.py | 7 ++++--- hls4ml/converters/pytorch_to_hls.py | 6 ++++-- hls4ml/model/quantizers.py | 20 ++++++++++++-------- hls4ml/optimization/__init__.py | 4 +--- 6 files changed, 25 insertions(+), 20 deletions(-) diff --git a/hls4ml/converters/keras/qkeras.py b/hls4ml/converters/keras/qkeras.py index 7357d95aed..d1910c070d 100644 --- a/hls4ml/converters/keras/qkeras.py +++ b/hls4ml/converters/keras/qkeras.py @@ -1,5 +1,3 @@ -from qkeras.quantizers import get_quantizer - from hls4ml.converters.keras.convolution import parse_conv1d_layer, parse_conv2d_layer from hls4ml.converters.keras.core import parse_batchnorm_layer, parse_dense_layer from hls4ml.converters.keras.recurrent import parse_rnn_layer @@ -88,6 +86,8 @@ def parse_qrnn_layer(keras_layer, input_names, input_shapes, data_reader): @keras_handler('QActivation') def parse_qactivation_layer(keras_layer, input_names, input_shapes, data_reader): + from qkeras.quantizers import get_quantizer + assert keras_layer['class_name'] == 'QActivation' supported_activations = [ 'quantized_relu', diff --git a/hls4ml/converters/keras_to_hls.py b/hls4ml/converters/keras_to_hls.py index e31e2b96a9..9fc63cf398 100644 --- a/hls4ml/converters/keras_to_hls.py +++ b/hls4ml/converters/keras_to_hls.py @@ -160,9 +160,9 @@ def get_model_arch(config): # Model instance passed in config from API keras_model = config['KerasModel'] if isinstance(keras_model, str): - from tensorflow.keras.models import load_model + import keras - keras_model = load_model(keras_model) + keras_model = keras.models.load_model(keras_model) model_arch = json.loads(keras_model.to_json()) reader = KerasModelReader(keras_model) elif 'KerasJson' in config: diff --git a/hls4ml/converters/onnx_to_hls.py b/hls4ml/converters/onnx_to_hls.py index 75850fa93e..99281888f3 100644 --- a/hls4ml/converters/onnx_to_hls.py +++ b/hls4ml/converters/onnx_to_hls.py @@ -1,6 +1,3 @@ -import onnx -from onnx import helper, numpy_helper - from hls4ml.model import ModelGraph @@ -21,6 +18,8 @@ def replace_char_inconsitency(name): def get_onnx_attribute(operation, name, default=None): + from onnx import helper + attr = next((x for x in operation.attribute if x.name == name), None) if attr is None: value = default @@ -76,6 +75,7 @@ def get_input_shape(graph, node): def get_constant_value(graph, constant_name): tensor = next((x for x in graph.initializer if x.name == constant_name), None) + from onnx import numpy_helper return numpy_helper.to_array(tensor) @@ -273,6 +273,7 @@ def onnx_to_hls(config): # Extract model architecture print('Interpreting Model ...') + import onnx onnx_model = onnx.load(config['OnnxModel']) if isinstance(config['OnnxModel'], str) else config['OnnxModel'] layer_list, input_layers, output_layers = parse_onnx_model(onnx_model) diff --git a/hls4ml/converters/pytorch_to_hls.py b/hls4ml/converters/pytorch_to_hls.py index 871026bc49..91357a7540 100644 --- a/hls4ml/converters/pytorch_to_hls.py +++ b/hls4ml/converters/pytorch_to_hls.py @@ -1,5 +1,4 @@ import numpy as np -import torch from hls4ml.model import ModelGraph @@ -27,6 +26,8 @@ def get_weights_data(self, layer_name, var_name): class PyTorchFileReader(PyTorchModelReader): # Inherit get_weights_data method def __init__(self, config): + import torch + self.config = config if not torch.cuda.is_available(): @@ -116,6 +117,8 @@ def parse_pytorch_model(config, verbose=True): Returns: ModelGraph: hls4ml model object. """ + import torch + from torch.fx import symbolic_trace # This is a list of dictionaries to hold all the layer info we need to generate HLS layer_list = [] @@ -135,7 +138,6 @@ def parse_pytorch_model(config, verbose=True): # dict of layer objects in non-traced form for access lateron children = {c[0]: c[1] for c in model.named_children()} # use symbolic_trace to get a full graph of the model - from torch.fx import symbolic_trace traced_model = symbolic_trace(model) # Define layers to skip for conversion to HLS diff --git a/hls4ml/model/quantizers.py b/hls4ml/model/quantizers.py index a5b9ceb8c4..b445c70af3 100644 --- a/hls4ml/model/quantizers.py +++ b/hls4ml/model/quantizers.py @@ -5,8 +5,6 @@ """ import numpy as np -import tensorflow as tf -from qkeras.quantizers import get_quantizer from hls4ml.model.types import ( ExponentPrecisionType, @@ -87,6 +85,8 @@ class QKerasQuantizer(Quantizer): """ def __init__(self, config): + from qkeras.quantizers import get_quantizer + self.quantizer_fn = get_quantizer(config) self.alpha = config['config'].get('alpha', None) if config['class_name'] == 'quantized_bits': @@ -106,8 +106,8 @@ def __init__(self, config): self.hls_type = FixedPrecisionType(width=16, integer=6, signed=True) def __call__(self, data): - tf_data = tf.convert_to_tensor(data) - return self.quantizer_fn(tf_data).numpy() + data = np.array(data, dtype='float32') + return self.quantizer_fn(data).numpy() # return self.quantizer_fn(data) def _get_type(self, quantizer_config): @@ -132,6 +132,8 @@ class QKerasBinaryQuantizer(Quantizer): """ def __init__(self, config, xnor=False): + from qkeras.quantizers import get_quantizer + self.bits = 1 if xnor else 2 self.hls_type = XnorPrecisionType() if xnor else IntegerPrecisionType(width=2, signed=True) self.alpha = config['config']['alpha'] @@ -141,8 +143,8 @@ def __init__(self, config, xnor=False): self.binary_quantizer = BinaryQuantizer(1) if xnor else BinaryQuantizer(2) def __call__(self, data): - x = tf.convert_to_tensor(data) - y = self.quantizer_fn(x).numpy() + data = np.array(data, dtype='float32') + y = self.quantizer_fn(data).numpy() return self.binary_quantizer(y) @@ -154,14 +156,16 @@ class QKerasPO2Quantizer(Quantizer): """ def __init__(self, config): + from qkeras.quantizers import get_quantizer + self.bits = config['config']['bits'] self.quantizer_fn = get_quantizer(config) self.hls_type = ExponentPrecisionType(width=self.bits, signed=True) def __call__(self, data): # Weights are quantized to nearest power of two - x = tf.convert_to_tensor(data) - y = self.quantizer_fn(x) + data = np.array(data, dtype='float32') + y = self.quantizer_fn(data) if hasattr(y, 'numpy'): y = y.numpy() return y diff --git a/hls4ml/optimization/__init__.py b/hls4ml/optimization/__init__.py index c626b70c2b..2b49886e39 100644 --- a/hls4ml/optimization/__init__.py +++ b/hls4ml/optimization/__init__.py @@ -1,3 +1 @@ -from .dsp_aware_pruning import optimize_keras_model_for_hls4ml # noqa: F401 -from .dsp_aware_pruning.attributes import get_attributes_from_keras_model_and_hls4ml_config # noqa: F401 -from .dsp_aware_pruning.keras import optimize_model # noqa: F401 +# No imports as each of the optimization modules may contain different dependencies. From b3c632b71d482dc602b9c4d4b3f83276cf747804 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Fri, 25 Oct 2024 20:12:28 -0700 Subject: [PATCH 02/16] make tf and qkeras optionl, stop assuming keras is tf.keras --- hls4ml/converters/__init__.py | 6 ++--- hls4ml/model/optimizer/passes/qkeras.py | 3 ++- hls4ml/model/profiling.py | 26 ++++++++++++++----- .../dsp_aware_pruning/keras/__init__.py | 4 --- hls4ml/utils/config.py | 4 +-- hls4ml/writer/catapult_writer.py | 4 ++- hls4ml/writer/quartus_writer.py | 4 ++- hls4ml/writer/vivado_writer.py | 4 ++- 8 files changed, 35 insertions(+), 20 deletions(-) diff --git a/hls4ml/converters/__init__.py b/hls4ml/converters/__init__.py index 3d7ce1fe56..1343907b54 100644 --- a/hls4ml/converters/__init__.py +++ b/hls4ml/converters/__init__.py @@ -93,10 +93,10 @@ def parse_yaml_config(config_file): """ def construct_keras_model(loader, node): - from tensorflow.keras.models import load_model - model_str = loader.construct_scalar(node) - return load_model(model_str) + import keras + + return keras.models.load_model(model_str) yaml.add_constructor('!keras_model', construct_keras_model, Loader=yaml.SafeLoader) diff --git a/hls4ml/model/optimizer/passes/qkeras.py b/hls4ml/model/optimizer/passes/qkeras.py index 03690bed0d..fb02d4eccf 100644 --- a/hls4ml/model/optimizer/passes/qkeras.py +++ b/hls4ml/model/optimizer/passes/qkeras.py @@ -1,5 +1,4 @@ import numpy as np -import tensorflow as tf from hls4ml.model.layers import ApplyAlpha from hls4ml.model.optimizer import ConfigurableOptimizerPass, OptimizerPass, register_pass @@ -113,6 +112,8 @@ def match(self, node): def transform(self, model, node): # The quantizer has to be applied to set the scale attribute # This must be applied to the _unquantized_ weights to obtain the correct scale + import tensorflow as tf + quantizer = node.weights['weight'].quantizer.quantizer_fn # get QKeras quantizer weights = node.weights['weight'].data_unquantized # get weights qweights = quantizer(tf.convert_to_tensor(weights)) diff --git a/hls4ml/model/profiling.py b/hls4ml/model/profiling.py index f30088b51d..df5e01d101 100644 --- a/hls4ml/model/profiling.py +++ b/hls4ml/model/profiling.py @@ -13,12 +13,11 @@ from hls4ml.model.layers import GRU, LSTM, SeparableConv1D, SeparableConv2D try: - import qkeras - from tensorflow import keras + import keras - __tf_profiling_enabled__ = True + __keras_profiling_enabled__ = True except ImportError: - __tf_profiling_enabled__ = False + __keras_profiling_enabled__ = False try: import torch @@ -27,6 +26,19 @@ except ImportError: __torch_profiling_enabled__ = False +try: + import qkeras + + __qkeras_profiling_enabled__ = True +except ImportError: + __qkeras_profiling_enabled__ = False + +_activations = list() +if __keras_profiling_enabled__: + _activations.append(keras.layers.Activation) +if __qkeras_profiling_enabled__: + _activations.append(qkeras.qactivations) + def get_unoptimized_hlsmodel(model): from hls4ml.converters import convert_from_config @@ -565,7 +577,7 @@ def numerical(model=None, hls_model=None, X=None, plot='boxplot'): if hls_model_present: data = weights_hlsmodel(hls_model_unoptimized, fmt='summary', plot=plot) elif model_present: - if __tf_profiling_enabled__ and isinstance(model, keras.Model): + if __keras_profiling_enabled__ and isinstance(model, keras.Model): data = weights_keras(model, fmt='summary', plot=plot) elif __torch_profiling_enabled__ and isinstance(model, torch.nn.Module): data = weights_torch(model, fmt='summary', plot=plot) @@ -603,7 +615,7 @@ def numerical(model=None, hls_model=None, X=None, plot='boxplot'): if X is not None: print("Profiling activations" + before) data = None - if __tf_profiling_enabled__ and isinstance(model, keras.Model): + if __keras_profiling_enabled__ and isinstance(model, keras.Model): data = activations_keras(model, X, fmt='summary', plot=plot) elif __torch_profiling_enabled__ and isinstance(model, torch.nn.Sequential): data = activations_torch(model, X, fmt='summary', plot=plot) @@ -673,7 +685,7 @@ def get_ymodel_keras(keras_model, X): if ( hasattr(layer, 'activation') and layer.activation is not None - and not isinstance(layer, (keras.layers.Activation, qkeras.qlayers.QActivation)) + and not isinstance(layer, _activations) and layer.activation.__name__ != 'linear' ): tmp_activation = layer.activation diff --git a/hls4ml/optimization/dsp_aware_pruning/keras/__init__.py b/hls4ml/optimization/dsp_aware_pruning/keras/__init__.py index 29012bd39e..b525f58a33 100644 --- a/hls4ml/optimization/dsp_aware_pruning/keras/__init__.py +++ b/hls4ml/optimization/dsp_aware_pruning/keras/__init__.py @@ -4,9 +4,6 @@ import numpy as np import tensorflow as tf -# Enables printing of loss tensors during custom training loop -from tensorflow.python.ops.numpy_ops import np_config - import hls4ml.optimization.dsp_aware_pruning.keras.utils as utils from hls4ml.optimization.dsp_aware_pruning.config import SUPPORTED_STRUCTURES from hls4ml.optimization.dsp_aware_pruning.keras.builder import build_optimizable_model, remove_custom_regularizers @@ -15,7 +12,6 @@ from hls4ml.optimization.dsp_aware_pruning.keras.reduction import reduce_model from hls4ml.optimization.dsp_aware_pruning.scheduler import OptimizationScheduler -np_config.enable_numpy_behavior() default_regularization_range = np.logspace(-6, -2, num=16).tolist() diff --git a/hls4ml/utils/config.py b/hls4ml/utils/config.py index 1db8e3c731..e3e0a8693b 100644 --- a/hls4ml/utils/config.py +++ b/hls4ml/utils/config.py @@ -1,7 +1,5 @@ import json -import qkeras - import hls4ml @@ -48,6 +46,8 @@ def create_config(output_dir='my-hls-test', project_name='myproject', backend='V def _get_precision_from_quantizer(quantizer): if isinstance(quantizer, str): + import qkeras + quantizer_obj = qkeras.get_quantizer(quantizer) quantizer = {} # Some activations are classes with get_config method diff --git a/hls4ml/writer/catapult_writer.py b/hls4ml/writer/catapult_writer.py index 7db1063206..9a48460995 100755 --- a/hls4ml/writer/catapult_writer.py +++ b/hls4ml/writer/catapult_writer.py @@ -889,7 +889,9 @@ def keras_model_representer(dumper, keras_model): return dumper.represent_scalar('!keras_model', model_path) try: - from tensorflow.keras import Model as KerasModel + import keras + + KerasModel = keras.models.Model yaml.add_multi_representer(KerasModel, keras_model_representer) except Exception: diff --git a/hls4ml/writer/quartus_writer.py b/hls4ml/writer/quartus_writer.py index 932a8b6a6d..1d61bde1f4 100644 --- a/hls4ml/writer/quartus_writer.py +++ b/hls4ml/writer/quartus_writer.py @@ -1327,7 +1327,9 @@ def keras_model_representer(dumper, keras_model): return dumper.represent_scalar('!keras_model', model_path) try: - from tensorflow.keras import Model as KerasModel + import keras + + KerasModel = keras.models.Model yaml.add_multi_representer(KerasModel, keras_model_representer) except Exception: diff --git a/hls4ml/writer/vivado_writer.py b/hls4ml/writer/vivado_writer.py index 0341959045..6531f9db87 100644 --- a/hls4ml/writer/vivado_writer.py +++ b/hls4ml/writer/vivado_writer.py @@ -817,7 +817,9 @@ def keras_model_representer(dumper, keras_model): return dumper.represent_scalar('!keras_model', model_path) try: - from tensorflow.keras import Model as KerasModel + import keras + + KerasModel = keras.models.Model yaml.add_multi_representer(KerasModel, keras_model_representer) except Exception: From 0e66776ee4e4ad04d693001432a993a5d1108c44 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Fri, 25 Oct 2024 20:34:29 -0700 Subject: [PATCH 03/16] less mandatory dependency --- hls4ml/model/profiling.py | 8 ++++---- hls4ml/report/quartus_report.py | 6 +++--- setup.cfg | 18 +++++++++++++----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/hls4ml/model/profiling.py b/hls4ml/model/profiling.py index df5e01d101..324ba9e887 100644 --- a/hls4ml/model/profiling.py +++ b/hls4ml/model/profiling.py @@ -33,11 +33,11 @@ except ImportError: __qkeras_profiling_enabled__ = False -_activations = list() +__keras_activations = list() if __keras_profiling_enabled__: - _activations.append(keras.layers.Activation) + __keras_activations.append(keras.layers.Activation) if __qkeras_profiling_enabled__: - _activations.append(qkeras.qactivations) + __keras_activations.append(qkeras.QActivation) def get_unoptimized_hlsmodel(model): @@ -685,7 +685,7 @@ def get_ymodel_keras(keras_model, X): if ( hasattr(layer, 'activation') and layer.activation is not None - and not isinstance(layer, _activations) + and not isinstance(layer, tuple(__keras_activations)) and layer.activation.__name__ != 'linear' ): tmp_activation = layer.activation diff --git a/hls4ml/report/quartus_report.py b/hls4ml/report/quartus_report.py index c337e5de10..47fc43c132 100644 --- a/hls4ml/report/quartus_report.py +++ b/hls4ml/report/quartus_report.py @@ -2,9 +2,6 @@ import webbrowser from ast import literal_eval -from calmjs.parse import asttypes, es5 -from tabulate import tabulate - def parse_quartus_report(hls_dir, write_to_file=True): ''' @@ -53,6 +50,8 @@ def read_quartus_report(hls_dir, open_browser=False): Returns: None ''' + from tabulate import tabulate + report = parse_quartus_report(hls_dir) print('HLS Resource Summary\n') @@ -100,6 +99,7 @@ def read_js_object(js_script): Returns: Dictionary of variables defines in script ''' + from calmjs.parse import asttypes, es5 def visit(node): if isinstance(node, asttypes.Program): diff --git a/setup.cfg b/setup.cfg index 0b81e7b592..c987f1c317 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,17 +22,15 @@ description_file = README.md [options] packages = find: install_requires = - calmjs.parse h5py numpy - onnx>=1.4.0 + pyyaml pydigitalwavetools==1.1 pyparsing pyyaml - tabulate - tensorflow>=2.8.0,<=2.14.1 - tensorflow-model-optimization<=0.7.5 + python_requires = >=3.10, <3.12 +python_requires = >=3.10 include_package_data = True scripts = scripts/hls4ml @@ -51,14 +49,24 @@ profiling = matplotlib pandas seaborn +qkeras = + qkeras + tensorflow>=2.8.0,<=2.14.1 + tensorflow-model-optimization<=0.7.5 +quantus_report = + calmjs.parse + tabulate sr = sympy testing = HGQ~=0.2.0 + calmjs.parse + onnx>=1.4.0 pytest pytest-cov pytest-randomly qonnx + tabulate torch [check-manifest] From 83ca2d6e0b1c1358a040aca5146821a3c7d64b74 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Sat, 26 Oct 2024 09:11:34 -0700 Subject: [PATCH 04/16] fix dsp_aware_pruning test import path --- test/pytest/test_optimization/test_attributes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pytest/test_optimization/test_attributes.py b/test/pytest/test_optimization/test_attributes.py index a42d3a6751..c9e22091f2 100644 --- a/test/pytest/test_optimization/test_attributes.py +++ b/test/pytest/test_optimization/test_attributes.py @@ -1,7 +1,7 @@ from tensorflow.keras.layers import Conv2D, Dense, Flatten, ReLU from tensorflow.keras.models import Sequential -from hls4ml.optimization import get_attributes_from_keras_model_and_hls4ml_config +from hls4ml.optimization.dsp_aware_pruning import get_attributes_from_keras_model_and_hls4ml_config from hls4ml.utils.config import config_from_keras_model From e5e3664bb1a014083cd422631bb2e0dbe412534d Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Sun, 15 Dec 2024 06:31:39 +0000 Subject: [PATCH 05/16] fix broken setup.cfg after rebase, rm pyparsing --- setup.cfg | 4 ---- 1 file changed, 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index c987f1c317..1d4241f063 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,12 +24,8 @@ packages = find: install_requires = h5py numpy - pyyaml pydigitalwavetools==1.1 - pyparsing pyyaml - -python_requires = >=3.10, <3.12 python_requires = >=3.10 include_package_data = True scripts = scripts/hls4ml From 2366b9f95471d9f2a9d57aa908bad1305f689a41 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Sun, 15 Dec 2024 06:31:59 +0000 Subject: [PATCH 06/16] purge qkeras workaround --- hls4ml/__init__.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/hls4ml/__init__.py b/hls4ml/__init__.py index e3a7247b0d..0ff5e52ac9 100644 --- a/hls4ml/__init__.py +++ b/hls4ml/__init__.py @@ -1,33 +1,3 @@ -# Temporary workaround for QKeras installation requirement, will be removed after 1.0.0 -def maybe_install_qkeras(): - import subprocess - import sys - - QKERAS_PKG_NAME = 'QKeras' - # QKERAS_PKG_SOURCE = QKERAS_PKG_NAME - QKERAS_PKG_SOURCE = 'qkeras@git+https://github.com/fastmachinelearning/qkeras.git' - - def pip_list(): - p = subprocess.run([sys.executable, '-m', 'pip', 'list'], check=True, capture_output=True) - return p.stdout.decode() - - def pip_install(package): - subprocess.check_call([sys.executable, '-m', 'pip', 'install', package]) - - all_pkgs = pip_list() - if QKERAS_PKG_NAME not in all_pkgs: - print('QKeras installation not found, installing one...') - pip_install(QKERAS_PKG_SOURCE) - print('QKeras installed.') - - -try: - maybe_install_qkeras() -except Exception: - print('Could not find QKeras installation, make sure you have QKeras installed.') - -# End of workaround - from hls4ml import converters, report, utils # noqa: F401, E402 try: From a15afa846a3d7867bfce7f17e123b334446e0a0b Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Sun, 15 Dec 2024 22:57:36 +0000 Subject: [PATCH 07/16] switch to pyproject.toml switch to pyproject.toml include pyproject.toml after install --- .pre-commit-config.yaml | 12 +-- MANIFEST.in | 2 +- scripts/hls4ml => hls4ml/cli/__init__.py | 0 pyproject.toml | 101 ++++++++++++++++++++++- setup.cfg | 74 ----------------- setup.py | 4 - 6 files changed, 104 insertions(+), 89 deletions(-) rename scripts/hls4ml => hls4ml/cli/__init__.py (100%) delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d45ffbdd27..77048cc449 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,6 +9,11 @@ repos: args: ['--line-length=125', '--skip-string-normalization'] +- repo: https://github.com/tox-dev/pyproject-fmt + rev: v2.5.0 + hooks: + - id: pyproject-fmt + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: @@ -16,6 +21,7 @@ repos: - id: check-case-conflict - id: check-merge-conflict - id: check-symlinks + - id: check-toml - id: check-yaml - id: debug-statements - id: end-of-file-fixer @@ -27,7 +33,6 @@ repos: rev: 5.13.2 hooks: - id: isort - args: ["--profile", "black", --line-length=125] - repo: https://github.com/asottile/pyupgrade rev: v3.19.1 @@ -35,11 +40,6 @@ repos: - id: pyupgrade args: ["--py36-plus"] -- repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.7.0 - hooks: - - id: setup-cfg-fmt - - repo: https://github.com/pycqa/flake8 rev: 7.1.1 hooks: diff --git a/MANIFEST.in b/MANIFEST.in index 549cc6983c..7bcfbfaf6d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include LICENSE README.md CONTRIBUTING.md CITATION.cff pyproject.toml setup.py setup.cfg .clang-format +include LICENSE README.md CONTRIBUTING.md CITATION.cff pyproject.toml setup.py .clang-format graft example-models graft test graft contrib diff --git a/scripts/hls4ml b/hls4ml/cli/__init__.py similarity index 100% rename from scripts/hls4ml rename to hls4ml/cli/__init__.py diff --git a/pyproject.toml b/pyproject.toml index 6402ab0e7a..b713b41d80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,103 @@ [build-system] -# AVOID CHANGING REQUIRES: IT WILL BE UPDATED BY PYSCAFFOLD! -requires = ["setuptools>=46.1.0", "setuptools_scm[toml]>=5", "wheel"] build-backend = "setuptools.build_meta" +requires = [ "setuptools>=61", "setuptools-scm>=8" ] + +[project] +name = "hls4ml" +version = "1.0.0" +description = "Machine learning in FPGAs using HLS" +readme = "README.md" +license = { text = "Apache-2.0" } +authors = [ { name = "hls4ml Team" } ] +requires-python = ">=3.10" +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: C++", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ "h5py", "numpy", "pydigitalwavetools==1.1", "pyyaml" ] + +optional-dependencies.doc = [ + "sphinx", + "sphinx-contributors", + "sphinx-github-changelog", + "sphinx-rtd-theme", +] +optional-dependencies.HGQ = [ "hgq~=0.2.0" ] +optional-dependencies.optimization = [ + "keras-tuner==1.1.3", + "ortools==9.4.1874", + "packaging", +] +optional-dependencies.profiling = [ "matplotlib", "pandas", "seaborn" ] +optional-dependencies.qkeras = [ + "qkeras", + "tensorflow>=2.8,<=2.14.1", + "tensorflow-model-optimization<=0.7.5", +] +optional-dependencies.quantus_report = [ "calmjs-parse", "tabulate" ] +optional-dependencies.sr = [ "sympy" ] +optional-dependencies.testing = [ + "calmjs-parse", + "hgq~=0.2.0", + "onnx>=1.4", + "pytest", + "pytest-cov", + "pytest-randomly", + "qonnx", + "tabulate", + "torch", +] +urls.Homepage = "https://fastmachinelearning.org/hls4ml" +scripts.hls4ml = "hls4ml.cli:main" +entry-points.pytest_randomly.random_seeder = "hls4ml:reseed" + +[tool.setuptools] +packages = [ "hls4ml" ] +include-package-data = true + [tool.setuptools_scm] -# See configuration details in https://github.com/pypa/setuptools_scm + version_scheme = "release-branch-semver" -git_describe_command = "git describe --dirty --tags --long --match v* --first-parent" +git_describe_command = [ + "git", + "describe", + "--dirty", + "--tags", + "--long", + "--match", + "v*", + "--first-parent", +] write_to = "hls4ml/_version.py" + +[tool.black] +line-length = 125 +skip-string-normalization = true + +[tool.isort] +profile = "black" +line_length = 125 + +[tool.flake8] +max-line-length = 125 +extend-ignore = [ "E203", "T201" ] + +[tool.check-manifest] +ignore = [ + ".github/**", + "docs/**", + ".pre-commit-config.yaml", + "Jenkinsfile", + "hls4ml/_version.py", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 1d4241f063..0000000000 --- a/setup.cfg +++ /dev/null @@ -1,74 +0,0 @@ -[metadata] -name = hls4ml -description = Machine learning in FPGAs using HLS -long_description = file: README.md -long_description_content_type = text/markdown -url = https://fastmachinelearning.org/hls4ml -author = hls4ml Team -license = Apache-2.0 -license_files = LICENSE -classifiers = - Development Status :: 4 - Beta - Intended Audience :: Developers - Intended Audience :: Science/Research - License :: OSI Approved :: Apache Software License - Programming Language :: C++ - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Topic :: Software Development :: Libraries - Topic :: Software Development :: Libraries :: Python Modules -description_file = README.md - -[options] -packages = find: -install_requires = - h5py - numpy - pydigitalwavetools==1.1 - pyyaml -python_requires = >=3.10 -include_package_data = True -scripts = scripts/hls4ml - -[options.entry_points] -pytest_randomly.random_seeder = - hls4ml = hls4ml:reseed - -[options.extras_require] -HGQ = - HGQ~=0.2.0 -optimization = - keras-tuner==1.1.3 - ortools==9.4.1874 - packaging -profiling = - matplotlib - pandas - seaborn -qkeras = - qkeras - tensorflow>=2.8.0,<=2.14.1 - tensorflow-model-optimization<=0.7.5 -quantus_report = - calmjs.parse - tabulate -sr = - sympy -testing = - HGQ~=0.2.0 - calmjs.parse - onnx>=1.4.0 - pytest - pytest-cov - pytest-randomly - qonnx - tabulate - torch - -[check-manifest] -ignore = - .github/** - docs/** - .pre-commit-config.yaml - Jenkinsfile - hls4ml/_version.py diff --git a/setup.py b/setup.py deleted file mode 100644 index 1abbd068c1..0000000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -import setuptools - -if __name__ == "__main__": - setuptools.setup() From 677abf54f6e8e84eadd0e388ba4fda4e1d3e6515 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Sun, 15 Dec 2024 22:57:49 +0000 Subject: [PATCH 08/16] format --- hls4ml/converters/onnx_to_hls.py | 2 ++ hls4ml/writer/oneapi_writer.py | 49 ++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/hls4ml/converters/onnx_to_hls.py b/hls4ml/converters/onnx_to_hls.py index 99281888f3..f3b6acbaf3 100644 --- a/hls4ml/converters/onnx_to_hls.py +++ b/hls4ml/converters/onnx_to_hls.py @@ -76,6 +76,7 @@ def get_input_shape(graph, node): def get_constant_value(graph, constant_name): tensor = next((x for x in graph.initializer if x.name == constant_name), None) from onnx import numpy_helper + return numpy_helper.to_array(tensor) @@ -274,6 +275,7 @@ def onnx_to_hls(config): print('Interpreting Model ...') import onnx + onnx_model = onnx.load(config['OnnxModel']) if isinstance(config['OnnxModel'], str) else config['OnnxModel'] layer_list, input_layers, output_layers = parse_onnx_model(onnx_model) diff --git a/hls4ml/writer/oneapi_writer.py b/hls4ml/writer/oneapi_writer.py index fe633214f6..c9af2544bd 100644 --- a/hls4ml/writer/oneapi_writer.py +++ b/hls4ml/writer/oneapi_writer.py @@ -102,9 +102,10 @@ def write_project_cpp(self, model): project_name = model.config.get_project_name() filedir = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(filedir, '../templates/oneapi/firmware/myproject.cpp')) as f, open( - f'{model.config.get_output_dir()}/src/firmware/{project_name}.cpp', 'w' - ) as fout: + with ( + open(os.path.join(filedir, '../templates/oneapi/firmware/myproject.cpp')) as f, + open(f'{model.config.get_output_dir()}/src/firmware/{project_name}.cpp', 'w') as fout, + ): model_inputs = model.get_input_variables() model_outputs = model.get_output_variables() model_brams = [var for var in model.get_weight_variables() if var.storage.lower() == 'bram'] @@ -207,9 +208,10 @@ def write_project_header(self, model): project_name = model.config.get_project_name() filedir = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(filedir, '../templates/oneapi/firmware/myproject.h')) as f, open( - f'{model.config.get_output_dir()}/src/firmware/{project_name}.h', 'w' - ) as fout: + with ( + open(os.path.join(filedir, '../templates/oneapi/firmware/myproject.h')) as f, + open(f'{model.config.get_output_dir()}/src/firmware/{project_name}.h', 'w') as fout, + ): model_inputs = model.get_input_variables() model_outputs = model.get_output_variables() # model_brams = [var for var in model.get_weight_variables() if var.storage.lower() == 'bram'] @@ -254,9 +256,10 @@ def write_defines(self, model): model (ModelGraph): the hls4ml model. """ filedir = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(filedir, '../templates/oneapi/firmware/defines.h')) as f, open( - f'{model.config.get_output_dir()}/src/firmware/defines.h', 'w' - ) as fout: + with ( + open(os.path.join(filedir, '../templates/oneapi/firmware/defines.h')) as f, + open(f'{model.config.get_output_dir()}/src/firmware/defines.h', 'w') as fout, + ): for line in f.readlines(): # Insert numbers if '// hls-fpga-machine-learning insert numbers' in line: @@ -298,9 +301,10 @@ def write_parameters(self, model): model (ModelGraph): the hls4ml model. """ filedir = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(filedir, '../templates/oneapi/firmware/parameters.h')) as f, open( - f'{model.config.get_output_dir()}/src/firmware/parameters.h', 'w' - ) as fout: + with ( + open(os.path.join(filedir, '../templates/oneapi/firmware/parameters.h')) as f, + open(f'{model.config.get_output_dir()}/src/firmware/parameters.h', 'w') as fout, + ): for line in f.readlines(): if '// hls-fpga-machine-learning insert includes' in line: newline = line @@ -376,9 +380,10 @@ def write_test_bench(self, model): output_predictions, f'{model.config.get_output_dir()}/tb_data/tb_output_predictions.dat' ) - with open(os.path.join(filedir, '../templates/oneapi/myproject_test.cpp')) as f, open( - f'{model.config.get_output_dir()}/src/{project_name}_test.cpp', 'w' - ) as fout: + with ( + open(os.path.join(filedir, '../templates/oneapi/myproject_test.cpp')) as f, + open(f'{model.config.get_output_dir()}/src/{project_name}_test.cpp', 'w') as fout, + ): for line in f.readlines(): indent = ' ' * (len(line) - len(line.lstrip(' '))) @@ -434,9 +439,10 @@ def write_bridge(self, model): indent = ' ' filedir = os.path.dirname(os.path.abspath(__file__)) - with open(os.path.join(filedir, '../templates/oneapi/myproject_bridge.cpp')) as f, open( - f'{model.config.get_output_dir()}/src/{project_name}_bridge.cpp', 'w' - ) as fout: + with ( + open(os.path.join(filedir, '../templates/oneapi/myproject_bridge.cpp')) as f, + open(f'{model.config.get_output_dir()}/src/{project_name}_bridge.cpp', 'w') as fout, + ): for line in f.readlines(): if 'MYPROJECT' in line: newline = line.replace('MYPROJECT', format(project_name.upper())) @@ -511,9 +517,10 @@ def write_build_script(self, model): # Makefile filedir = os.path.dirname(os.path.abspath(__file__)) device = model.config.get_config_value('Part') - with open(os.path.join(filedir, '../templates/oneapi/CMakeLists.txt')) as f, open( - f'{model.config.get_output_dir()}/CMakeLists.txt', 'w' - ) as fout: + with ( + open(os.path.join(filedir, '../templates/oneapi/CMakeLists.txt')) as f, + open(f'{model.config.get_output_dir()}/CMakeLists.txt', 'w') as fout, + ): for line in f.readlines(): line = line.replace('myproject', model.config.get_project_name()) line = line.replace('mystamp', model.config.get_config_value('Stamp')) From ba88210e8839d6e7667686b0c96a2f5b7078ef74 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Sun, 15 Dec 2024 23:12:45 +0000 Subject: [PATCH 09/16] rm useless flake8 config in pyprject.toml --- pyproject.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b713b41d80..756e688d5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,10 +89,6 @@ skip-string-normalization = true profile = "black" line_length = 125 -[tool.flake8] -max-line-length = 125 -extend-ignore = [ "E203", "T201" ] - [tool.check-manifest] ignore = [ ".github/**", From ea4edbbddbddd81446624a074f3a09b061fbc7da Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Mon, 16 Dec 2024 01:20:13 +0000 Subject: [PATCH 10/16] Add hint on import failure --- hls4ml/converters/__init__.py | 47 +++++++++-------------------- hls4ml/converters/onnx_to_hls.py | 4 +++ hls4ml/converters/pytorch_to_hls.py | 4 +++ hls4ml/model/__init__.py | 7 ----- hls4ml/model/quantizers.py | 4 +++ hls4ml/report/quartus_report.py | 4 +++ hls4ml/utils/config.py | 2 ++ pyproject.toml | 1 + 8 files changed, 34 insertions(+), 39 deletions(-) diff --git a/hls4ml/converters/__init__.py b/hls4ml/converters/__init__.py index 1343907b54..693a76f666 100644 --- a/hls4ml/converters/__init__.py +++ b/hls4ml/converters/__init__.py @@ -1,6 +1,5 @@ import importlib import os -import warnings import yaml @@ -10,33 +9,19 @@ from hls4ml.converters.keras_to_hls import get_supported_keras_layers # noqa: F401 from hls4ml.converters.keras_to_hls import parse_keras_model # noqa: F401 from hls4ml.converters.keras_to_hls import keras_to_hls, register_keras_layer_handler +from hls4ml.converters.onnx_to_hls import get_supported_onnx_layers # noqa: F401 from hls4ml.converters.onnx_to_hls import parse_onnx_model # noqa: F401 +from hls4ml.converters.onnx_to_hls import onnx_to_hls, register_onnx_layer_handler +from hls4ml.converters.pytorch_to_hls import ( # noqa: F401 + get_supported_pytorch_layers, + pytorch_to_hls, + register_pytorch_layer_handler, +) from hls4ml.model import ModelGraph from hls4ml.utils.config import create_config +from hls4ml.utils.dependency import requires from hls4ml.utils.symbolic_utils import LUTFunction -# ----------Make converters available if the libraries can be imported----------# -try: - from hls4ml.converters.pytorch_to_hls import ( # noqa: F401 - get_supported_pytorch_layers, - pytorch_to_hls, - register_pytorch_layer_handler, - ) - - __pytorch_enabled__ = True -except ImportError: - warnings.warn("WARNING: Pytorch converter is not enabled!", stacklevel=1) - __pytorch_enabled__ = False - -try: - from hls4ml.converters.onnx_to_hls import get_supported_onnx_layers # noqa: F401 - from hls4ml.converters.onnx_to_hls import onnx_to_hls, register_onnx_layer_handler - - __onnx_enabled__ = True -except ImportError: - warnings.warn("WARNING: ONNX converter is not enabled!", stacklevel=1) - __onnx_enabled__ = False - # ----------Layer handling register----------# model_types = ['keras', 'pytorch', 'onnx'] @@ -51,7 +36,7 @@ # and has 'handles' attribute # and is defined in this module (i.e., not imported) if callable(func) and hasattr(func, 'handles') and func.__module__ == lib.__name__: - for layer in func.handles: + for layer in func.handles: # type: ignore if model_type == 'keras': register_keras_layer_handler(layer, func) elif model_type == 'pytorch': @@ -124,15 +109,9 @@ def convert_from_config(config): model = None if 'OnnxModel' in yamlConfig: - if __onnx_enabled__: - model = onnx_to_hls(yamlConfig) - else: - raise Exception("ONNX not found. Please install ONNX.") + model = onnx_to_hls(yamlConfig) elif 'PytorchModel' in yamlConfig: - if __pytorch_enabled__: - model = pytorch_to_hls(yamlConfig) - else: - raise Exception("PyTorch not found. Please install PyTorch.") + model = pytorch_to_hls(yamlConfig) else: model = keras_to_hls(yamlConfig) @@ -174,6 +153,7 @@ def _check_model_config(model_config): return model_config +@requires('_keras') def convert_from_keras_model( model, output_dir='my-hls-test', @@ -237,6 +217,7 @@ def convert_from_keras_model( return keras_to_hls(config) +@requires('_torch') def convert_from_pytorch_model( model, output_dir='my-hls-test', @@ -308,6 +289,7 @@ def convert_from_pytorch_model( return pytorch_to_hls(config) +@requires('onnx') def convert_from_onnx_model( model, output_dir='my-hls-test', @@ -371,6 +353,7 @@ def convert_from_onnx_model( return onnx_to_hls(config) +@requires('sr') def convert_from_symbolic_expression( expr, n_symbols=None, diff --git a/hls4ml/converters/onnx_to_hls.py b/hls4ml/converters/onnx_to_hls.py index f3b6acbaf3..d51701e726 100644 --- a/hls4ml/converters/onnx_to_hls.py +++ b/hls4ml/converters/onnx_to_hls.py @@ -1,4 +1,5 @@ from hls4ml.model import ModelGraph +from hls4ml.utils.dependency import requires # ----------------------Helpers--------------------- @@ -17,6 +18,7 @@ def replace_char_inconsitency(name): return name.replace('.', '_') +@requires('onnx') def get_onnx_attribute(operation, name, default=None): from onnx import helper @@ -73,6 +75,7 @@ def get_input_shape(graph, node): return rv +@requires('onnx') def get_constant_value(graph, constant_name): tensor = next((x for x in graph.initializer if x.name == constant_name), None) from onnx import numpy_helper @@ -258,6 +261,7 @@ def parse_onnx_model(onnx_model): return layer_list, input_layers, output_layers +@requires('onnx') def onnx_to_hls(config): """Convert onnx model to hls model from configuration. diff --git a/hls4ml/converters/pytorch_to_hls.py b/hls4ml/converters/pytorch_to_hls.py index 91357a7540..e71c0bc1ac 100644 --- a/hls4ml/converters/pytorch_to_hls.py +++ b/hls4ml/converters/pytorch_to_hls.py @@ -1,6 +1,7 @@ import numpy as np from hls4ml.model import ModelGraph +from hls4ml.utils.dependency import requires class PyTorchModelReader: @@ -24,6 +25,7 @@ def get_weights_data(self, layer_name, var_name): return data +@requires('_torch') class PyTorchFileReader(PyTorchModelReader): # Inherit get_weights_data method def __init__(self, config): import torch @@ -105,6 +107,7 @@ def decorator(function): # ---------------------------------------------------------------- +@requires('_torch') def parse_pytorch_model(config, verbose=True): """Convert PyTorch model to hls4ml ModelGraph. @@ -401,6 +404,7 @@ def parse_pytorch_model(config, verbose=True): return layer_list, input_layers +@requires('_torch') def pytorch_to_hls(config): layer_list, input_layers = parse_pytorch_model(config) print('Creating HLS model') diff --git a/hls4ml/model/__init__.py b/hls4ml/model/__init__.py index fc504392b6..4ca72e3cd6 100644 --- a/hls4ml/model/__init__.py +++ b/hls4ml/model/__init__.py @@ -1,8 +1 @@ from hls4ml.model.graph import HLSConfig, ModelGraph # noqa: F401 - -try: - from hls4ml.model import profiling # noqa: F401 - - __profiling_enabled__ = True -except ImportError: - __profiling_enabled__ = False diff --git a/hls4ml/model/quantizers.py b/hls4ml/model/quantizers.py index b445c70af3..eb313fc4ea 100644 --- a/hls4ml/model/quantizers.py +++ b/hls4ml/model/quantizers.py @@ -14,6 +14,7 @@ SaturationMode, XnorPrecisionType, ) +from hls4ml.utils.dependency import requires class Quantizer: @@ -84,6 +85,7 @@ class QKerasQuantizer(Quantizer): config (dict): Config of the QKeras quantizer to wrap. """ + @requires('qkeras') def __init__(self, config): from qkeras.quantizers import get_quantizer @@ -131,6 +133,7 @@ class QKerasBinaryQuantizer(Quantizer): config (dict): Config of the QKeras quantizer to wrap. """ + @requires('qkeras') def __init__(self, config, xnor=False): from qkeras.quantizers import get_quantizer @@ -155,6 +158,7 @@ class QKerasPO2Quantizer(Quantizer): config (dict): Config of the QKeras quantizer to wrap. """ + @requires('qkeras') def __init__(self, config): from qkeras.quantizers import get_quantizer diff --git a/hls4ml/report/quartus_report.py b/hls4ml/report/quartus_report.py index 47fc43c132..677a931402 100644 --- a/hls4ml/report/quartus_report.py +++ b/hls4ml/report/quartus_report.py @@ -2,6 +2,8 @@ import webbrowser from ast import literal_eval +from hls4ml.utils.dependency import requires + def parse_quartus_report(hls_dir, write_to_file=True): ''' @@ -39,6 +41,7 @@ def parse_quartus_report(hls_dir, write_to_file=True): return results +@requires('quantus-report') def read_quartus_report(hls_dir, open_browser=False): ''' Parse and print the Quartus report to print the report. Optionally open a browser. @@ -89,6 +92,7 @@ def _find_project_dir(hls_dir): return top_func_name + '-fpga.prj' +@requires('quantus-report') def read_js_object(js_script): ''' Reads the JavaScript file and return a dictionary of variables definded in the script. diff --git a/hls4ml/utils/config.py b/hls4ml/utils/config.py index e3e0a8693b..0161187a2d 100644 --- a/hls4ml/utils/config.py +++ b/hls4ml/utils/config.py @@ -1,6 +1,7 @@ import json import hls4ml +from hls4ml.utils.dependency import requires def create_config(output_dir='my-hls-test', project_name='myproject', backend='Vivado', version='1.0.0', **kwargs): @@ -44,6 +45,7 @@ def create_config(output_dir='my-hls-test', project_name='myproject', backend='V return config +@requires('qkeras') def _get_precision_from_quantizer(quantizer): if isinstance(quantizer, str): import qkeras diff --git a/pyproject.toml b/pyproject.toml index 756e688d5c..24175c9612 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ optional-dependencies.doc = [ "sphinx-rtd-theme", ] optional-dependencies.HGQ = [ "hgq~=0.2.0" ] +optional-dependencies.onnx = [ "onnx>=1.4" ] optional-dependencies.optimization = [ "keras-tuner==1.1.3", "ortools==9.4.1874", From 6bcaa80a6d7da4f2bbd1e30c8d82059b226f1186 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Mon, 16 Dec 2024 01:32:12 +0000 Subject: [PATCH 11/16] leftover --- hls4ml/utils/dependency.py | 55 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 hls4ml/utils/dependency.py diff --git a/hls4ml/utils/dependency.py b/hls4ml/utils/dependency.py new file mode 100644 index 0000000000..e546dcb8c9 --- /dev/null +++ b/hls4ml/utils/dependency.py @@ -0,0 +1,55 @@ +import sys +from functools import wraps +from importlib.metadata import metadata +from inspect import ismethod + +extra_requires: dict[str, list[str]] = {} +subpackage = None +for k, v in metadata('hls4ml')._headers: # type: ignore + if k != 'Requires-Dist': + continue + if '; extra == ' not in v: + continue + + req, pkg = v.split('; extra == ') + pkg = pkg.strip('"') + + extra_requires.setdefault(pkg, []).append(req) + + +def requires(pkg: str): + """Mark a function or method as requiring a package to be installed. + 'name': requires hls4ml[name] to be installed. + '_name': requires name to be installed. + + Parameters + ---------- + pkg : str + The package to require. + """ + + def deco(f): + if ismethod(f): + qualifier = f"Method {f.__self__.__class__.__name__}.{f.__name__}" + else: + qualifier = f"Function {f.__name__}" + + if not pkg.startswith("_"): + reqs = ", ".join(extra_requires[pkg]) + msg = f"{qualifier} requires {reqs}, but package {{ename}} is missing" + "Please consider install it with `pip install hls4ml[{pkg}]` for full functionality with {pkg}." + else: + msg = f"{qualifier} requires {pkg[1:]}, but package {{ename}} is missing." + "Consider install it with `pip install {pkg}`." + + @wraps(f) + def inner(*args, **kwargs): + try: + return f(*args, **kwargs) + except ImportError as e: + print(msg.format(ename=e.name), file=sys.stderr) + raise e + + return inner + + return deco From d5532a6cc19e233503dfaea415f220bf357251d8 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Mon, 16 Dec 2024 01:32:46 +0000 Subject: [PATCH 12/16] rm setup.py from manifest --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 7bcfbfaf6d..708e40c86b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include LICENSE README.md CONTRIBUTING.md CITATION.cff pyproject.toml setup.py .clang-format +include LICENSE README.md CONTRIBUTING.md CITATION.cff pyproject.toml .clang-format graft example-models graft test graft contrib From fd4a7e7ca9aa5da16c646e694b33d998a47ecbaf Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Mon, 16 Dec 2024 01:48:01 +0000 Subject: [PATCH 13/16] manifest fix 2 --- MANIFEST.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 708e40c86b..5bec5fe2a6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,5 +3,6 @@ graft example-models graft test graft contrib recursive-include hls4ml/templates * -global-exclude .git .gitmodules .gitlab-ci.yml +recursive-include hls4ml *.py +global-exclude .git .gitmodules .gitlab-ci.yml *.pyc include hls4ml/backends/vivado_accelerator/supported_boards.json From a968eedf43d274d7f7d86d979c6d3fb462b7bff3 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Fri, 17 Jan 2025 15:13:06 +0000 Subject: [PATCH 14/16] move contrib --- {contrib => hls4ml/contrib}/README.md | 0 {contrib => hls4ml/contrib}/__init__.py | 0 {contrib => hls4ml/contrib}/garnet.py | 0 {contrib => hls4ml/contrib}/kl_layer/README.md | 0 {contrib => hls4ml/contrib}/kl_layer/kl_layer.h | 0 {contrib => hls4ml/contrib}/kl_layer/kl_layer.py | 0 test/pytest/test_garnet.py | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) rename {contrib => hls4ml/contrib}/README.md (100%) rename {contrib => hls4ml/contrib}/__init__.py (100%) rename {contrib => hls4ml/contrib}/garnet.py (100%) rename {contrib => hls4ml/contrib}/kl_layer/README.md (100%) rename {contrib => hls4ml/contrib}/kl_layer/kl_layer.h (100%) rename {contrib => hls4ml/contrib}/kl_layer/kl_layer.py (100%) diff --git a/contrib/README.md b/hls4ml/contrib/README.md similarity index 100% rename from contrib/README.md rename to hls4ml/contrib/README.md diff --git a/contrib/__init__.py b/hls4ml/contrib/__init__.py similarity index 100% rename from contrib/__init__.py rename to hls4ml/contrib/__init__.py diff --git a/contrib/garnet.py b/hls4ml/contrib/garnet.py similarity index 100% rename from contrib/garnet.py rename to hls4ml/contrib/garnet.py diff --git a/contrib/kl_layer/README.md b/hls4ml/contrib/kl_layer/README.md similarity index 100% rename from contrib/kl_layer/README.md rename to hls4ml/contrib/kl_layer/README.md diff --git a/contrib/kl_layer/kl_layer.h b/hls4ml/contrib/kl_layer/kl_layer.h similarity index 100% rename from contrib/kl_layer/kl_layer.h rename to hls4ml/contrib/kl_layer/kl_layer.h diff --git a/contrib/kl_layer/kl_layer.py b/hls4ml/contrib/kl_layer/kl_layer.py similarity index 100% rename from contrib/kl_layer/kl_layer.py rename to hls4ml/contrib/kl_layer/kl_layer.py diff --git a/test/pytest/test_garnet.py b/test/pytest/test_garnet.py index 62bc82a8c0..057fe36c78 100644 --- a/test/pytest/test_garnet.py +++ b/test/pytest/test_garnet.py @@ -6,7 +6,7 @@ from tensorflow.keras.models import Model import hls4ml -from contrib.garnet import GarNet, GarNetStack +from hls4ml.contrib.garnet import GarNet, GarNetStack test_root_path = Path(__file__).parent From f7347f5fdadb6e9350e9ba0017b1f3dc620ef0b4 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Sat, 18 Jan 2025 19:44:34 +0000 Subject: [PATCH 15/16] bump HGQ req. ver --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 24175c9612..48b330e6c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ optional-dependencies.doc = [ "sphinx-github-changelog", "sphinx-rtd-theme", ] -optional-dependencies.HGQ = [ "hgq~=0.2.0" ] +optional-dependencies.HGQ = [ "hgq>=0.2.3" ] optional-dependencies.onnx = [ "onnx>=1.4" ] optional-dependencies.optimization = [ "keras-tuner==1.1.3", From 7fc65bebb558b68c57dc1eab4bc11d37004990b3 Mon Sep 17 00:00:00 2001 From: Chang Sun Date: Sat, 18 Jan 2025 19:50:37 +0000 Subject: [PATCH 16/16] incl. all in hls4ml/contrib in sdist --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 5bec5fe2a6..e3ee5ded3c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,5 +4,6 @@ graft test graft contrib recursive-include hls4ml/templates * recursive-include hls4ml *.py +recursive-include hls4ml/contrib * global-exclude .git .gitmodules .gitlab-ci.yml *.pyc include hls4ml/backends/vivado_accelerator/supported_boards.json