-
Notifications
You must be signed in to change notification settings - Fork 409
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
Vitis HLS backend #629
Vitis HLS backend #629
Changes from 22 commits
cf44223
baacb45
be460ce
c7c10e3
ad89e3c
88c3955
e8b2176
ef5530d
0884961
b033ec6
12850af
d7e6527
6916392
f44d621
dd8959b
f7e7ee5
b5afb52
797c5f4
8c7a6b0
94dbe80
9dcf2f3
8b27ba6
a2119aa
bfffab8
35fe572
2526d8e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from hls4ml.model.optimizer import OptimizerPass | ||
|
||
|
||
class ValidateConvImplementation(OptimizerPass): | ||
|
||
def match(self, node): | ||
return 'Conv' in node.class_name | ||
|
||
def transform(self, model, node): | ||
if node.get_attr('implementation', 'linebuffer') == 'encoded': | ||
print(f'WARNING: "Encoded" implementation in "{node.name}" ({node.class_name}) is not supported in Vitis backend. Switching to "LineBuffer" implementation.') | ||
node.set_attr('implementation', 'linebuffer') | ||
|
||
|
||
class ValidateStrategy(OptimizerPass): | ||
_resource_layer_cls = ['Conv1D', 'Conv2D', 'Dense'] | ||
|
||
def match(self, node): | ||
is_resource_layer = len([layer_cls for layer_cls in self._resource_layer_cls if layer_cls in node.class_name]) > 0 | ||
is_resource_strategy = node.model.config.is_resource_strategy(node) | ||
|
||
return is_resource_layer and is_resource_strategy | ||
|
||
def transform(self, model, node): | ||
n_in, _ = model.config.backend.get_layer_mult_size(node) | ||
rf = node.get_attr('reuse_factor') | ||
if rf > n_in and rf % n_in > 0: | ||
print(f'WARNING: "Resource" strategy in "{node.name}" ({node.class_name}) may have suboptimal QoR in Vitis backend due to use of "urem" cores. Consider using a different ReuseFactor or switching to "Latency" strategy.') |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import os | ||
import sys | ||
|
||
from hls4ml.backends import VivadoBackend | ||
from hls4ml.model.flow import register_flow, get_flow | ||
from hls4ml.report import parse_vivado_report | ||
|
||
|
||
class VitisBackend(VivadoBackend): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have some concerns inheriting from the VivadoBackend for two reasons. One is that it seems to violate the "is a" requirement for inheritance. Maybe the common features can be factored out? The other is more long-term. In the future when Vitis HLS becomes our primary backend, do we still want to inherit from VivadoBackend? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would agree that having a parent |
||
def __init__(self): | ||
super(VivadoBackend, self).__init__(name='Vitis') | ||
self._register_layer_attributes() | ||
self._register_flows() | ||
|
||
def _register_flows(self): | ||
validation_passes = [ | ||
'vitis:validate_conv_implementation', | ||
'vitis:validate_strategy', | ||
] | ||
validation_flow = register_flow('validation', validation_passes, requires=['vivado:init_layers'], backend=self.name) | ||
|
||
writer_passes = ['make_stamp', 'vitis:write_hls'] | ||
self._writer_flow = register_flow('write', writer_passes, requires=['vitis:ip'], backend=self.name) | ||
|
||
ip_flow_requirements = get_flow('vivado:ip').requires.copy() | ||
ip_flow_requirements.insert(ip_flow_requirements.index('vivado:init_layers'), validation_flow) | ||
|
||
self._default_flow = register_flow('ip', None, requires=ip_flow_requirements, backend=self.name) | ||
|
||
def build(self, model, reset=False, csim=True, synth=True, cosim=False, validation=False, export=False, vsynth=False): | ||
if 'linux' in sys.platform: | ||
found = os.system('command -v vitis_hls > /dev/null') | ||
if found != 0: | ||
raise Exception('Vitis HLS installation not found. Make sure "vitis_hls" is on PATH.') | ||
|
||
curr_dir = os.getcwd() | ||
os.chdir(model.config.get_output_dir()) | ||
os.system('vitis_hls -f build_prj.tcl "reset={reset} csim={csim} synth={synth} cosim={cosim} validation={validation} export={export} vsynth={vsynth}"' | ||
.format(reset=reset, csim=csim, synth=synth, cosim=cosim, validation=validation, export=export, vsynth=vsynth)) | ||
os.chdir(curr_dir) | ||
|
||
return parse_vivado_report(model.config.get_output_dir()) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
#ifndef NNET_CONV1D_RESOURCE_H_ | ||
#define NNET_CONV1D_RESOURCE_H_ | ||
|
||
#include "nnet_common.h" | ||
#include "nnet_dense.h" | ||
|
||
namespace nnet { | ||
|
||
template<class data_T, class res_T, typename CONFIG_T> | ||
void conv_1d_resource_cl( | ||
data_T data[CONFIG_T::in_width * CONFIG_T::n_chan], | ||
res_T res[CONFIG_T::out_width * CONFIG_T::n_filt], | ||
typename CONFIG_T::weight_t weights[CONFIG_T::filt_width * CONFIG_T::n_chan * CONFIG_T::n_filt], | ||
typename CONFIG_T::bias_t biases[CONFIG_T::n_filt]) | ||
{ | ||
constexpr unsigned mult_n_in = CONFIG_T::filt_width * CONFIG_T::n_chan; | ||
constexpr unsigned mult_n_out = CONFIG_T::n_filt; | ||
constexpr unsigned block_factor = DIV_ROUNDUP(mult_n_in * mult_n_out, CONFIG_T::reuse_factor); | ||
constexpr unsigned multscale = block_factor / mult_n_out; | ||
|
||
assert((block_factor % mult_n_out == 0 || CONFIG_T::reuse_factor >= mult_n_in) && "The current Reuse Factor is not allowed"); | ||
assert((CONFIG_T::reuse_factor <= CONFIG_T::filt_width * CONFIG_T::n_chan) && "This function is correct only for RF <= FILT_WIDTH * N_CHAN"); | ||
|
||
// Treating weights as 2d is required to make sure Vitis doesn't use urem cores to calculate indices. | ||
// Also, we don't apply ARRAY_RESHAPE pragma as Vitis figures this out on its own. | ||
typename CONFIG_T::weight_t (*weights_2d)[CONFIG_T::reuse_factor] = (typename CONFIG_T::weight_t (*)[CONFIG_T::reuse_factor]) weights; | ||
|
||
data_T data_buf[CONFIG_T::n_pixels][mult_n_in]; | ||
#pragma HLS ARRAY_PARTITION variable=data_buf complete dim=0 | ||
|
||
#pragma HLS ARRAY_PARTITION variable=biases complete | ||
|
||
typename CONFIG_T::accum_t acc[CONFIG_T::n_pixels][mult_n_out]; | ||
#pragma HLS ARRAY_PARTITION variable=acc complete dim=0 | ||
|
||
PartitionLoop: | ||
for (unsigned i_part = 0; i_part < CONFIG_T::n_partitions; i_part++) { | ||
//#pragma HLS UNROLL // We don't want this loop unrolled | ||
|
||
CONFIG_T::template fill_buffer<data_T, CONFIG_T>::fill_buffer(data, data_buf, i_part); | ||
|
||
PixelInitAccumLoop: | ||
for (unsigned i_pxl = 0; i_pxl < CONFIG_T::n_pixels; i_pxl++) { | ||
#pragma HLS UNROLL | ||
|
||
InitAccumLoop: | ||
for (unsigned i_acc = 0; i_acc < mult_n_out; i_acc++) { | ||
#pragma HLS UNROLL | ||
acc[i_pxl][i_acc] = (typename CONFIG_T::accum_t) biases[i_acc]; | ||
} | ||
} | ||
|
||
ReuseLoop: | ||
for (unsigned i_rf = 0; i_rf < CONFIG_T::reuse_factor; i_rf++) { | ||
#pragma HLS PIPELINE II=1 rewind | ||
|
||
unsigned i_in = i_rf; | ||
unsigned i_out = 0; | ||
unsigned i_acc = 0; | ||
|
||
MultLoop: | ||
for (unsigned i_blk = 0; i_blk < block_factor; i_blk++) { | ||
#pragma HLS UNROLL | ||
|
||
PixelMultLoop: | ||
for (unsigned i_pxl = 0; i_pxl < CONFIG_T::n_pixels; i_pxl++) { | ||
#pragma HLS UNROLL | ||
|
||
acc[i_pxl][i_out] += static_cast<typename CONFIG_T::accum_t>( | ||
CONFIG_T::mult_config::template product<data_T, typename CONFIG_T::mult_config::weight_t>::product(data_buf[i_pxl][i_in], weights_2d[i_blk][i_rf])); | ||
} | ||
|
||
// Increment i_in | ||
i_in += CONFIG_T::reuse_factor; | ||
if (i_in >= mult_n_in) { | ||
i_in = i_rf; | ||
} | ||
// Increment i_out | ||
if (i_acc + 1 >= multscale) { | ||
i_acc = 0; | ||
i_out++; | ||
} else { | ||
i_acc++; | ||
} | ||
} | ||
} | ||
|
||
PixelResultLoop: | ||
for (unsigned i_pxl = 0; i_pxl < CONFIG_T::n_pixels; i_pxl++) { | ||
#pragma HLS UNROLL | ||
// Cast to "res_t" type | ||
ResultLoop: | ||
for (unsigned i_res = 0; i_res < mult_n_out; i_res++) { | ||
#pragma HLS UNROLL | ||
*(res++) = cast<data_T, res_T, typename CONFIG_T::mult_config>(acc[i_pxl][i_res]); | ||
} | ||
} | ||
} | ||
} | ||
|
||
} | ||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#ifndef NNET_CONV1D_STREAM_H_ | ||
#define NNET_CONV1D_STREAM_H_ | ||
|
||
#include "nnet_common.h" | ||
#include "nnet_conv_stream.h" | ||
#include "hls_stream.h" | ||
|
||
namespace nnet { | ||
|
||
template<class data_T, class res_T, typename CONFIG_T> | ||
void conv_1d_cl( | ||
hls::stream<data_T> &data, | ||
hls::stream<res_T> &res, | ||
typename CONFIG_T::weight_t weights[CONFIG_T::filt_width * CONFIG_T::n_chan * CONFIG_T::n_filt], | ||
typename CONFIG_T::bias_t biases[CONFIG_T::n_filt]) | ||
{ | ||
assert(CONFIG_T::implementation == conv_implementation::linebuffer && "Only \"linebuffer\" implementation is supported in Vitis HLS."); | ||
|
||
assert(CONFIG_T::pad_left == 0 && CONFIG_T::pad_right == 0); | ||
|
||
if (CONFIG_T::strategy == nnet::latency) { | ||
ReadInputWidth: for (unsigned i_iw = 0; i_iw < CONFIG_T::in_width; i_iw++) { | ||
#pragma HLS PIPELINE II=CONFIG_T::reuse_factor | ||
compute_output_buffer_1d<data_T, res_T, CONFIG_T>(data.read(), res, weights, biases); | ||
} | ||
} else { | ||
ReadInputWidthSerial: for (unsigned i_iw = 0; i_iw < CONFIG_T::in_width; i_iw++) { | ||
compute_output_buffer_1d<data_T, res_T, CONFIG_T>(data.read(), res, weights, biases); | ||
} | ||
} | ||
|
||
} | ||
|
||
|
||
} | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would prefer docstrings in new classes that are added describing their purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm planning a new PR that adds docstrings to most of the remaining codebase, not just Vitis parts.