217 lines
7.8 KiB
Python
217 lines
7.8 KiB
Python
#
|
|
# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
|
|
|
|
from network_pool import pippin_28_28
|
|
from tensorflow_quantization import custom_qdq_cases
|
|
import pytest
|
|
from tensorflow_quantization import quantize_model
|
|
from tensorflow_quantization.utils import convert_saved_model_to_onnx
|
|
from tensorflow_quantization.utils import CreateAssetsFolders
|
|
|
|
import tensorflow as tf
|
|
|
|
test_assets = CreateAssetsFolders("test_custom_qdq_cases")
|
|
|
|
|
|
def test_resnet_residual_qdq_case():
|
|
model = pippin_28_28()
|
|
test_assets.add_folder("pipin_28_28")
|
|
tf.keras.models.save_model(model, test_assets.pipin_28_28.fp32_saved_model)
|
|
convert_saved_model_to_onnx(
|
|
saved_model_dir=test_assets.pipin_28_28.fp32_saved_model,
|
|
onnx_model_path=test_assets.pipin_28_28.fp32_onnx_model,
|
|
)
|
|
|
|
resnet_residual_qdq = custom_qdq_cases.ResNetV1QDQCase()
|
|
r = resnet_residual_qdq.case(model, None)
|
|
expected_qdq_insertion = {
|
|
"add": 1,
|
|
"add_1": "any",
|
|
}
|
|
assert (
|
|
len(r.layers) == 2
|
|
), "There should be 2 custom layers, but found {}".format(len(r.layers))
|
|
for l in r.layers:
|
|
if l.name not in expected_qdq_insertion:
|
|
raise Exception(
|
|
"Layer {} is not expected to be treated as custom layer".format(l.name)
|
|
)
|
|
else:
|
|
if l.quantization_index != None:
|
|
if expected_qdq_insertion[l.name] == "any":
|
|
continue
|
|
assert (
|
|
l.quantization_index[0] == expected_qdq_insertion[l.name]
|
|
), "For layer {l_name}, only {expected_qdq} indices should be quantized".format(
|
|
l_name=l.name, expected_qdq=expected_qdq_insertion[l.name]
|
|
)
|
|
|
|
|
|
def assert_add_bn_expected_layers(
|
|
r, expected_add_layer_behavior, expected_bn_layer_behavior, expected_mp_layer_behavior
|
|
):
|
|
assert len(r.layers) == (
|
|
len(expected_add_layer_behavior) + len(expected_bn_layer_behavior) + len(expected_mp_layer_behavior)
|
|
), "Not all expected layers are captured for ResNet custom QDQ case."
|
|
for l in r.layers:
|
|
assert (
|
|
l.name in expected_add_layer_behavior
|
|
or l.name in expected_bn_layer_behavior
|
|
or l.name in expected_mp_layer_behavior
|
|
), "layer {} is not expected to be captured for ResNet custom QDQ case".format(
|
|
l.name
|
|
)
|
|
if "add" in l.name:
|
|
if expected_add_layer_behavior[l.name] == "any":
|
|
continue
|
|
assert l.quantization_index[0] == expected_add_layer_behavior[l.name], (
|
|
"For layer {l_name}, expected quantization index is {expected_add_behavior} but index {l_quant_id} "
|
|
"is captured in ResNet custom QDQ case.".format(
|
|
l_name=l.name,
|
|
expected_add_behavior=expected_add_layer_behavior[l.name],
|
|
l_quant_idx=l.quantization_index[0],
|
|
)
|
|
)
|
|
|
|
|
|
def test_resnet50_residual_qdq_case():
|
|
resnet50 = tf.keras.applications.resnet50.ResNet50(weights=None)
|
|
test_assets.add_folder("resnet50_v1")
|
|
tf.keras.models.save_model(resnet50, test_assets.resnet50_v1.fp32_saved_model)
|
|
convert_saved_model_to_onnx(
|
|
saved_model_dir=test_assets.resnet50_v1.fp32_saved_model,
|
|
onnx_model_path=test_assets.resnet50_v1.fp32_onnx_model,
|
|
)
|
|
|
|
resnet_custom_qdq_case = custom_qdq_cases.ResNetV1QDQCase()
|
|
r = resnet_custom_qdq_case.case(resnet50, None)
|
|
for r_1 in r.layers:
|
|
print("\"{}\",".format(r_1.name))
|
|
|
|
expected_add_layer_behavior = {
|
|
"conv2_block1_add": "any",
|
|
"conv2_block2_add": 0,
|
|
"conv2_block3_add": 0,
|
|
"conv3_block1_add": "any",
|
|
"conv3_block2_add": 0,
|
|
"conv3_block3_add": 0,
|
|
"conv3_block4_add": 0,
|
|
"conv4_block1_add": "any",
|
|
"conv4_block2_add": 0,
|
|
"conv4_block3_add": 0,
|
|
"conv4_block4_add": 0,
|
|
"conv4_block5_add": 0,
|
|
"conv4_block6_add": 0,
|
|
"conv5_block1_add": "any",
|
|
"conv5_block2_add": 0,
|
|
"conv5_block3_add": 0,
|
|
}
|
|
|
|
# Empty, no BatchNorm layers should be quantized in ResNet-v1
|
|
expected_bn_layer_behavior = {}
|
|
|
|
# MaxPool quantization is actually not needed in ResNet-v1
|
|
expected_mp_layer_behavior = {}
|
|
|
|
assert_add_bn_expected_layers(
|
|
r, expected_add_layer_behavior, expected_bn_layer_behavior, expected_mp_layer_behavior
|
|
)
|
|
|
|
q_resnet50 = quantize_model(resnet50, custom_qdq_cases=[resnet_custom_qdq_case])
|
|
tf.keras.models.save_model(q_resnet50, test_assets.resnet50_v1.int8_saved_model)
|
|
convert_saved_model_to_onnx(
|
|
saved_model_dir=test_assets.resnet50_v1.int8_saved_model,
|
|
onnx_model_path=test_assets.resnet50_v1.int8_onnx_model,
|
|
)
|
|
|
|
|
|
def test_resnet50v2_bn_qdq_case():
|
|
resnet50_v2 = tf.keras.applications.resnet_v2.ResNet50V2(weights=None)
|
|
test_assets.add_folder("resnet50_v2")
|
|
tf.keras.models.save_model(resnet50_v2, test_assets.resnet50_v2.fp32_saved_model)
|
|
convert_saved_model_to_onnx(
|
|
saved_model_dir=test_assets.resnet50_v2.fp32_saved_model,
|
|
onnx_model_path=test_assets.resnet50_v2.fp32_onnx_model,
|
|
)
|
|
|
|
resnet_custom_qdq_case = custom_qdq_cases.ResNetV2QDQCase()
|
|
r = resnet_custom_qdq_case.case(resnet50_v2, None)
|
|
for r_1 in r.layers:
|
|
print("\"{}\",".format(r_1.name))
|
|
|
|
expected_add_layer_behavior = {
|
|
"conv2_block1_out": 0,
|
|
"conv2_block2_out": 0,
|
|
"conv2_block3_out": 0,
|
|
"conv3_block1_out": 0,
|
|
"conv3_block2_out": 0,
|
|
"conv3_block3_out": 0,
|
|
"conv3_block4_out": 0,
|
|
"conv4_block1_out": 0,
|
|
"conv4_block2_out": 0,
|
|
"conv4_block3_out": 0,
|
|
"conv4_block4_out": 0,
|
|
"conv4_block5_out": 0,
|
|
"conv4_block6_out": 0,
|
|
"conv5_block1_out": 0,
|
|
"conv5_block2_out": 0,
|
|
"conv5_block3_out": 0,
|
|
}
|
|
|
|
# ResNet-v2 quantizes BatchNorms that are not connected to Conv layers
|
|
expected_bn_layer_behavior = {
|
|
"conv2_block1_preact_bn",
|
|
"conv2_block2_preact_bn",
|
|
"conv2_block3_preact_bn",
|
|
"conv3_block1_preact_bn",
|
|
"conv3_block2_preact_bn",
|
|
"conv3_block3_preact_bn",
|
|
"conv3_block4_preact_bn",
|
|
"conv4_block1_preact_bn",
|
|
"conv4_block2_preact_bn",
|
|
"conv4_block3_preact_bn",
|
|
"conv4_block4_preact_bn",
|
|
"conv4_block5_preact_bn",
|
|
"conv4_block6_preact_bn",
|
|
"conv5_block1_preact_bn",
|
|
"conv5_block2_preact_bn",
|
|
"conv5_block3_preact_bn",
|
|
"post_bn",
|
|
}
|
|
|
|
# ResNet-v2 quantizes all MaxPool layers
|
|
expected_mp_layer_behavior = {
|
|
"pool1_pool",
|
|
"max_pooling2d",
|
|
"max_pooling2d_1",
|
|
"max_pooling2d_2",
|
|
}
|
|
|
|
assert_add_bn_expected_layers(
|
|
r, expected_add_layer_behavior, expected_bn_layer_behavior, expected_mp_layer_behavior
|
|
)
|
|
|
|
q_resnet50_v2 = quantize_model(
|
|
resnet50_v2, custom_qdq_cases=[resnet_custom_qdq_case]
|
|
)
|
|
tf.keras.models.save_model(q_resnet50_v2, test_assets.resnet50_v2.int8_saved_model)
|
|
convert_saved_model_to_onnx(
|
|
saved_model_dir=test_assets.resnet50_v2.int8_saved_model,
|
|
onnx_model_path=test_assets.resnet50_v2.int8_onnx_model,
|
|
)
|