Skip to content

Commit 8e8c450

Browse files
Merge remote-tracking branch 'upstream/main' into conv_tr_parallel
2 parents d1d1e3e + 19c541a commit 8e8c450

34 files changed

+993
-494
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
A package for machine learning inference in FPGAs. We create firmware implementations of machine learning algorithms using high level synthesis language (HLS). We translate traditional open-source machine learning package models into HLS that can be configured for your use-case!
1010

11-
**Contact:** hls4ml.help@gmail.com
11+
If you have any questions, comments, or ideas regarding hls4ml or just want to show us how you use hls4ml, don't hesitate to reach us through the [discussions](https://github.com/fastmachinelearning/hls4ml/discussions) tab.
1212

1313
# Documentation & Tutorial
1414

docs/index.rst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,7 @@ Welcome to hls4ml's documentation!
3535

3636
``hls4ml`` is a Python package for machine learning inference in FPGAs. We create firmware implementations of machine learning algorithms using high level synthesis language (HLS). We translate traditional open-source machine learning package models into HLS that can be configured for your use-case!
3737

38-
The project is currently in development, so please let us know if you are interested, your experiences with the package, and if you would like new features to be added.
39-
40-
Contact: hls4ml.help@gmail.com
38+
The project is currently in development, so please let us know if you are interested, your experiences with the package, and if you would like new features to be added. You can reach us through our GitHub page.
4139

4240

4341
Project Status

hls4ml/backends/backend.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,12 @@ def _init_class_optimizers(self):
2525
return class_optimizers
2626

2727
def _init_file_optimizers(self):
28-
opt_path = os.path.dirname(inspect.getfile(self.__class__)) + '/passes'
29-
module_path = self.__module__[:self.__module__.rfind('.')] + '.passes'
30-
file_optimizers = extract_optimizers_from_path(opt_path, module_path, self)
31-
for base in self.__class__.__bases__:
32-
opt_path = os.path.dirname(inspect.getfile(base)) + '/passes'
33-
module_path = base.__module__[:base.__module__.rfind('.')] + '.passes'
34-
base_optimizers = extract_optimizers_from_path(opt_path, module_path, self)
35-
file_optimizers.update(base_optimizers)
28+
file_optimizers = {}
29+
for cls in [*self.__class__.__bases__, self.__class__]:
30+
opt_path = os.path.dirname(inspect.getfile(cls)) + '/passes'
31+
module_path = cls.__module__[:cls.__module__.rfind('.')] + '.passes'
32+
cls_optimizers = extract_optimizers_from_path(opt_path, module_path, self)
33+
file_optimizers.update(cls_optimizers)
3634
return file_optimizers
3735

