Skip to content
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

Update QONNX parsing for 1.0 #979

Open
wants to merge 81 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
0765ec4
Add needed layer types for QONNX
jmitrevs Jul 11, 2023
ff788ea
add qonnx pytest
jmitrevs Jul 12, 2023
cda7208
first migration of onnx parsing
jmitrevs Jul 12, 2023
af47a0d
change tuples to lists
jmitrevs Jul 12, 2023
8f8cc0b
snapshot of adding qonnx optimizers
jmitrevs Jul 12, 2023
5cea82d
snapshot that runs qonnx test, but gets incorrect results
jmitrevs Jul 13, 2023
d5394d4
add quant node quantizer
jmitrevs Jul 13, 2023
9817ed3
fix broadcasting when going from Merge to ApplyAlpha
jmitrevs Jul 13, 2023
e494f43
update linear merging
jmitrevs Jul 13, 2023
ffddb5e
update automatic setting of accumulators (QONNX-only for now)
jmitrevs Jul 13, 2023
57c89fb
update qonnx tests
jmitrevs Jul 13, 2023
233905a
remove batch dimension from flatten in Keras
jmitrevs Jul 18, 2023
aafe0ca
Merge branch 'main' into qonnx_0p8
jmitrevs Aug 2, 2023
6f11955
fix optimizer that fuses consecutive batch norms
jmitrevs Aug 3, 2023
0becc04
Merge branch 'main' into qonnx_0p8
jmitrevs Jan 22, 2024
1f433fe
Merge remote-tracking branch 'vloncar/auto_precision' into qonnx-1p0
jmitrevs Feb 1, 2024
76be67b
snapshot of work
jmitrevs Feb 3, 2024
4d52975
snapshot before removing redundant precision attributes
jmitrevs Feb 5, 2024
cf5c9a1
snapshot
jmitrevs Feb 7, 2024
81f3e53
bug fixes from attempting to run
jmitrevs Feb 8, 2024
9a74e46
fix some bugs from qonnx pytest
jmitrevs Feb 12, 2024
60a74bb
fix assertion of not matching the number of inputs when replacing node
jmitrevs Feb 12, 2024
a032a5d
Merge remote-tracking branch 'vloncar/auto_precision' into qonnx-1p0
jmitrevs Feb 29, 2024
88a8d35
update some precisions inference
jmitrevs Feb 29, 2024
0379db2
Merge remote-tracking branch 'upstream/main' into qonnx-1p0
jmitrevs Feb 29, 2024
10a3c50
extract bitwidth from size 1 array in quant node
jmitrevs Feb 29, 2024
ab8d67b
update automatic onnx configuration
jmitrevs Mar 2, 2024
0a863ad
standardize on merge operators
jmitrevs Mar 2, 2024
bfe6a3f
snapshot of current work
jmitrevs Mar 8, 2024
25849ef
Fix bug in FuseBatchNormalization
jmitrevs Mar 10, 2024
4485bf3
fix issue with configuration setup of test
jmitrevs Mar 11, 2024
52067c3
fix bug in FuseConsecutiveBatchNormalization
jmitrevs Mar 11, 2024
24d6245
add missing header
jmitrevs Mar 11, 2024
835af4e
attempt to make qonnx tests match better
jmitrevs Mar 11, 2024
4a41b63
Merge remote-tracking branch 'upstream/main' into qonnx-1p0
jmitrevs Mar 12, 2024
2bcec04
fix pre-commit
jmitrevs Mar 12, 2024
b3facd2
remove count, become more selective on when True is returned
jmitrevs Apr 17, 2024
b580866
Merge remote-tracking branch 'upstream/main' into qonnx-1p0
jmitrevs Apr 19, 2024
105b38a
Merge remote-tracking branch 'upstream/main' into qonnx-1p0
jmitrevs Apr 19, 2024
0d8108e
fix optimizer issue when quantizer is None
jmitrevs Apr 19, 2024
229b44a
Merge remote-tracking branch 'upstream/main' into qonnx-1p0
jmitrevs May 3, 2024
1fa59dc
update pytest image to 0.5.6
jmitrevs May 16, 2024
65857a4
Merge branch 'main' into qonnx-1p0
jmitrevs May 30, 2024
b565067
Merge branch 'main' into qonnx-1p0
jmitrevs May 31, 2024
2a78f93
Merge branch 'main' into qonnx-1p0
jmitrevs Jun 25, 2024
c5841a2
seperate out parse_qonnx flow
jmitrevs Jun 25, 2024
de790ca
Again allow for None in target shape--for pytorch
jmitrevs Jun 26, 2024
6189953
Merge branch 'main' into qonnx-1p0
jmitrevs Jul 17, 2024
2909d15
Following what seems to be done in the main branch
jmitrevs Jul 18, 2024
c9693da
update infer_precision based on changes in keras-config-auto
jmitrevs Jul 19, 2024
aaaa2fc
loosen batchnorm merging restrictions, fix ternary handling
jmitrevs Jul 19, 2024
a2b88f4
remove some backends from slow qonnx test
jmitrevs Jul 19, 2024
169d9e5
Merge remote-tracking branch 'upstream/main' into qonnx-1p0
jmitrevs Aug 21, 2024
ef02b4f
move multi_dense to conv above inferming precision types
jmitrevs Aug 21, 2024
c3ffa7b
fix the default reuse factor
jmitrevs Aug 21, 2024
3591ae5
Merge remote-tracking branch 'upstream/main' into qonnx-1p0
jmitrevs Sep 3, 2024
cc7652d
Pre-commit fix
jmitrevs Sep 3, 2024
b36fe4f
fix qonnx review suggestions
jmitrevs Sep 4, 2024
c37d953
fix qonnx review suggestions (part 2)
jmitrevs Sep 4, 2024
23825de
fix error message
jmitrevs Sep 4, 2024
cad06fa
change order of qonnx optimizers
jmitrevs Sep 9, 2024
5e9f4d6
Merge branch 'main' into qonnx-1p0
jmitrevs Sep 11, 2024
51c80f9
make the optimizer oder be more similar to main branch
jmitrevs Sep 12, 2024
8e6dd58
Merge remote-tracking branch 'upstream/main' into qonnx-1p0
jmitrevs Sep 12, 2024
ce09665
Merge branch 'main' into qonnx-1p0
jmitrevs Sep 13, 2024
8eaf10a
fix dimensions when moving scales
jmitrevs Sep 19, 2024
d80dc3b
Added support and some missing parts for `Depthwise` and `Pointwise` …
jmitrevs Sep 20, 2024
fae647d
add seperable conv to test
jmitrevs Sep 23, 2024
56c85a4
fix pointwise with naming, quant_opt
jmitrevs Sep 24, 2024
b0efdd6
fix ConstantBatchNormFusion
jmitrevs Sep 24, 2024
14da6f5
update broadcasting for moving scales for conv
jmitrevs Sep 25, 2024
0333d36
snapshot of current development
jmitrevs Sep 26, 2024
80184d2
snapshot working through scale downs
jmitrevs Sep 26, 2024
6bb0817
finish making the various cases
jmitrevs Sep 26, 2024
766a14c
accidentally reverted the example models
jmitrevs Sep 26, 2024
5ff1373
some bug fixes
jmitrevs Sep 26, 2024
65e0127
Merge pull request #10 from jmitrevs/qonnx-1p0-sepconv-dev
jmitrevs Sep 29, 2024
da4f9e5
Merge branch 'main' into qonnx-1p0
jmitrevs Sep 29, 2024
86abdd2
update qonnx sepconv test
jmitrevs Sep 29, 2024
6363702
Merge branch 'main' into qonnx-1p0
jmitrevs Oct 1, 2024
accadaf
Merge branch 'main' into qonnx-1p0
jmitrevs Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion hls4ml/backends/fpga/fpga_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
LSTM,
Activation,
BatchNormalization,
BatchNormOnnx,
Conv,
Conv1D,
Conv2D,
Dense,
Expand All @@ -22,8 +24,11 @@
GarNetStack,
GlobalPooling1D,
GlobalPooling2D,
MatMul,
Merge,
Pooling1D,
Pooling2D,
Quant,
SeparableConv1D,
SeparableConv2D,
SimpleRNN,
Expand Down Expand Up @@ -63,14 +68,25 @@ def __init__(self, name):
LSTM,
GRU,
Dot,
Conv,
MatMul,
]

