Skip to content

Commit 2fa84c7

Browse files
committed
1D Convolution & Pooling Quartus PyTests
1 parent c74274e commit 2fa84c7

File tree

2 files changed

+68
-57
lines changed

2 files changed

+68
-57
lines changed

test/pytest/test_conv1d.py

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1+
from distutils.command.config import config
12
from hls4ml.converters.keras_to_hls import keras_to_hls
23
import pytest
34
import hls4ml
45
import numpy as np
56
from sklearn.metrics import accuracy_score
6-
import tensorflow as tf
77
from tensorflow.keras.models import model_from_json
8-
import yaml
98
from pathlib import Path
109

1110
test_root_path = Path(__file__).parent
@@ -25,46 +24,58 @@ def keras_model():
2524
model.load_weights(example_model_path / 'keras/KERAS_conv1d_weights.h5')
2625
return model
2726

28-
@pytest.fixture
29-
@pytest.mark.parametrize('settings', [('io_parallel', 'latency'),
30-
('io_parallel', 'resource'),
31-
('io_stream', 'latency'),
32-
('io_stream', 'resource')])
33-
def hls_model(settings):
34-
io_type = settings[0]
35-
strategy = settings[1]
36-
config = hls4ml.converters.create_config(output_dir = 'hls4mlprj_conv1d_{}_{}'.format(io_type, strategy))
37-
config['KerasJson'] = str(example_model_path / 'keras/KERAS_conv1d.json')
38-
config['KerasH5'] = str(example_model_path / 'keras/KERAS_conv1d_weights.h5')
39-
config['OutputDir'] = str(test_root_path / 'hls4mlprj_conv1d_{}_{}'.format(io_type, strategy))
40-
config['IOType'] = io_type
27+
@pytest.fixture
28+
@pytest.mark.parametrize('backend, io_type, strategy', [
29+
('Quartus', 'io_parallel', 'resource'),
30+
('Vivado', 'io_parallel', 'resource'),
31+
32+
('Vivado', 'io_parallel', 'latency'),
33+
34+
('Vivado', 'io_stream', 'latency'),
35+
('Vivado', 'io_stream', 'resource')
36+
])
37+
def hls_model(keras_model, backend, io_type, strategy):
38+
default_precision = 'ap_fixed<16,3,AP_RND_CONV,AP_SAT>' if backend=='Vivado' else 'ac_fixed<16,3,true,AC_RND_CONV,AC_SAT>'
39+
fc1_weight_precision = 'ap_fixed<16,3>' if backend=='Vivado' else 'ac_fixed<16,3,true>'
40+
fc1_result_precision = 'ap_fixed<16,6,AP_RND_CONV,AP_SAT>' if backend=='Vivado' else 'ac_fixed<16,6,true,AC_RND_CONV,AC_SAT>'
41+
output_softmax_weight_precision = 'ap_fixed<16,6>' if backend=='Vivado' else 'ac_fixed<16,6,true>'
42+
output_softmax_result_precision = 'ap_fixed<16,6,AP_RND_CONV,AP_SAT>' if backend=='Vivado' else 'ac_fixed<16,6,true,AP_RND_CONV,AP_SAT>'
43+
44+
# Default config
45+
hls_config = hls4ml.utils.config_from_keras_model(keras_model)
46+
hls_config['Model']['Strategy'] = strategy
47+
hls_config['Model']['ReuseFactor'] = 1
48+
hls_config['Model']['Precision'] = default_precision
49+
50+
# Some model-specific precision tuning
51+
hls_config['LayerName'] = {}
52+
hls_config['LayerName']['fc1_relu'] = {'Precision':{'weight' : fc1_weight_precision, 'result' : fc1_result_precision}}
53+
hls_config['LayerName']['output_softmax'] = {'Precision':{'weight' : output_softmax_weight_precision, 'result' : output_softmax_result_precision}}
54+
hls_config['LayerName']['output_softmax_softmax'] = {'Strategy':'Stable'}
4155