3836
def _get_layer_initializers(self):
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import json
2+
3+
from pyDigitalWaveTools.vcd.parser import VcdParser
4+
5+
from hls4ml.model.optimizer.optimizer import ConfigurableOptimizerPass, ModelOptimizerPass
6+
7+
8+
def populate_values(values, name, data, depth):
9+
values.append({'name': name, 'data': [], 'max': 0, 'depth': 0})
10+
get_values = lambda x: int(x[1][1:], 2)
11+
values[-1]['data'] = [get_values(x) for x in data]
12+
values[-1]['max'] = max(values[-1]['data'])
13+
values[-1]['depth'] = int(depth[0][1][1:], 2)
14+
return values
15+
16+
17+
def set_big_fifos(vars_to_profile, profiling_fifo_depth):
18+
for k, v in vars_to_profile.items():
19+
v.pragma = (v.pragma[0], profiling_fifo_depth)
20+
21+
22+
def get_vcd_data(model):
23+
model.write()
24+
model.build(reset=False, csim=True, synth=True, cosim=True, validation=False, export=False, vsynth=False,
25+
fifo_opt=True)
26+
27+
with open(
28+
model.config.get_output_dir() + '/' + model.config.get_project_name() + '_prj' + '/solution1/sim/verilog/fifo_opt.vcd') as vcd_file:
29+
vcd = VcdParser()
30+
vcd.parse(vcd_file)
31+
data = vcd.scope.toJson()
32+
return data
33+
34+
35+
def generate_max_depth_file(model, maxs):
36+
with open(model.config.get_output_dir() + '/max_depth.json', 'w') as f:
37+
json.dump(maxs, f, indent=4)
38+
39+
40+
def set_fifo_depth(model, maxs):
41+
for k, v in model.output_vars.items():
42+
filtered_max = [x['max'] for x in maxs if v.name in x['name']]
43+
if len(filtered_max) == 0:
44+
continue
45+
if len(filtered_max) > 1:
46+
print('WARNING! Check names of FIFOs')
47+
v.pragma = (v.pragma[0], filtered_max[0] + 1)
48+
49+
50+
class FifoDepthOptimization(ConfigurableOptimizerPass, ModelOptimizerPass):
51+
def __init__(self):
52+
self.values = []
53+
54+
def transform(self, model):
55+
# use `large_fifo_depth = 0` to keep the default fifo depth
56+
profiling_fifo_depth = getattr(self, 'profiling_fifo_depth', 100_000)
57+
58+
# check axi-stream or io-stream, if not one the 2 exit
59+
if not (model.config.get_config_value('IOType') == 'io_stream'):
60+
raise Exception('To use this optimization you have to set `IOType` field to `io_stream` in the HLS config')
61+
62+
# initialize all the fifos to `profiling_fifo_depth` so that they will be automatically implemented in BRAMs
63+
# and so they will be profiled
64+
if profiling_fifo_depth:
65+
vars_to_profile = {k: v for k, v in model.output_vars.items() if v != model.get_output_variables()[0] and
66+
v != model.get_input_variables()[0]}
67+
68+
set_big_fifos(vars_to_profile, profiling_fifo_depth)
69+
70+
data = get_vcd_data(model)
71+
72+
if len(data['children']) == 0:
73+
print("FIFO depth optimization found no FIFOs implemented using BRAMs in the design, no optimization is possible. Consider increasing profiling_fifo_depth.")
74+
return False
75+
76+
n_elem = len(data['children'][0]['children'][0]['children'])
77+
for i in range(n_elem):
78+
name = data['children'][0]['children'][0]['children'][i]['name']
79+
data_p = data['children'][0]['children'][0]['children'][i]['children'][0]['data']
80+
depth = data['children'][0]['children'][0]['children'][i]['children'][1]['data']
81+
populate_values(self.values, name, data_p, depth)
82+
83+
maxs = [{'name': i['name'], 'max': i['max'], 'depth': i['depth']} for i in self.values]
84+
85+
generate_max_depth_file(model, maxs)
86+
87+
set_fifo_depth(model, maxs)
88+
89+
print('[hls4ml] - FIFO optimization completed')
90+
return False

hls4ml/backends/vivado/passes/pooling_templates.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
static const unsigned stride_width = {stride_width};
1919
static const nnet::Pool_Op pool_op = nnet::{pool_op};
2020
static const nnet::conv_implementation implementation = nnet::conv_implementation::{implementation};
21-
static const unsigned reuse = {reuse};
21+
static const unsigned reuse_factor = {reuse};
2222
typedef {accum_t.name} accum_t;
2323
}};\n"""
2424

@@ -43,15 +43,15 @@
4343
static const unsigned pad_right = {pad_right};
4444
static const nnet::Pool_Op pool_op = nnet::{pool_op};
4545
static const nnet::conv_implementation implementation = nnet::conv_implementation::{implementation};
46-
static const unsigned reuse = {reuse};
46+
static const unsigned reuse_factor = {reuse};
4747
typedef {accum_t.name} accum_t;
4848
}};\n"""
4949

5050
global_pooling1d_config_template = """struct config{index} : nnet::pooling1d_config {{
5151
static const unsigned n_in = {n_in};
5252
static const unsigned n_filt = {n_filt};
5353
static const nnet::Pool_Op pool_op = nnet::{pool_op};
54-
static const unsigned reuse = {reuse};
54+
static const unsigned reuse_factor = {reuse};
5555
typedef {accum_t.name} accum_t;
5656
}};\n"""
5757

