Skip to content

Commit 7063e87

Browse files
jmitrevsvloncar
andauthored
Fix inplace variables (#714)
* extract inplace variables from qonnx branch * fix up reshape batch dimension handling * add repack stream for quartus * fix typo in HLS pragma * pre-commint cleanup, and add missing check for pragma not None * Minor cosmetic fixes --------- Co-authored-by: Vladimir Loncar <[email protected]>
1 parent 48bcbbf commit 7063e87

File tree

17 files changed

+694
-266
lines changed

17 files changed

+694
-266
lines changed

hls4ml/backends/fpga/fpga_types.py

Lines changed: 187 additions & 82 deletions
Large diffs are not rendered by default.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from hls4ml.model.layers import Reshape
2+
from hls4ml.model.optimizer import OptimizerPass
3+
from hls4ml.model.types import InplaceTensorVariable
4+
5+
6+
class InplaceParallelReshape(OptimizerPass):
7+
"""
8+
Replaces the output variable of Reshape layer with an inplace variable when using io_parallel.
9+
10+
This is done because in io_parallel tensors are stored as flat arrays, requiring no reshaping.
11+
"""
12+
13+
def match(self, node):
14+
return isinstance(node, Reshape)
15+
16+
def transform(self, model, node):
17+
if model.config.get_config_value('IOType') != 'io_parallel':
18+
return False
19+
20+
outvar = node.get_output_variable()
21+
invar = node.get_input_variable()
22+
newoutvar = InplaceTensorVariable(outvar, invar)
23+
node.set_attr(node.outputs[0], newoutvar)
24+
return False
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from hls4ml.model.layers import Reshape
2+
from hls4ml.model.optimizer import OptimizerPass
3+
from hls4ml.model.types import InplaceTensorVariable
4+
5+
6+
class InplaceStreamFlatten(OptimizerPass):
7+
"""
8+
Replaces the output variable of Reshape (flatten) layer with an inplace variable when using io_stream.
9+
10+
This optimizer avoids the expensive repacking of the stream when Reshape layer flattens the tensor to 1d.
11+
"""
12+
13+
def match(self, node):
14+
# Reshape acts as a Flatten layer when the result has 1 dimension
15+
return isinstance(node, Reshape) and len(node.get_output_variable().shape) == 1
16+
17+
def transform(self, model, node):
18+
if model.config.get_config_value('IOType') != 'io_stream':
19+
return False
20+
21+
outvar = node.get_output_variable()
22+
invar = node.get_input_variable()
23+
newoutvar = InplaceTensorVariable(outvar, invar)
24+
node.set_attr(node.outputs[0], newoutvar)
25+
return False
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import numpy as np
2+
3+
from hls4ml.backends.template import FunctionCallTemplate
4+
from hls4ml.model.layers import Layer, Reshape, register_layer
5+
from hls4ml.model.optimizer import OptimizerPass
6+
7+
8+
class Repack(Layer):
9+
'''Inserted between layers with different packing factors.'''
10+
11+
def initialize(self):
12+
shape = self.attributes['target_shape']
13+
if shape[0] is None:
14+
shape = shape[1:]
15+
dims = [f'N_SIZE_{i}_{self.index}' for i in range(1, len(shape) + 1)]
16+
17+
self.add_output_variable(shape, dims)
18+
19+
20+
repack_function_template = 'nnet::repack_stream<{input_t}, {output_t}, {size}>({input}, {output});'
21+
repack_include_list = ['nnet_utils/nnet_stream.h']
22+
23+
24+
class RepackFunctionTemplate(FunctionCallTemplate):
25+
def __init__(self):
26+
super().__init__(Repack, include_header=repack_include_list)
27+
self.template = repack_function_template
28+
29+
def format(self, node):
30+
params = self._default_function_params(node)
31+
params['size'] = np.prod(node.get_output_variable().shape)
32+
33+
return self.template.format(**params)
34+
35+
36+
def register_repack_stream(backend):
37+
# Register the layer types to the layer map
38+
register_layer('Repack', Repack)
39+
40+
# Register the optimization passes
41+
backend.register_pass('reshape_stream', ReshapeStream)
42+
43+
# Register template passes
44+
backend.register_template(RepackFunctionTemplate)
45+
46+
47+
class ReshapeStream(OptimizerPass):
48+
'''Repacks stream for Reshape layer'''
49+
50+
def match(self, node):
51+
# do not run optimizer pass for a flatten layer (1 output dimension)
52+
return isinstance(node, Reshape) and len(node.get_output_variable().shape) > 1
53+
54+
def transform(self, model, node):
55+
if model.config.get_config_value('IOType') != 'io_stream':
56+
return False
57+
58+
attrs = {'target_shape': node.get_attr('target_shape')}
59+
60+
# Insert new Repack node instead of Reshape
61+
repack_layer = model.make_node(Repack, 'repack_' + node.name, attrs, node.inputs.copy())
62+
model.replace_node(node, repack_layer)
63+
64+
return True

hls4ml/backends/quartus/passes/transform_types.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,47 @@
1-
1+
from hls4ml.backends.fpga.fpga_types import (
2+
ACTypeConverter,
3+
HLSTypeConverter,
4+
QuartusArrayVariableConverter,
5+
QuartusInplaceArrayVariableConverter,
6+
QuartusInplaceStreamVariableConverter,
7+
QuartusStreamVariableConverter,
8+
QuartusStructMemberVariableConverter,
9+
StaticWeightVariableConverter,
10+
)
211
from hls4ml.model.optimizer import GlobalOptimizerPass
3-
from hls4ml.model.types import InplaceVariable
4-
from hls4ml.backends.fpga.fpga_types import ACTypeConverter, QuartusArrayVariableConverter, HLSTypeConverter, QuartusInplaceVariableConverter, QuartusStreamVariableConverter, QuartusStructMemberVariableConverter, StaticWeightVariableConverter
12+
from hls4ml.model.types import InplaceTensorVariable
13+
514

615
class TransformTypes(GlobalOptimizerPass):
716
def __init__(self):
817
self.type_converter = HLSTypeConverter(precision_converter=ACTypeConverter())
918
self.array_var_converter = QuartusArrayVariableConverter(type_converter=self.type_converter)
19+
self.inplace_array_var_converter = QuartusInplaceArrayVariableConverter(type_converter=self.type_converter)
1020
self.struct_var_converter = QuartusStructMemberVariableConverter(type_converter=self.type_converter)
1121
self.stream_var_converter = QuartusStreamVariableConverter(type_converter=self.type_converter)
22+
self.inplace_stream_var_converter = QuartusInplaceStreamVariableConverter(type_converter=self.type_converter)
1223
self.weight_var_converter = StaticWeightVariableConverter(type_converter=self.type_converter)
13-
self.inplace_var_converter = QuartusInplaceVariableConverter(type_converter=self.type_converter)
1424

1525
def transform(self, model, node):
1626
io_type = node.model.config.get_config_value('IOType')
1727

1828
for out_name, var in node.variables.items():
19-
if isinstance(var, InplaceVariable):
20-
new_var = self.inplace_var_converter.convert(var, io_type)
2129
if io_type == 'io_stream':
22-
new_var = self.stream_var_converter.convert(var)
30+
if isinstance(var, InplaceTensorVariable):
31+
new_var = self.inplace_stream_var_converter.convert(var)
32+
else:
33+
new_var = self.stream_var_converter.convert(var)
2334
elif io_type == 'io_parallel':
24-
if node.name in node.model.inputs:
35+
if out_name in node.model.inputs:
2536
new_var = self.struct_var_converter.convert(var, pragma='hls_register', struct_name='inputs')
26-
elif node.name in node.model.outputs:
37+
elif out_name in node.model.outputs:
2738
new_var = self.struct_var_converter.convert(var, pragma='hls_register', struct_name='outputs')
39+
elif isinstance(var, InplaceTensorVariable):
40+
new_var = self.inplace_array_var_converter.convert(var, pragma='')
2841
else:
2942
new_var = self.array_var_converter.convert(var, pragma='hls_register')
3043
else:
31-
raise Exception('Unknown IOType {} in {} ({})'.format(io_type, node.name, node.class_name))
44+
raise Exception(f'Unknown IOType {io_type} in {node.name} ({node.class_name})')
3245

3346
node.set_attr(out_name, new_var)
3447

hls4ml/backends/quartus/quartus_backend.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def _register_flows(self):
4545
initializers = self._get_layer_initializers()
4646
init_flow = register_flow('init_layers', initializers, requires=['optimize'], backend=self.name)
4747

48-
streaming_passes = ['quartus:clone_output']
48+
streaming_passes = ['quartus:reshape_stream', 'quartus:clone_output']
4949
streaming_flow = register_flow('streaming', streaming_passes, requires=[init_flow], backend=self.name)
5050

5151
quartus_types = [
@@ -63,7 +63,13 @@ def _register_flows(self):
6363
]
6464
quantization_flow = register_flow('quantization', quantization_passes, requires=[init_flow], backend=self.name)
6565

66-
optimization_passes = ['quartus:remove_final_reshape', 'quartus:optimize_pointwise_conv', 'quartus:skip_softmax']
66+
optimization_passes = [
67+
'quartus:remove_final_reshape',
68+
'quartus:optimize_pointwise_conv',
69+
'quartus:inplace_parallel_reshape',
70+
'quartus:inplace_stream_flatten',
71+
'quartus:skip_softmax',
72+
]
6773
optimization_flow = register_flow('optimize', optimization_passes, requires=[init_flow], backend=self.name)
6874

6975
templates = self._get_layer_templates()

hls4ml/backends/template.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
from hls4ml.model.optimizer.optimizer import OptimizerPass
32

43

@@ -9,7 +8,7 @@ def __init__(self, name, layer_class, attribute_name):
98
if not isinstance(self.layer_class, (list, tuple, set)):
109
self.layer_class = [self.layer_class]
1110
self.attribute_name = attribute_name
12-
11+
1312
def match(self, node):
1413
for layer_cls in self.layer_class:
1514
if node.class_name == layer_cls.__name__:
@@ -20,13 +19,14 @@ def transform(self, model, node):
2019
formatted_template = self.format(node)
2120
node.set_attr(self.attribute_name, formatted_template)
2221
return False
23-
22+
2423
def format(self, node):
2524
raise NotImplementedError
2625

2726
def get_name(self):
2827
return self.name
29-
28+
29+
3030
class LayerConfigTemplate(Template):
3131
def __init__(self, layer_class):
3232
if isinstance(layer_class, (list, tuple, set)):
@@ -35,7 +35,7 @@ def __init__(self, layer_class):
3535
name = layer_class.__name__.lower()
3636
name += '_config_template'
3737
super().__init__(name, layer_class, 'config_cpp')
38-
38+
3939
def _default_config_params(self, layer):
4040
params = {}
4141
params.update(layer.attributes)
@@ -44,6 +44,7 @@ def _default_config_params(self, layer):
4444

4545
return params
4646

47+
4748
class FunctionCallTemplate(Template):
4849
def __init__(self, layer_class, include_header=None):
4950
if isinstance(layer_class, (list, tuple, set)):
@@ -52,12 +53,15 @@ def __init__(self, layer_class, include_header=None):
5253
name = layer_class.__name__.lower()
5354
name += '_function_template'
5455
super().__init__(name, layer_class, 'function_cpp')
55-
self.include_header = include_header
56-
56+
if include_header is None:
57+
self.include_header = ()
58+
else:
59+
self.include_header = include_header
60+
5761
def _default_function_params(self, layer):
5862
params = {}
5963
params.update(layer.attributes)
60-
params['config'] = 'config{}'.format(layer.index)
64+
params['config'] = f'config{layer.index}'
6165
params['input_t'] = layer.get_input_variable().type.name
6266
params['output_t'] = layer.get_output_variable().type.name
6367
params['input'] = layer.get_input_variable().name

0 commit comments

Comments
 (0)