Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infrastructure for saving/loading hls4ml models #1158

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/api/serialization.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
============================
Saving/Loading hls4ml models
============================

``hls4ml`` model objects (instances of ``ModelGraph`` class) can be serialized to disk and loaded at a later stage. The saved model doesn't require original Keras/PyTorch/ONNX model for loading.

To save/load a model use the following API:

.. code-block:: python

from hls4ml.converters import convert_from_keras_model
from hls4ml.utils.serialization import serialize_model, deserialize_model

model = convert_from_keras_model(keras_model, ...)

# Save a model to some path
serialize_model(model, 'some/path/my_hls4ml_model.fml')

# Load a model from a file
loaded_model = deserialize_model('some/path/my_hls4ml_model.fml')


Saved model will have a ``.fml`` extension, but is in fact a gzipped tar archive. Loaded model can be used in the same way as the original one. This includes modification of certain config parameters, for example output directory, layer reuse factor etc.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
api/concepts
api/configuration
api/command
api/serialization

.. toctree::
:hidden:
Expand Down
6 changes: 5 additions & 1 deletion hls4ml/backends/fpga/fpga_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,12 @@ def create_layer_class(self, layer_class):
if issubclass(layer_class, cls):
new_attrubutes.extend(attributes)

layer_cls_fqn = layer_class.__module__ + '.' + layer_class.__qualname__

return type(
self.name + layer_class.__name__, (layer_class,), {'_expected_attributes': new_attrubutes, '_wrapped': True}
self.name + layer_class.__name__,
(layer_class,),
{'_expected_attributes': new_attrubutes, '_wrapped': layer_cls_fqn},
)

def compile(self, model):
Expand Down
33 changes: 26 additions & 7 deletions hls4ml/backends/fpga/fpga_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def __init__(self, type_map, prefix):
def convert(self, precision_type):
type_cls = type(precision_type)
type_cls_name = type_cls.__name__
type_cls_fqn = type_cls.__module__ + '.' + type_cls.__qualname__

# If the type is already converted, do nothing
if type_cls_name.startswith(self.prefix):
Expand All @@ -111,7 +112,9 @@ def convert(self, precision_type):
definition_cls = self.type_map.get(type_cls, None)

if definition_cls is not None:
precision_type.__class__ = type(self.prefix + type_cls_name, (type_cls, definition_cls), {})
precision_type.__class__ = type(
self.prefix + type_cls_name, (type_cls, definition_cls), {'_wrapped': type_cls_fqn}
)
return precision_type
else:
raise Exception(f'Cannot convert precision type to {self.prefix}: {precision_type.__class__.__name__}')
Expand Down Expand Up @@ -206,6 +209,7 @@ def __init__(self, precision_converter):
def convert(self, atype):
type_cls = type(atype)
type_cls_name = type_cls.__name__
type_cls_fqn = type_cls.__module__ + '.' + type_cls.__qualname__

# If the type is already converted, do nothing
if type_cls_name.startswith('HLS'):
Expand All @@ -214,7 +218,7 @@ def convert(self, atype):
conversion_cls = self.type_map.get(type_cls, None)

if conversion_cls is not None:
atype.__class__ = type('HLS' + type_cls_name, (type_cls, conversion_cls), {})
atype.__class__ = type('HLS' + type_cls_name, (type_cls, conversion_cls), {'_wrapped': type_cls_fqn})
atype.convert_precision(self.precision_converter)
return atype
else:
Expand Down Expand Up @@ -246,8 +250,11 @@ def convert(self, tensor_var, pragma='partition'):

tensor_var.pragma = pragma
tensor_var.type = self.type_converter.convert(tensor_var.type)
tensor_cls_fqn = tensor_var.__class__.__module__ + '.' + tensor_var.__class__.__qualname__

tensor_var.__class__ = type(self.prefix + 'ArrayVariable', (type(tensor_var), self.definition_cls), {})
tensor_var.__class__ = type(
self.prefix + 'ArrayVariable', (type(tensor_var), self.definition_cls), {'_wrapped': tensor_cls_fqn}
)
return tensor_var


Expand All @@ -273,8 +280,11 @@ def convert(self, tensor_var, pragma='partition', struct_name=None):
tensor_var.struct_name = str(struct_name)
tensor_var.member_name = tensor_var.name
tensor_var.name = tensor_var.struct_name + '.' + tensor_var.member_name
type_cls_fqn = tensor_var.__class__.__module__ + '.' + tensor_var.__class__.__qualname__