@@ -60,7 +60,7 @@
6060
static const unsigned in_width = {in_width};
6161
static const unsigned n_filt = {n_filt};
6262
static const nnet::Pool_Op pool_op = nnet::{pool_op};
63-
static const unsigned reuse = {reuse};
63+
static const unsigned reuse_factor = {reuse};
6464
typedef {accum_t.name} accum_t;
6565
}};\n"""
6666

hls4ml/backends/vivado/passes/recurrent_templates.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
typedef {bias_t.name} bias_t;
1818
typedef {weight_t.name} weight_t;
1919
typedef ap_{index_t} index_t;
20-
template<class x_T, class y_T, class res_T>
21-
using product = nnet::product::{product_type}<x_T, y_T, res_T>;
20+
template<class x_T, class y_T>
21+
using product = nnet::product::{product_type}<x_T, y_T>;
2222
}};\n"""
2323

2424
#activation templates

hls4ml/backends/vivado/passes/transform_types.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ def transform(self, model, node):
2020
new_var = self.inplace_var_converter.convert(var, io_type)
2121
if io_type == 'io_stream':
2222
new_var = self.stream_var_converter.convert(var)
23-
elif io_type == 'io_serial':
24-
new_var = self.array_var_converter.convert(var, pragma='stream')
2523
elif io_type == 'io_parallel':
2624
if node.name in node.model.inputs:
2725
new_var = self.array_var_converter.convert(var, pragma='reshape')

hls4ml/backends/vivado/vivado_backend.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,17 @@ def _register_flows(self):
7373
]
7474
self._writer_flow = register_flow('write', writer_passes, requires=['vivado:ip'], backend=self.name)
7575

76+
fifo_depth_opt_passes = [
77+
'vivado:fifo_depth_optimization'
78+
] + writer_passes # After optimization, a new project will be written
79+
80+
register_flow('fifo_depth_optimization', fifo_depth_opt_passes, requires=[self._writer_flow], backend=self.name)
81+
7682
all_passes = get_backend_passes(self.name)
7783

7884
extras = [
7985
# Ideally this should be empty
80-
opt_pass for opt_pass in all_passes if opt_pass not in initializers + streaming_passes + quantization_passes + optimization_passes + vivado_types + templates + writer_passes
86+
opt_pass for opt_pass in all_passes if opt_pass not in initializers + streaming_passes + quantization_passes + optimization_passes + vivado_types + templates + writer_passes + fifo_depth_opt_passes
8187
]
8288