for layer in accum_layers:
attrs = self.attribute_map.get(layer, [])
attrs.append(TypeAttribute('accum'))
self.attribute_map[layer] = attrs

rf_layers = accum_layers + [BatchNormalization, Activation, Embedding, GarNet, GarNetStack]
rf_layers = accum_layers + [
BatchNormalization,
Activation,
Embedding,
GarNet,
GarNetStack,
Quant,
BatchNormOnnx,
Merge,
]

for layer in rf_layers:
attrs = self.attribute_map.get(layer, [])
Expand Down
1 change: 1 addition & 0 deletions hls4ml/converters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from hls4ml.converters.keras_to_hls import get_supported_keras_layers # noqa: F401
from hls4ml.converters.keras_to_hls import parse_keras_model # noqa: F401
from hls4ml.converters.keras_to_hls import keras_to_hls, register_keras_layer_handler
from hls4ml.converters.onnx_to_hls import parse_onnx_model # noqa: F401
from hls4ml.model import ModelGraph
from hls4ml.utils.config import create_config
from hls4ml.utils.symbolic_utils import LUTFunction
Expand Down
4 changes: 2 additions & 2 deletions hls4ml/converters/keras/reshape.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ def parse_flatten_layer(keras_layer, input_names, input_shapes, data_reader):
layer = parse_default_keras_layer(keras_layer, input_names)

