From 68ae74ba18e6409e765ce7cf1153ea6cffe44a10 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 6 Jun 2023 18:31:39 +0200 Subject: [PATCH 1/2] Fix parsing of QConv2DBatchnorm weights --- hls4ml/converters/keras/convolution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hls4ml/converters/keras/convolution.py b/hls4ml/converters/keras/convolution.py index d8e137e6e8..5ebd2abee1 100644 --- a/hls4ml/converters/keras/convolution.py +++ b/hls4ml/converters/keras/convolution.py @@ -44,7 +44,7 @@ def parse_conv2d_layer(keras_layer, input_names, input_shapes, data_reader): (layer['in_height'], layer['in_width'], layer['n_chan']) = parse_data_format(input_shapes[0], layer['data_format']) - if layer['class_name'] in ['Conv2D', 'QConv2D']: + if layer['class_name'] in ['Conv2D', 'QConv2D', 'QConv2DBatchnorm']: layer['weight_data'] = get_weights_data(data_reader, layer['name'], 'kernel') elif layer['class_name'] in ['SeparableConv2D', 'QSeparableConv2D']: layer['depthwise_data'], layer['pointwise_data'] = get_weights_data( From 2d5c42dc7e018a019a22f755804e0ecd357dee49 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 6 Jun 2023 19:19:57 +0200 Subject: [PATCH 2/2] Add test case for handling QConv2DBN --- test/pytest/test_qkeras.py | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/pytest/test_qkeras.py b/test/pytest/test_qkeras.py index 8645ecd0bc..9ef8fe16b6 100644 --- a/test/pytest/test_qkeras.py +++ b/test/pytest/test_qkeras.py @@ -3,6 +3,7 @@ import numpy as np import pytest +from qkeras.qconv2d_batchnorm import QConv2DBatchnorm from qkeras.qlayers import QActivation, QDense from qkeras.quantizers import binary, quantized_bits, quantized_relu, quantized_sigmoid, quantized_tanh, ternary from qkeras.utils import _add_supported_quantized_objects @@ -348,3 +349,44 @@ def test_qactivation_kwarg(randX_100_10, activation_quantizer, weight_quantizer) y_hls4ml = np.where(y_hls4ml == 0, -1, 1) wrong = (y_hls4ml != y_qkeras).ravel() assert sum(wrong) / len(wrong) <= 0.005 + + +@pytest.fixture(scope='module') +def randX_100_8_8_1(): + return np.random.rand(100, 8, 8, 1) + + +@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus']) +@pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream']) +def test_qconv2dbn(randX_100_8_8_1, backend, io_type): + ''' + Test proper handling of QConv2DBatchnorm. + ''' + X = randX_100_8_8_1 + X = np.round(X * 2**10) * 2**-10 # make it an exact ap_fixed<16,6> + model = Sequential() + model.add( + QConv2DBatchnorm( + 4, + kernel_size=(3, 3), + input_shape=(8, 8, 1), + kernel_quantizer='quantized_bits(8, 0, alpha=1)', + kernel_initializer='ones', + bias_quantizer='quantized_bits(8, 0, alpha=1)', + bias_initializer='zeros', + activation='quantized_relu(8, 0)', + ) + ) + model.compile() + + config = hls4ml.utils.config_from_keras_model(model, granularity='name') + output_dir = str(test_root_path / f'hls4mlprj_qkeras_qconv2dbn_{backend}_{io_type}') + hls_model = hls4ml.converters.convert_from_keras_model( + model, hls_config=config, output_dir=output_dir, backend=backend, io_type=io_type + ) + hls_model.compile() + + y_qkeras = model.predict(X) + y_hls4ml = hls_model.predict(X) + + np.testing.assert_array_equal(y_qkeras, y_hls4ml.reshape(y_qkeras.shape))