tensor_var.__class__ = type(self.prefix + 'StructMemberVariable', (type(tensor_var), self.definition_cls), {})
tensor_var.__class__ = type(
self.prefix + 'StructMemberVariable', (type(tensor_var), self.definition_cls), {'_wrapped': type_cls_fqn}
)
return tensor_var


Expand All @@ -299,8 +309,11 @@ def convert(self, tensor_var, n_pack=1, depth=0):
tensor_var.type = self.type_converter.convert(
PackedType(tensor_var.type.name, tensor_var.type.precision, tensor_var.shape[-1], n_pack)
)
tensor_cls_fqn = tensor_var.__class__.__module__ + '.' + tensor_var.__class__.__qualname__

tensor_var.__class__ = type(self.prefix + 'StreamVariable', (type(tensor_var), self.definition_cls), {})
tensor_var.__class__ = type(
self.prefix + 'StreamVariable', (type(tensor_var), self.definition_cls), {'_wrapped': tensor_cls_fqn}
)
return tensor_var


Expand All @@ -318,8 +331,11 @@ def convert(self, tensor_var, n_pack=1, depth=0):
tensor_var.type = self.type_converter.convert(
PackedType(tensor_var.type.name, tensor_var.type.precision, tensor_var.input_var.shape[-1], n_pack)
)
tensor_cls_fqn = tensor_var.__class__.__module__ + '.' + tensor_var.__class__.__qualname__

tensor_var.__class__ = type(self.prefix + 'StreamVariable', (type(tensor_var), self.definition_cls), {})
tensor_var.__class__ = type(
self.prefix + 'StreamVariable', (type(tensor_var), self.definition_cls), {'_wrapped': tensor_cls_fqn}
)
return tensor_var


Expand All @@ -344,8 +360,11 @@ def convert(self, weight_var):
weight_var.weight_class = weight_var.__class__.__name__
weight_var.storage = 'register'
weight_var.type = self.type_converter.convert(weight_var.type)
tensor_cls_fqn = weight_var.__class__.__module__ + '.' + weight_var.__class__.__qualname__

weight_var.__class__ = type('StaticWeightVariable', (type(weight_var), StaticWeightVariableDefinition), {})
weight_var.__class__ = type(
'StaticWeightVariable', (type(weight_var), StaticWeightVariableDefinition), {'_wrapped': tensor_cls_fqn}
)
return weight_var


Expand Down
2 changes: 1 addition & 1 deletion hls4ml/converters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,6 @@ def convert_from_symbolic_expression(

config['HLSConfig'] = {'Model': {'Precision': precision, 'ReuseFactor': 1}}

hls_model = ModelGraph(config, layer_list)
hls_model = ModelGraph.from_layer_list(config, layer_list)

return hls_model
2 changes: 1 addition & 1 deletion hls4ml/converters/keras_to_hls.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,5 +326,5 @@ def keras_to_hls(config):
model_arch, reader = get_model_arch(config)
layer_list, input_layers, output_layers, _ = parse_keras_model(model_arch, reader)
print('Creating HLS model')
hls_model = ModelGraph(config, layer_list, input_layers, output_layers)
hls_model = ModelGraph.from_layer_list(config, layer_list, input_layers, output_layers)
return hls_model
2 changes: 1 addition & 1 deletion hls4ml/converters/onnx_to_hls.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,5 +282,5 @@ def onnx_to_hls(config):
#################

print('Creating HLS model')
hls_model = ModelGraph(config, layer_list, input_layers, output_layers)
hls_model = ModelGraph.from_layer_list(config, layer_list, input_layers, output_layers)
return hls_model
2 changes: 1 addition & 1 deletion hls4ml/converters/pytorch_to_hls.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,5 +371,5 @@ def parse_pytorch_model(config, verbose=True):
def pytorch_to_hls(config):
layer_list, input_layers = parse_pytorch_model(config)
print('Creating HLS model')
hls_model = ModelGraph(config, layer_list, inputs=input_layers)
hls_model = ModelGraph.from_layer_list(config, layer_list, inputs=input_layers)
return hls_model
Loading
Loading