layer['class_name'] = 'Reshape'
layer['target_shape'] = [input_shapes[0][0], np.prod(input_shapes[0][1:])]
output_shape = layer['target_shape']
layer['target_shape'] = [np.prod(input_shapes[0][1:])] # target shape has no batch dimension
output_shape = input_shapes[0][:1] + layer['target_shape']

return layer, output_shape

Expand Down
127 changes: 57 additions & 70 deletions hls4ml/converters/onnx/convolution.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,72 @@
from hls4ml.converters.onnx_to_hls import (
compute_pads_1d,
compute_pads_2d,
get_onnx_attribute,
get_onnx_input_name,
onnx_handler,
)
from hls4ml.converters.utils import compute_padding_1d, compute_padding_2d
import numpy as np

from hls4ml.converters.onnx_to_hls import get_onnx_attribute, onnx_handler


@onnx_handler('Conv')
def parse_conv_layer(reader, node, inputs_map, input_shapes, graph, config):
def parse_conv_layer(node, input_names, input_shapes, graph):
layer = {}
layer['name'] = node.name
layer['data_format'] = 'channels_first' # ONNX's default is channel first
layer['inputs'] = get_onnx_input_name(node, graph)
reader.add_input(layer['name'], node.input)
if node.domain != 'qonnx.custom_op.channels_last':
raise RuntimeError("Please convert the model to channels-last format with qonnx-to-channels-last")
layer['data_format'] = 'channels_last' # QONNX needs to be channels-last.
layer['inputs'] = input_names
layer['outputs'] = node.output

strides = get_onnx_attribute(node, 'strides')
kernel_shape = get_onnx_attribute(node, 'kernel_shape')

if len(input_shapes[0]) == 3: # Conv1D
layer['class_name'] = 'Conv1D'

layer['in_width'] = input_shapes[0][2]
layer['n_chan'] = input_shapes[0][1]
layer['filt_width'] = kernel_shape[0]
layer['n_filt'] = reader.get_weights_data(layer['name'], 'kernel').shape[2]
layer['stride_width'] = strides[0]
pads = compute_pads_1d(node, layer)

# Note: currently don't have support for auto_pad.
pads = get_onnx_attribute(node, 'pads')
dilations = get_onnx_attribute(node, 'dilations')
if dilations is None:
dilations = [1] * len(layer['kernel_shape'])

if get_onnx_attribute(node, 'group') != 1:
raise ValueError("Only 1 group supported corrently")

layer['in_width'] = input_shapes[0][-2]
layer['n_chan'] = input_shapes[0][-1]
layer['n_filt'] = input_shapes[1][0]

layer['n_dim'] = len(input_shapes[0]) - 2 # 2 comes from channels and batch dimentions
if layer['n_dim'] not in (1, 2):
raise ValueError("Only 1D and 2D convolutions are supported")
layer['class_name'] = 'Conv'

# set some values needed later
if layer['n_dim'] == 1:
# this is 1D convolution
full_width = layer['in_width'] + pads[0] + pads[1]
eff_kernel_width = kernel_shape[0] * dilations[0]
layer['out_width'] = int(np.ceil((full_width - eff_kernel_width + 1) / strides[0]))
# for compatibility interpret some variables
layer['pad_left'] = pads[0]
layer['pad_right'] = pads[1]

if all(x == 0 for x in pads): # No padding, i.e., 'VALID' padding
layer['padding'] = 'valid'
else:
layer['padding'] = 'same'

