Skip to content

Commit 9857da1

Browse files
calad0ivloncarJanFSchulte
authored
Lazy converter imports and migrate to pyproject.toml (#1094)
* import converter dependencies lazily * make tf and qkeras optionl, stop assuming keras is tf.keras * less mandatory dependency * fix dsp_aware_pruning test import path * fix broken setup.cfg after rebase, rm pyparsing * purge qkeras workaround * switch to pyproject.toml switch to pyproject.toml include pyproject.toml after install * format * rm useless flake8 config in pyprject.toml * Add hint on import failure * leftover * rm setup.py from manifest * manifest fix 2 * move contrib * bump HGQ req. ver * incl. all in hls4ml/contrib in sdist * remove excess @requires, update hgq dep flag * style fix * use dynamic version * Use the cli as a python module instead of hardcoded path * It's called "quartus" not "quantus" * It's called "quartus" not "quantus" part2 * Forgot to commit the __main__ of cli * Import numerical instead of using a full name --------- Co-authored-by: Vladimir Loncar <[email protected]> Co-authored-by: Jan-Frederik Schulte <[email protected]>
1 parent 1ad1ad9 commit 9857da1

38 files changed

+285
-228
lines changed

.pre-commit-config.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ repos:
99
args: ['--line-length=125',
1010
'--skip-string-normalization']
1111

12+
- repo: https://github.com/tox-dev/pyproject-fmt
13+
rev: v2.5.0
14+
hooks:
15+
- id: pyproject-fmt
16+
1217
- repo: https://github.com/pre-commit/pre-commit-hooks
1318
rev: v5.0.0
1419
hooks:
1520
- id: check-added-large-files
1621
- id: check-case-conflict
1722
- id: check-merge-conflict
1823
- id: check-symlinks
24+
- id: check-toml
1925
- id: check-yaml
2026
- id: debug-statements
2127
- id: end-of-file-fixer
@@ -27,19 +33,13 @@ repos:
2733
rev: 5.13.2
2834
hooks:
2935
- id: isort
30-
args: ["--profile", "black", --line-length=125]
3136

3237
- repo: https://github.com/asottile/pyupgrade
3338
rev: v3.19.1
3439
hooks:
3540
- id: pyupgrade
3641
args: ["--py36-plus"]
3742

38-
- repo: https://github.com/asottile/setup-cfg-fmt
39-
rev: v2.7.0
40-
hooks:
41-
- id: setup-cfg-fmt
42-
4343
- repo: https://github.com/pycqa/flake8
4444
rev: 7.1.1
4545
hooks:

MANIFEST.in

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
include LICENSE README.md CONTRIBUTING.md CITATION.cff pyproject.toml setup.py setup.cfg .clang-format
1+
include LICENSE README.md CONTRIBUTING.md CITATION.cff pyproject.toml .clang-format
22
graft example-models
33
graft test
44
graft contrib
55
recursive-include hls4ml/templates *
6-
global-exclude .git .gitmodules .gitlab-ci.yml
6+
recursive-include hls4ml *.py
7+
recursive-include hls4ml/contrib *
8+
global-exclude .git .gitmodules .gitlab-ci.yml *.pyc
79
include hls4ml/backends/vivado_accelerator/supported_boards.json

hls4ml/__init__.py

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,3 @@
1-
# Temporary workaround for QKeras installation requirement, will be removed after 1.0.0
2-
def maybe_install_qkeras():
3-
import subprocess
4-
import sys
5-
6-
QKERAS_PKG_NAME = 'QKeras'
7-
# QKERAS_PKG_SOURCE = QKERAS_PKG_NAME
8-
QKERAS_PKG_SOURCE = 'qkeras@git+https://github.com/fastmachinelearning/qkeras.git'
9-
10-
def pip_list():
11-
p = subprocess.run([sys.executable, '-m', 'pip', 'list'], check=True, capture_output=True)
12-
return p.stdout.decode()
13-
14-
def pip_install(package):
15-
subprocess.check_call([sys.executable, '-m', 'pip', 'install', package])
16-
17-
all_pkgs = pip_list()
18-
if QKERAS_PKG_NAME not in all_pkgs:
19-
print('QKeras installation not found, installing one...')
20-
pip_install(QKERAS_PKG_SOURCE)
21-
print('QKeras installed.')
22-
23-
24-
try:
25-
maybe_install_qkeras()
26-
except Exception:
27-
print('Could not find QKeras installation, make sure you have QKeras installed.')
28-
29-
# End of workaround
30-
311
from hls4ml import converters, report, utils # noqa: F401, E402
322

333
try:
File renamed without changes.

hls4ml/cli/__main__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from . import main
2+
3+
main()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

hls4ml/converters/__init__.py

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import importlib
22
import os
3-
import warnings
43

54
import yaml
65

@@ -10,33 +9,19 @@
109
from hls4ml.converters.keras_to_hls import get_supported_keras_layers # noqa: F401
1110
from hls4ml.converters.keras_to_hls import parse_keras_model # noqa: F401
1211
from hls4ml.converters.keras_to_hls import keras_to_hls, register_keras_layer_handler
12+
from hls4ml.converters.onnx_to_hls import get_supported_onnx_layers # noqa: F401
1313
from hls4ml.converters.onnx_to_hls import parse_onnx_model # noqa: F401
14+
from hls4ml.converters.onnx_to_hls import onnx_to_hls, register_onnx_layer_handler
15+
from hls4ml.converters.pytorch_to_hls import ( # noqa: F401
16+
get_supported_pytorch_layers,
17+
pytorch_to_hls,
18+
register_pytorch_layer_handler,
19+
)
1420
from hls4ml.model import ModelGraph
1521
from hls4ml.utils.config import create_config
22+
from hls4ml.utils.dependency import requires
1623
from hls4ml.utils.symbolic_utils import LUTFunction
1724

18-
# ----------Make converters available if the libraries can be imported----------#
19-
try:
20-
from hls4ml.converters.pytorch_to_hls import ( # noqa: F401
21-
get_supported_pytorch_layers,
22-
pytorch_to_hls,
23-
register_pytorch_layer_handler,
24-
)
25-
26-
__pytorch_enabled__ = True
27-
except ImportError:
28-
warnings.warn("WARNING: Pytorch converter is not enabled!", stacklevel=1)
29-
__pytorch_enabled__ = False
30-
31-
try:
32-
from hls4ml.converters.onnx_to_hls import get_supported_onnx_layers # noqa: F401
33-
from hls4ml.converters.onnx_to_hls import onnx_to_hls, register_onnx_layer_handler
34-
35-
__onnx_enabled__ = True
36-
except ImportError:
37-
warnings.warn("WARNING: ONNX converter is not enabled!", stacklevel=1)
38-
__onnx_enabled__ = False
39-
4025
# ----------Layer handling register----------#
4126
model_types = ['keras', 'pytorch', 'onnx']
4227

@@ -51,7 +36,7 @@
5136
# and has 'handles' attribute
5237
# and is defined in this module (i.e., not imported)
5338
if callable(func) and hasattr(func, 'handles') and func.__module__ == lib.__name__:
54-
for layer in func.handles:
39+
for layer in func.handles: # type: ignore
5540
if model_type == 'keras':
5641
register_keras_layer_handler(layer, func)
5742
elif model_type == 'pytorch':
@@ -93,10 +78,10 @@ def parse_yaml_config(config_file):
9378
"""
9479

9580
def construct_keras_model(loader, node):
96-
from tensorflow.keras.models import load_model
97-
9881
model_str = loader.construct_scalar(node)
99-
return load_model(model_str)
82+
import keras
83+
84+
return keras.models.load_model(model_str)
10085

10186
yaml.add_constructor('!keras_model', construct_keras_model, Loader=yaml.SafeLoader)
10287

@@ -124,15 +109,9 @@ def convert_from_config(config):
124109

125110
model = None
126111
if 'OnnxModel' in yamlConfig:
127-
if __onnx_enabled__:
128-
model = onnx_to_hls(yamlConfig)
129-
else:
130-
raise Exception("ONNX not found. Please install ONNX.")
112+
model = onnx_to_hls(yamlConfig)
131113
elif 'PytorchModel' in yamlConfig:
132-
if __pytorch_enabled__:
133-
model = pytorch_to_hls(yamlConfig)
134-
else:
135-
raise Exception("PyTorch not found. Please install PyTorch.")
114+
model = pytorch_to_hls(yamlConfig)
136115
else:
137116
model = keras_to_hls(yamlConfig)
138117

@@ -174,6 +153,7 @@ def _check_model_config(model_config):
174153
return model_config
175154

176155

156+
@requires('_keras')
177157
def convert_from_keras_model(
178158
model,
179159
output_dir='my-hls-test',
@@ -237,6 +217,7 @@ def convert_from_keras_model(
237217
return keras_to_hls(config)
238218

239219

220+
@requires('_torch')
240221
def convert_from_pytorch_model(
241222
model,
242223
output_dir='my-hls-test',
@@ -308,6 +289,7 @@ def convert_from_pytorch_model(
308289
return pytorch_to_hls(config)
309290

310291

292+
@requires('onnx')
311293
def convert_from_onnx_model(
312294
model,
313295
output_dir='my-hls-test',
@@ -371,6 +353,7 @@ def convert_from_onnx_model(
371353
return onnx_to_hls(config)
372354

373355

356+
@requires('sr')
374357
def convert_from_symbolic_expression(
375358
expr,
376359
n_symbols=None,

hls4ml/converters/keras/qkeras.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from qkeras.quantizers import get_quantizer
2-
31
from hls4ml.converters.keras.convolution import parse_conv1d_layer, parse_conv2d_layer
42
from hls4ml.converters.keras.core import parse_batchnorm_layer, parse_dense_layer
53
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):
8886

8987
@keras_handler('QActivation')
9088
def parse_qactivation_layer(keras_layer, input_names, input_shapes, data_reader):
89+
from qkeras.quantizers import get_quantizer
90+
9191
assert keras_layer['class_name'] == 'QActivation'
9292
supported_activations = [
9393
'quantized_relu',

hls4ml/converters/keras_to_hls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,9 @@ def get_model_arch(config):
160160
# Model instance passed in config from API
161161
keras_model = config['KerasModel']
162162
if isinstance(keras_model, str):
163-
from tensorflow.keras.models import load_model
163+
import keras
164164

165-
keras_model = load_model(keras_model)
165+
keras_model = keras.models.load_model(keras_model)
166166
model_arch = json.loads(keras_model.to_json())
167167
reader = KerasModelReader(keras_model)
168168
elif 'KerasJson' in config:

hls4ml/converters/onnx_to_hls.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import onnx
2-
from onnx import helper, numpy_helper
3-
41
from hls4ml.model import ModelGraph
2+
from hls4ml.utils.dependency import requires
53

64

75
# ----------------------Helpers---------------------
@@ -21,6 +19,8 @@ def replace_char_inconsitency(name):
2119

2220

2321
def get_onnx_attribute(operation, name, default=None):
22+
from onnx import helper
23+
2424
attr = next((x for x in operation.attribute if x.name == name), None)
2525
if attr is None:
2626
value = default
@@ -76,6 +76,8 @@ def get_input_shape(graph, node):
7676

7777
def get_constant_value(graph, constant_name):
7878
tensor = next((x for x in graph.initializer if x.name == constant_name), None)
79+
from onnx import numpy_helper
80+
7981
return numpy_helper.to_array(tensor)
8082

8183

@@ -257,6 +259,7 @@ def parse_onnx_model(onnx_model):
257259
return layer_list, input_layers, output_layers
258260

259261

262+
@requires('onnx')
260263
def onnx_to_hls(config):
261264
"""Convert onnx model to hls model from configuration.
262265
@@ -273,6 +276,8 @@ def onnx_to_hls(config):
273276
# Extract model architecture
274277
print('Interpreting Model ...')
275278

279+
import onnx
280+
276281
onnx_model = onnx.load(config['OnnxModel']) if isinstance(config['OnnxModel'], str) else config['OnnxModel']
277282

278283
layer_list, input_layers, output_layers = parse_onnx_model(onnx_model)

hls4ml/converters/pytorch_to_hls.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import numpy as np
2-
import torch
32

43
from hls4ml.model import ModelGraph
4+
from hls4ml.utils.dependency import requires
55

66

77
class PyTorchModelReader:
@@ -27,6 +27,8 @@ def get_weights_data(self, layer_name, var_name):
2727

2828
class PyTorchFileReader(PyTorchModelReader): # Inherit get_weights_data method
2929
def __init__(self, config):
30+
import torch
31+
3032
self.config = config
3133

3234
if not torch.cuda.is_available():
@@ -116,6 +118,8 @@ def parse_pytorch_model(config, verbose=True):
116118
Returns:
117119
ModelGraph: hls4ml model object.
118120
"""
121+
import torch
122+
from torch.fx import symbolic_trace
119123

120124
# This is a list of dictionaries to hold all the layer info we need to generate HLS
121125
layer_list = []
@@ -135,7 +139,6 @@ def parse_pytorch_model(config, verbose=True):
135139
# dict of layer objects in non-traced form for access lateron
136140
children = {c[0]: c[1] for c in model.named_children()}
137141
# use symbolic_trace to get a full graph of the model
138-
from torch.fx import symbolic_trace
139142

140143
traced_model = symbolic_trace(model)
141144
# Define layers to skip for conversion to HLS
@@ -399,6 +402,7 @@ def parse_pytorch_model(config, verbose=True):
399402
return layer_list, input_layers
400403

401404

405+
@requires('_torch')
402406
def pytorch_to_hls(config):
403407
layer_list, input_layers = parse_pytorch_model(config)
404408
print('Creating HLS model')

hls4ml/model/__init__.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1 @@
11
from hls4ml.model.graph import HLSConfig, ModelGraph # noqa: F401
2-
3-
try:
4-
from hls4ml.model import profiling # noqa: F401
5-
6-
__profiling_enabled__ = True
7-
except ImportError:
8-
__profiling_enabled__ = False

hls4ml/model/optimizer/passes/qkeras.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import numpy as np
2-
import tensorflow as tf
32

43
from hls4ml.model.layers import ApplyAlpha
54
from hls4ml.model.optimizer import ConfigurableOptimizerPass, OptimizerPass, register_pass
@@ -113,6 +112,8 @@ def match(self, node):
113112
def transform(self, model, node):
114113
# The quantizer has to be applied to set the scale attribute
115114
# This must be applied to the _unquantized_ weights to obtain the correct scale
115+
import tensorflow as tf
116+
116117
quantizer = node.weights['weight'].quantizer.quantizer_fn # get QKeras quantizer
117118
weights = node.weights['weight'].data_unquantized # get weights
118119
qweights = quantizer(tf.convert_to_tensor(weights))

0 commit comments

Comments
 (0)