42-
hls_config = {'Model' : {'Strategy' : strategy,
43-
'ReuseFactor' : 1,
44-
'Precision' : 'ap_fixed<16,3,AP_RND_CONV,AP_SAT>'}}
45-
# Some model specific precision tuning
46-
config['LayerName'] = {}
47-
config['LayerName']['fc1_relu'] = {'Precision':{'weight' : 'ap_fixed<16,3>', 'result' : 'ap_fixed<16,6,AP_RND_CONV,AP_SAT>'}}
48-
config['LayerName']['output_softmax'] = {'Precision':{'weight' : 'ap_fixed<16,6>', 'result' : 'ap_fixed<16,6,AP_RND_CONV,AP_SAT>'}}
49-
config['LayerName']['output_softmax_softmax'] = {'Strategy':'Stable'}
50-
config['HLSConfig'] = hls_config
51-
hls_model = keras_to_hls(config)
56+
output_dir = str(test_root_path / 'hls4mlprj_conv1d_{}_{}_{}'.format(backend, io_type, strategy))
57+
hls_model = hls4ml.converters.convert_from_keras_model(keras_model, hls_config=hls_config, backend=backend, io_type=io_type, output_dir=output_dir)
5258
hls_model.compile()
5359
return hls_model
5460

55-
@pytest.mark.parametrize('settings', [('io_parallel', 'latency'),
56-
('io_parallel', 'resource'),
57-
('io_stream', 'latency'),
58-
('io_stream', 'resource')])
61+
@pytest.mark.parametrize('backend, io_type, strategy', [
62+
('Quartus', 'io_parallel', 'resource'),
63+
('Vivado', 'io_parallel', 'resource'),
64+
65+
('Vivado', 'io_parallel', 'latency'),
66+
67+
('Vivado', 'io_stream', 'latency'),
68+
('Vivado', 'io_stream', 'resource')
69+
])
5970
def test_accuracy(data, keras_model, hls_model):
6071
X = data
6172
model = keras_model
62-
# model under test predictions and accuracy
73+
74+
# Model under test predictions and accuracy
6375
y_keras = model.predict(X)
6476
y_hls4ml = hls_model.predict(X)
65-
# "accuracy" of hls4ml predictions vs keras
77+
78+
# "Accuracy" of hls4ml predictions vs keras
6679
rel_acc = accuracy_score(np.argmax(y_keras, axis=1), np.argmax(y_hls4ml, axis=1))
67-
6880
print('hls4ml accuracy relative to keras: {}'.format(rel_acc))
69-
7081
assert rel_acc > 0.98

test/pytest/test_keras_api.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,13 @@ def test_activations(activation_function, backend):
9090

9191
assert list(hls_model.get_layers())[2].attributes['class_name'] == activation_function.__class__.__name__
9292

93-
94-
keras_conv1d = [Conv1D]
9593
padds_options = ['same', 'valid']
96-
@pytest.mark.parametrize("conv1d", keras_conv1d)
97-
@pytest.mark.parametrize("padds", padds_options)
98-
def test_conv1d(conv1d, padds):
94+
@pytest.mark.parametrize('padds', padds_options)
95+
@pytest.mark.parametrize('backend', ['Vivado', 'Quartus'])
96+
def test_conv1d(padds, backend):
9997
model = tf.keras.models.Sequential()
10098
input_shape = (10, 128, 4)
101-
model.add(conv1d(filters=32,
99+
model.add(Conv1D(filters=32,
102100
kernel_size=3,
103101
strides=1,
104102
padding=padds,
@@ -108,22 +106,23 @@ def test_conv1d(conv1d, padds):
108106
use_bias=False,
109107
data_format='channels_last'))
110108
model.add(Activation(activation='relu'))
111-
112109
model.compile(optimizer='adam', loss='mse')
110+
113111
X_input = np.random.rand(10,128,4)
114112
keras_prediction = model.predict(X_input)
113+
115114
config = hls4ml.utils.config_from_keras_model(model)
116-
output_dir = str(test_root_path / 'hls4mlprj_keras_api_conv1d_{}'.format(padds))
117-
hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir)
115+
output_dir = str(test_root_path / 'hls4mlprj_keras_api_conv1d_{}_{}'.format(padds, backend))
116+
hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir, backend=backend)
118117
hls_model.compile()
119-
hls_prediction = hls_model.predict(X_input)
118+
hls_prediction = hls_model.predict(X_input).reshape(keras_prediction.shape)
119+
120+
# 5e-2 might be too high
121+
np.testing.assert_allclose(hls_prediction, keras_prediction, rtol=0, atol=5e-2)
120122

