# # 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, )