8389
if len(extras) > 0:
@@ -106,16 +112,16 @@ def create_initial_config(self, part='xcku115-flvb2104-2-i', clock_period=5, io_
106112

107113
return config
108114

109-
def build(self, model, reset=False, csim=True, synth=True, cosim=False, validation=False, export=False, vsynth=False):
115+
def build(self, model, reset=False, csim=True, synth=True, cosim=False, validation=False, export=False, vsynth=False, fifo_opt=False):
110116
if 'linux' in sys.platform:
111117
found = os.system('command -v vivado_hls > /dev/null')
112118
if found != 0:
113119
raise Exception('Vivado HLS installation not found. Make sure "vivado_hls" is on PATH.')
114120

115121
curr_dir = os.getcwd()
116122
os.chdir(model.config.get_output_dir())
117-
os.system('vivado_hls -f build_prj.tcl "reset={reset} csim={csim} synth={synth} cosim={cosim} validation={validation} export={export} vsynth={vsynth}"'
118-
.format(reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth))
123+
os.system('vivado_hls -f build_prj.tcl "reset={reset} csim={csim} synth={synth} cosim={cosim} validation={validation} export={export} vsynth={vsynth} fifo_opt={fifo_opt}"'
124+
.format(reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth, fifo_opt=fifo_opt))
119125
os.chdir(curr_dir)
120126

121127
return parse_vivado_report(model.config.get_output_dir())
@@ -338,9 +344,6 @@ def init_lstm(self, layer):
338344
reuse_factor = layer.model.config.get_reuse_factor(layer)
339345
layer.set_attr('recurrent_reuse_factor', reuse_factor)
340346

341-
recurrent_bias = np.zeros(layer.weights['recurrent_weight'].shape[1])
342-
layer.add_weights_variable(name='recurrent_bias', var_name='br{index}', data=recurrent_bias)
343-
344347
index_t = IntegerPrecisionType(width=1, signed=False)
345348

346349
if 'table_t' not in layer.attributes:
@@ -364,9 +367,6 @@ def init_gru(self, layer):
364367
reuse_factor = layer.model.config.get_reuse_factor(layer)
365368
layer.set_attr('recurrent_reuse_factor', reuse_factor)
366369

367-
recurrent_bias = np.zeros(layer.weights['recurrent_weight'].shape[1])
368-
layer.add_weights_variable(name='recurrent_bias', var_name='br{index}', data=recurrent_bias)
369-
370370
index_t = IntegerPrecisionType(width=1, signed=False)
371371

372372
if 'table_t' not in layer.attributes:

hls4ml/backends/vivado_accelerator/passes/__init__.py

Whitespace-only changes.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from hls4ml.backends.vivado.passes.fifo_depth_optimization import set_big_fifos, get_vcd_data, populate_values, \
2+
generate_max_depth_file, set_fifo_depth
3+
from hls4ml.model.optimizer.optimizer import ConfigurableOptimizerPass, ModelOptimizerPass
4+
5+
6+
class FifoDepthOptimization(ConfigurableOptimizerPass, ModelOptimizerPass):
7+
def __init__(self):
8+
self.values = []
9+
10+
def transform(self, model):
11+
# use `large_fifo_depth = 0` to keep the default fifo depth
12+
profiling_fifo_depth = getattr(self, 'profiling_fifo_depth', 100_000)
13+
14+
# check axi-stream or io-stream, if not one the 2 exit
15+
if not(model.config.get_config_value('IOType') == 'io_stream' or
16+
model.config.get_config_value('AcceleratorConfig')['Interface'] == 'axi_stream' or
17+
model.config.get_config_value('AcceleratorConfig')['Interface'] == 'axi_master'):
18+
raise Exception('To use this optimization you have to set `IOType` field to `io_stream` in the HLS config '
19+
'or `axi_stream` or `axi_master` in `AcceleratorConfig` interface field')
20+
21+
# initialize all the fifos to 10000 so that they will be automatically implemented in BRAMs and so they will be
22+
# profiled
23+
24+
if profiling_fifo_depth:
25+
set_big_fifos(model.output_vars, profiling_fifo_depth)
26+
27+
data = get_vcd_data(model)
28+
29+
for i in range(1, len(data['children'][0]['children'][0]['children'])):
30+
# wrapper fifos
31+
populate_values(self.values,
32+
data['children'][0]['children'][0]['children'][i]['name'],
33+
data['children'][0]['children'][0]['children'][i]['children'][0]['data'],
34+
data['children'][0]['children'][0]['children'][i]['children'][1]['data'])
35+
36+
n_elem = len(data['children'][0]['children'][0]['children'][0]['children'])
37+
for i in range(n_elem):
38+
name = data['children'][0]['children'][0]['children'][0]['children'][i]['name']
39+
data_p = data['children'][0]['children'][0]['children'][0]['children'][i]['children'][0]['data']
40+
depth = data['children'][0]['children'][0]['children'][0]['children'][i]['children'][1]['data']
41+
populate_values(self.values, name, data_p, depth)
42+
43+
maxs = [{'name': i['name'], 'max': i['max'], 'depth': i['depth']} for i in self.values]
44+
45+
generate_max_depth_file(model, maxs)
46+
47+
set_fifo_depth(model, maxs)
48+
49+
inp = model.get_input_variables()[0]
50+
out = model.get_output_variables()[0]
51+
for x in maxs:
52+
if 'in_local' in x['name']:
53+
inp.pragma = (inp.pragma[0], x['max'] + 1)
54+
elif 'out_local' in x['name']:
55+
out.pragma = (out.pragma[0], x['max'] + 1)
56+
57+
print('[hls4ml] - FIFO optimization completed')
58+
return False

0 commit comments

Comments
 (0)