diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0601a84b2d..9380ac1689 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.0 @@ -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..5bec5fe2a6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,8 @@ -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 .clang-format 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 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: 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/hls4ml/converters/__init__.py b/hls4ml/converters/__init__.py index 3d7ce1fe56..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': @@ -93,10 +78,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) @@ -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/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..d51701e726 100644 --- a/hls4ml/converters/onnx_to_hls.py +++ b/hls4ml/converters/onnx_to_hls.py @@ -1,7 +1,5 @@ -import onnx -from onnx import helper, numpy_helper - from hls4ml.model import ModelGraph +from hls4ml.utils.dependency import requires # ----------------------Helpers--------------------- @@ -20,7 +18,10 @@ def replace_char_inconsitency(name): return name.replace('.', '_') +@requires('onnx') 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 @@ -74,8 +75,11 @@ 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 + return numpy_helper.to_array(tensor) @@ -257,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. @@ -273,6 +278,8 @@ 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 79ca1fa5c6..f279a1970a 100644 --- a/hls4ml/converters/pytorch_to_hls.py +++ b/hls4ml/converters/pytorch_to_hls.py @@ -1,6 +1,5 @@ -import torch - from hls4ml.model import ModelGraph +from hls4ml.utils.dependency import requires class PyTorchModelReader: @@ -24,8 +23,11 @@ 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 + self.config = config if not torch.cuda.is_available(): @@ -103,6 +105,7 @@ def decorator(function): # ---------------------------------------------------------------- +@requires('_torch') def parse_pytorch_model(config, verbose=True): """Convert PyTorch model to hls4ml ModelGraph. @@ -368,6 +371,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/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 84a83de23e..6def53f7d1 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 + +__keras_activations = list() +if __keras_profiling_enabled__: + __keras_activations.append(keras.layers.Activation) +if __qkeras_profiling_enabled__: + __keras_activations.append(qkeras.QActivation) + def get_unoptimized_hlsmodel(model): from hls4ml.converters import convert_from_config @@ -482,7 +494,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.Sequential): data = weights_torch(model, fmt='summary', plot=plot) @@ -520,7 +532,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) @@ -590,7 +602,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, tuple(__keras_activations)) and layer.activation.__name__ != 'linear' ): tmp_activation = layer.activation diff --git a/hls4ml/model/quantizers.py b/hls4ml/model/quantizers.py index a5b9ceb8c4..eb313fc4ea 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, @@ -16,6 +14,7 @@ SaturationMode, XnorPrecisionType, ) +from hls4ml.utils.dependency import requires class Quantizer: @@ -86,7 +85,10 @@ class QKerasQuantizer(Quantizer): config (dict): Config of the QKeras quantizer to wrap. """ + @requires('qkeras') 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 +108,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): @@ -131,7 +133,10 @@ 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 + 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 +146,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) @@ -153,15 +158,18 @@ class QKerasPO2Quantizer(Quantizer): config (dict): Config of the QKeras quantizer to wrap. """ + @requires('qkeras') 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. 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/report/quartus_report.py b/hls4ml/report/quartus_report.py index c337e5de10..677a931402 100644 --- a/hls4ml/report/quartus_report.py +++ b/hls4ml/report/quartus_report.py @@ -2,8 +2,7 @@ import webbrowser from ast import literal_eval -from calmjs.parse import asttypes, es5 -from tabulate import tabulate +from hls4ml.utils.dependency import requires def parse_quartus_report(hls_dir, write_to_file=True): @@ -42,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. @@ -53,6 +53,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') @@ -90,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. @@ -100,6 +103,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/hls4ml/utils/config.py b/hls4ml/utils/config.py index e450084095..8c8ff3a069 100644 --- a/hls4ml/utils/config.py +++ b/hls4ml/utils/config.py @@ -1,8 +1,7 @@ import json -import qkeras - 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): @@ -46,8 +45,11 @@ 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 + quantizer_obj = qkeras.get_quantizer(quantizer) quantizer = {} # Some activations are classes with get_config method 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 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/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')) 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: diff --git a/pyproject.toml b/pyproject.toml index 6402ab0e7a..24175c9612 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,100 @@ [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.onnx = [ "onnx>=1.4" ] +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.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 0b81e7b592..0000000000 --- a/setup.cfg +++ /dev/null @@ -1,70 +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 = - calmjs.parse - h5py - numpy - onnx>=1.4.0 - 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 -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 -sr = - sympy -testing = - HGQ~=0.2.0 - pytest - pytest-cov - pytest-randomly - qonnx - 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() 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