(layer['out_width'], _, _) = compute_padding_1d(
layer['padding'], layer['in_width'], layer['stride_width'], layer['filt_width']
)

output_shape = [input_shapes[0][0], layer['n_filt'], layer['out_width']]

elif len(input_shapes[0]) == 4: # Conv2D
layer['class_name'] = 'Conv2D'

layer['in_height'] = input_shapes[0][2]
layer['in_width'] = input_shapes[0][3]
layer['n_chan'] = input_shapes[0][1]

layer['filt_width'] = kernel_shape[0]
layer['stride_width'] = strides[0]
layer['dilation_width'] = dilations[0]
else:
# 2d
layer['in_height'] = input_shapes[0][-3]
full_height = layer['in_height'] + pads[0] + pads[2]
eff_kernel_height = kernel_shape[0] * dilations[0]
out_height = int(np.ceil((full_height - eff_kernel_height + 1) / strides[0]))
layer['out_height'] = out_height

full_width = input_shapes[0][-2] + pads[1] + pads[3]
eff_kernel_width = kernel_shape[1] * dilations[1]
out_width = int(np.ceil((full_width - eff_kernel_width + 1) / strides[1]))
layer['out_width'] = out_width
# for compatibility interpret some variables
layer['pad_top'] = pads[0]
layer['pad_left'] = pads[1]
layer['pad_bottom'] = pads[2]
layer['pad_right'] = pads[3]
layer['filt_height'] = kernel_shape[0]
layer['filt_width'] = kernel_shape[1]

layer['n_filt'] = next(
(x.type.tensor_type.shape.dim[1].dim_value for x in graph.value_info if x.name == node.output[0]), None
)
layer['stride_height'] = strides[0]
layer['stride_width'] = strides[1]
pads = compute_pads_2d(node, layer)

layer['pad_top'] = pads[0]
layer['pad_bottom'] = pads[2]
layer['pad_left'] = pads[1]
layer['pad_right'] = pads[3]

if all(x == 0 for x in pads): # No padding, i.e., 'VALID' padding in Keras/Tensorflow
layer['padding'] = 'valid'
else: # Only 'valid' and 'same' padding are available in Keras
layer['padding'] = 'same'

(layer['out_height'], layer['out_width'], _, _, _, _) = compute_padding_2d(
layer['padding'],
layer['in_height'],
layer['in_width'],
layer['stride_height'],
layer['stride_width'],
layer['filt_height'],
layer['filt_width'],
)

output_shape = [input_shapes[0][0], layer['n_filt'], layer['out_height'], layer['out_width']]
layer['dilation_height'] = dilations[0]
layer['dilation_width'] = dilations[1]

return layer, output_shape
return layer
103 changes: 57 additions & 46 deletions hls4ml/converters/onnx/core.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
from hls4ml.converters.onnx_to_hls import get_onnx_attribute, get_onnx_input_name, onnx_handler
import numpy as np

from hls4ml.converters.onnx_to_hls import get_onnx_attribute, onnx_handler

@onnx_handler(*['Gemm', 'MatMul'])
def parse_gemm_layer(reader, node, inputs_map, input_shapes, graph, config):

@onnx_handler('MatMul')
def parse_matmul_layer(node, input_names, input_shapes, graph):
layer = {}

layer['class_name'] = 'Dense'
layer['class_name'] = 'MatMul'
layer['name'] = node.name
layer['inputs'] = get_onnx_input_name(node, graph)

tran_weight = get_onnx_attribute(node, 'transB', 0)
reader.add_input(layer['name'], node.input, tran_weight)

weights_shape = reader.get_weights_data(layer['name'], 'kernel').shape
layer['n_in'] = weights_shape[0]
layer['n_out'] = weights_shape[1]

output_shape = input_shapes[0][:]
output_shape[-1] = layer['n_out']
layer['inputs'] = input_names
layer['outputs'] = list(node.output)

return layer, output_shape
return layer


# ------------------Global paras for activations
# TODO: repair HardSigmoid support
# https://github.com/fastmachinelearning/hls4ml/issues/409
activation_layers = [
Expand All @@ -37,7 +29,7 @@ def parse_gemm_layer(reader, node, inputs_map, input_shapes, graph, config):
'Softmax',
'Softsign',
'Softplus',
'Clip',
# 'Clip',
]
jmitrevs marked this conversation as resolved.
Show resolved Hide resolved

