Skip to content

Commit

Permalink
Merge pull request #997 from fastmachinelearning/weight_quantizer_none
Browse files Browse the repository at this point in the history
add protection for when kernel_quantizer is None
  • Loading branch information
jmitrevs authored Jul 1, 2024
2 parents 02688f1 + 03ea15d commit 7255cbc
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 24 deletions.
32 changes: 8 additions & 24 deletions hls4ml/converters/keras/qkeras.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@


def get_quantizer_from_config(keras_layer, quantizer_var):
quantizer_config = keras_layer['config'][f'{quantizer_var}_quantizer']
quantizer_config = keras_layer['config'].get(f'{quantizer_var}_quantizer', None)
if quantizer_config is None:
return None # No quantizer specified in the layer
if keras_layer['class_name'] == 'QBatchNormalization':
return QKerasQuantizer(quantizer_config)
elif 'binary' in quantizer_config['class_name']:
Expand All @@ -25,10 +27,7 @@ def parse_qdense_layer(keras_layer, input_names, input_shapes, data_reader):
layer, output_shape = parse_dense_layer(keras_layer, input_names, input_shapes, data_reader)

layer['weight_quantizer'] = get_quantizer_from_config(keras_layer, 'kernel')
if keras_layer['config']['bias_quantizer'] is not None:
layer['bias_quantizer'] = get_quantizer_from_config(keras_layer, 'bias')
else:
layer['bias_quantizer'] = None
layer['bias_quantizer'] = get_quantizer_from_config(keras_layer, 'bias')

return layer, output_shape

Expand All @@ -43,10 +42,7 @@ def parse_qconv_layer(keras_layer, input_names, input_shapes, data_reader):
layer, output_shape = parse_conv2d_layer(keras_layer, input_names, input_shapes, data_reader)

layer['weight_quantizer'] = get_quantizer_from_config(keras_layer, 'kernel')
if keras_layer['config']['bias_quantizer'] is not None:
layer['bias_quantizer'] = get_quantizer_from_config(keras_layer, 'bias')
else:
layer['bias_quantizer'] = None
layer['bias_quantizer'] = get_quantizer_from_config(keras_layer, 'bias')

return layer, output_shape

Expand All @@ -56,11 +52,7 @@ def parse_qdepthwiseqconv_layer(keras_layer, input_names, input_shapes, data_rea
layer, output_shape = parse_conv2d_layer(keras_layer, input_names, input_shapes, data_reader)

layer['depthwise_quantizer'] = get_quantizer_from_config(keras_layer, 'depthwise')

if keras_layer['config']['bias_quantizer'] is not None:
layer['bias_quantizer'] = get_quantizer_from_config(keras_layer, 'bias')
else:
layer['bias_quantizer'] = None
layer['bias_quantizer'] = get_quantizer_from_config(keras_layer, 'bias')

return layer, output_shape

Expand All @@ -76,11 +68,7 @@ def parse_qsepconv_layer(keras_layer, input_names, input_shapes, data_reader):

layer['depthwise_quantizer'] = get_quantizer_from_config(keras_layer, 'depthwise')
layer['pointwise_quantizer'] = get_quantizer_from_config(keras_layer, 'pointwise')

if keras_layer['config']['bias_quantizer'] is not None:
layer['bias_quantizer'] = get_quantizer_from_config(keras_layer, 'bias')
else:
layer['bias_quantizer'] = None
layer['bias_quantizer'] = get_quantizer_from_config(keras_layer, 'bias')

return layer, output_shape

Expand All @@ -93,11 +81,7 @@ def parse_qrnn_layer(keras_layer, input_names, input_shapes, data_reader):

layer['weight_quantizer'] = get_quantizer_from_config(keras_layer, 'kernel')
layer['recurrent_quantizer'] = get_quantizer_from_config(keras_layer, 'recurrent')

if keras_layer['config']['bias_quantizer'] is not None:
layer['bias_quantizer'] = get_quantizer_from_config(keras_layer, 'bias')
else:
layer['bias_quantizer'] = None
layer['bias_quantizer'] = get_quantizer_from_config(keras_layer, 'bias')

return layer, output_shape

Expand Down
32 changes: 32 additions & 0 deletions test/pytest/test_qkeras.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,38 @@ def test_qactivation_kwarg(randX_100_10, activation_quantizer, weight_quantizer)
assert sum(wrong) / len(wrong) <= 0.005


@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus'])
@pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream'])
def test_quantizer_parsing(randX_100_10, backend, io_type):
X = randX_100_10
X = np.round(X * 2**10) * 2**-10 # make it an exact ap_fixed<16,6>
model = Sequential()
model.add(
QDense(
8,
input_shape=(10,),
kernel_quantizer=None, # Incorrect usage, but shouldn't break hls4ml
kernel_initializer='ones',
bias_quantizer=None,
bias_initializer='zeros',
activation='quantized_relu(8, 0)',
)
)
model.compile()

config = hls4ml.utils.config_from_keras_model(model, granularity='name', default_precision='fixed<24,8>')
output_dir = str(test_root_path / f'hls4mlprj_qkeras_quant_parse_{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))


@pytest.fixture(scope='module')
def randX_100_8_8_1():
return np.random.rand(100, 8, 8, 1)
Expand Down

0 comments on commit 7255cbc

Please sign in to comment.