121123
assert len(model.layers) + 2 == len(hls_model.get_layers())
122124
assert list(hls_model.get_layers())[1].attributes['name'] == model.layers[0]._name
123-
124-
print(list(hls_model.get_layers())[1].attributes)
125-
if conv1d == 'Conv1D':
126-
assert list(hls_model.get_layers())[1].attributes['class_name'] == 'Conv1D'
125+
assert list(hls_model.get_layers())[1].attributes['class_name'] == 'Conv1D'
127126
assert list(hls_model.get_layers())[1].attributes['activation'] == str(model.layers[0].activation).split()[1]
128127
assert list(hls_model.get_layers())[1].attributes["in_width"] == model.layers[0]._batch_input_shape[1]
129128
assert list(hls_model.get_layers())[1].attributes['filt_width'] == model.layers[0].kernel_size[0]
@@ -139,12 +138,9 @@ def test_conv1d(conv1d, padds):
139138
pad_left = pad_along_width // 2
140139
pad_right = pad_along_width - pad_left
141140

142-
out_valid = math.ceil(float(model.layers[0]._batch_input_shape[1] - model.layers[0].kernel_size[0] + 1) / float(model.layers[0].strides[0]))
143-
144141
if model.layers[0].padding == 'same':
145142
assert list(hls_model.get_layers())[1].attributes['pad_left'] == pad_left
146143
assert list(hls_model.get_layers())[1].attributes['pad_right'] == pad_right
147-
148144
elif model.layers[0].padding == 'valid':
149145
assert list(hls_model.get_layers())[1].attributes['pad_left'] == 0
150146
assert list(hls_model.get_layers())[1].attributes['pad_right'] == 0
@@ -229,11 +225,13 @@ def test_conv2d(chans, padds, backend):
229225
assert list(hls_model.get_layers())[1].attributes['pad_right'] == 0
230226

231227
pooling_layers = [MaxPooling1D, MaxPooling2D, AveragePooling1D, AveragePooling2D]
232-
@pytest.mark.parametrize("pooling", pooling_layers)
233-
@pytest.mark.parametrize("padds", padds_options)
234-
@pytest.mark.parametrize("chans", chans_options)
235-
def test_pooling(pooling, padds, chans):
228+
@pytest.mark.parametrize('pooling', pooling_layers)
229+
@pytest.mark.parametrize('padds', padds_options)
230+
@pytest.mark.parametrize('chans', chans_options)
231+
@pytest.mark.parametrize('backend', ['Vivado', 'Quartus'])
232+
def test_pooling(pooling, padds, chans, backend):
236233
assert '1D' in pooling.__name__ or '2D' in pooling.__name__
234+
237235
input_shape = (8,8,3) if '2D' in pooling.__name__ else (64,3)
238236
model = tf.keras.models.Sequential()
239237
if '2D' in pooling.__name__:
@@ -243,20 +241,22 @@ def test_pooling(pooling, padds, chans):
243241
model.add(Conv1D(filters=32, kernel_size=3, strides=1, padding=padds,
244242
input_shape=input_shape, kernel_initializer='normal', use_bias=False,
245243
data_format=chans))
246-
247244
pool_size = (2, 2) if '2D' in pooling.__name__ else 2
248245
model.add(pooling(pool_size=pool_size, strides=None, padding=padds, data_format=None))
249246
model.compile(optimizer='adam', loss='mse')
250247

251248
X_input = np.random.rand(100, *input_shape)
252249
keras_prediction = model.predict(X_input)
253250
config = hls4ml.utils.config_from_keras_model(model)
254-
output_dir = str(test_root_path / 'hls4mlprj_keras_api_pooling_{}_{}_{}'.format(pooling.__name__, chans, padds))
251+
output_dir = str(test_root_path / 'hls4mlprj_keras_api_pooling_{}_{}_{}_{}'.format(pooling.__name__, chans, padds, backend))
255252

256-
hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir)
253+
hls_model = hls4ml.converters.convert_from_keras_model(model, hls_config=config, output_dir=output_dir, backend=backend)
257254
hls_model.compile()
258255
hls_prediction = hls_model.predict(X_input).reshape(keras_prediction.shape)
259256

257+
# 5e-2 might be too high
258+
np.testing.assert_allclose(hls_prediction, keras_prediction, rtol=0, atol=5e-2)
259+
260260
hls_pool = list(hls_model.get_layers())[-1]
261261
ker_pool = model.layers[-1]
262262
if '2D' in pooling.__name__:

0 commit comments

Comments
 (0)