activation_map = {
Expand All @@ -53,70 +45,89 @@ def parse_gemm_layer(reader, node, inputs_map, input_shapes, graph, config):
'Softmax': 'Softmax',
'Softsign': 'Activation',
'Softplus': 'Activation',
'Clip': 'Clip',
# 'Clip': 'Clip',
}
jmitrevs marked this conversation as resolved.
Show resolved Hide resolved
# ---------


@onnx_handler(*activation_layers)
def parse_activation_layer(reader, node, inputs_map, input_shapes, graph, config):
def parse_activation_layer(node, input_names, input_shapes, graph):
layer = {}

layer['name'] = node.name
layer['class_name'] = activation_map[node.op_type]
layer['activation'] = node.op_type.lower()
layer['inputs'] = get_onnx_input_name(node, graph)
layer['inputs'] = input_names
layer['outputs'] = list(node.output)

if layer['class_name'] != 'Activation':
if layer['class_name'] == 'Softmax':
layer['activation'] = 'softmax'
layer['axis'] = get_onnx_attribute(node, 'axis', -1)

elif layer['class_name'] in ['ELU', 'LeakyReLU', 'ThresholdedReLU']:
layer['activation'] = layer['class_name']
layer['activ_param'] = get_onnx_attribute(node, 'alpha', 0.01)

elif layer['class_name'] == 'Clip':
clip_min_node = [x for x in graph.initializer if x.name in node.input]
clip_min = clip_min_node[0].float_data[0]
# # Don't yet support Clip
# elif layer['class_name'] == 'Clip':
# clip_min_node = [x for x in graph.initializer if x.name in input_names]
# clip_min = clip_min_node[0].float_data[0]

# Check if it's relu or not
if clip_min == 0.0:
layer['class_name'] = 'Activation'
layer['activation'] = 'ReLU'
else:
raise Exception('Clip with min != 0 is not supported yet!')
# # Check if it's relu or not
# if clip_min == 0.0:
# layer['class_name'] = 'Activation'
# layer['activation'] = 'ReLU'
# else:
# raise Exception('Clip with min != 0 is not supported yet!')

else:
layer['activation'] = layer['class_name']
layer['class_name'] = 'Activation'

return layer, [shape for shape in input_shapes[0]]
return layer


@onnx_handler('BatchNormalization')
def parse_batchnorm_layer(reader, node, inputs_map, input_shapes, graph, config):
def parse_batchnorm_layer(node, input_names, input_shapes, graph):
layer = {}

layer['class_name'] = 'BatchNormalization'
layer['data_format'] = 'channels_first'
layer['class_name'] = 'BatchNormOnnx'
layer['name'] = node.name
layer['inputs'] = get_onnx_input_name(node, graph)
layer['inputs'] = input_names
layer['outputs'] = list(node.output)

# Other attributes
layer['epsilon'] = get_onnx_attribute(node, 'epsilon')
layer['momentum'] = get_onnx_attribute(node, 'momentum')
layer['epsilon'] = get_onnx_attribute(node, 'epsilon', 1e-05)
# layer['momentum'] = get_onnx_attribute(node, 'momentum', 0.9) # not used

reader.add_input(layer['name'], node.input)

in_size = 1
for dim in input_shapes[0][1:]:
in_size *= dim

layer['n_in'] = layer['n_out'] = in_size
layer['n_in'] = layer['n_out'] = np.prod(input_shapes[0][1:])

if len(input_shapes[0]) == 2:
layer['n_filt'] = -1
elif len(input_shapes[0]) > 2:
layer['n_filt'] = input_shapes[0][1] # Always channel first for onnx
if node.domain != 'qonnx.custom_op.channels_last':
raise RuntimeError("Please convert the model to channels-last format with qonnx-to-channels-last")
layer['data_format'] = 'channels_last' # QONNX needs to be channels-last.
layer['n_filt'] = input_shapes[0][-1]
else:
raise RuntimeError(f"Unexpected input shape: {input_shapes[0]}")

return layer


@onnx_handler('Quant')
def parse_quant_layer(node, input_names, input_shapes, graph):
layer = {}

layer['class_name'] = 'Quant'
layer['name'] = node.name
layer['inputs'] = input_names
layer['outputs'] = list(node.output)

# Other attributes
layer['narrow'] = bool(get_onnx_attribute(node, 'narrow'))
layer['rounding_mode'] = get_onnx_attribute(node, 'rounding_mode')
layer['signed'] = bool(get_onnx_attribute(node, 'signed'))

return layer, [shape for shape in input_shapes[0]]
return layer
Loading
Loading