# Copyright (c) ONNX Project Contributors # # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations import numpy as np import onnx from onnx import TensorProto from onnx.backend.test.case.base import Base from onnx.backend.test.case.node import expect from onnx.helper import make_tensor class QuantizeLinear(Base): @staticmethod def export() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale", "y_zero_point"], outputs=["y"], ) x = np.array([0, 2, 3, 1000, -254, -1000]).astype(np.float32) y_scale = np.float32(2) y_zero_point = np.uint8(128) y = np.array([128, 129, 130, 255, 1, 0]).astype(np.uint8) expect( node, inputs=[x, y_scale, y_zero_point], outputs=[y], name="test_quantizelinear", ) @staticmethod def export_axis() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale", "y_zero_point"], outputs=["y"], ) x = np.array( [ [ [[-162, 10], [-100, 232], [-20, -50]], [[-76, 0], [0, 252], [32, -44]], [[245, -485], [-960, -270], [-375, -470]], ], ], dtype=np.float32, ) y_scale = np.array([2, 4, 5], dtype=np.float32) y_zero_point = np.array([84, 24, 196], dtype=np.uint8) y = (x / y_scale.reshape(1, 3, 1, 1) + y_zero_point.reshape(1, 3, 1, 1)).astype( np.uint8 ) expect( node, inputs=[x, y_scale, y_zero_point], outputs=[y], name="test_quantizelinear_axis", ) @staticmethod def export_e4m3fn() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale", "y_zero_point"], outputs=["y"], ) x = np.array([0.0, 1.0, 2.0, 100000.0, 200.0]).astype(np.float32) y_scale = np.float32(2) y_zero_point = make_tensor("y_zero_point", TensorProto.FLOAT8E4M3FN, [1], [0]) y = make_tensor("y", TensorProto.FLOAT8E4M3FN, [5], [0, 0.5, 1, 448, 96]) expect( node, inputs=[x, y_scale, y_zero_point], outputs=[y], name="test_quantizelinear_e4m3fn", ) @staticmethod def export_e5m2() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale", "y_zero_point"], outputs=["y"], ) x = np.array([0.0, 1.0, 2.0, 100000.0, 200.0]).astype(np.float32) y_scale = np.float32(2) y_zero_point = make_tensor("y_zero_point", TensorProto.FLOAT8E5M2, [1], [0.0]) y = make_tensor("y", TensorProto.FLOAT8E5M2, [5], [0, 0.5, 1, 49152, 96]) expect( node, inputs=[x, y_scale, y_zero_point], outputs=[y], name="test_quantizelinear_e5m2", ) @staticmethod def export_uint16() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale", "y_zero_point"], outputs=["y"], ) x = np.array( [ 0.0, -128.0, 3.0, -3.0, 2.9, -2.9, 3.1, -3.1, 65536.0, -65534.0, 70000.0, -70000.0, ] ).astype(np.float32) y_scale = np.float32(2.0) y_zero_point = np.uint16(32767) y = np.array( [ 32767, 32703, 32769, 32765, 32768, 32766, 32769, 32765, 65535, 0, 65535, 0, ] ).astype(np.uint16) expect( node, inputs=[x, y_scale, y_zero_point], outputs=[y], name="test_quantizelinear_uint16", ) @staticmethod def export_int16() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale", "y_zero_point"], outputs=["y"], ) x = np.array( [ 0.0, -514.0, 3.0, -3.0, 2.9, -2.9, 3.1, -3.1, 65022.0, -66046.0, 65023.0, -66047.0, 65024.0, -66048.0, 70000.0, -70000.0, ] ).astype(np.float32) y_scale = np.float32(2.0) y_zero_point = np.int16(256) y = np.array( [ 256, -1, 258, 254, 257, 255, 258, 254, 32767, -32767, 32767, -32768, 32767, -32768, 32767, -32768, ] ).astype(np.int16) expect( node, inputs=[x, y_scale, y_zero_point], outputs=[y], name="test_quantizelinear_int16", ) @staticmethod def export_uint4() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale", "y_zero_point"], outputs=["y"], axis=0, ) x = np.array( [ [0.0, 2.5, 4.8, 8.6], [-30, -20, 6, 9], [12, 15, 16, 40], ] ).astype(np.float32) y_scale = np.asarray([2.0, 3.0, 4.0], dtype=np.float32) y_zero_point = make_tensor( "y_zero_point", TensorProto.UINT4, y_scale.shape, np.ones_like(y_scale) ) y = make_tensor( "y", TensorProto.UINT4, x.shape, [1, 2, 3, 5, -1, -1, 3, 4, 4, 5, 5, 11] ) expect( node, inputs=[x, y_scale, y_zero_point], outputs=[y], name="test_quantizelinear_uint4", ) @staticmethod def export_int4() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale", "y_zero_point"], outputs=["y"], axis=0, ) x = np.array( [ [0.0, 2.5, 4.8, 8.6], [-30, -20, 6, 9], [12, 15, 16, 40], ] ).astype(np.float32) y_scale = np.asarray([2.0, 3.0, 4.0], dtype=np.float32) y_zero_point = make_tensor( "y_zero_point", TensorProto.INT4, y_scale.shape, np.ones_like(y_scale) ) y = make_tensor( "y", TensorProto.INT4, x.shape, [1, 2, 3, 5, -8, -6, 3, 4, 4, 5, 5, 7] ) expect( node, inputs=[x, y_scale, y_zero_point], outputs=[y], name="test_quantizelinear_int4", ) @staticmethod def export_float4e2m1() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale", "y_zero_point"], outputs=["y"], axis=0, ) x = np.array( [ [0.0, 2.5, 4.8, 8.6], [-30, -20, 6, 9], [-0.0, -2.5, -4.8, -8.6], ] ).astype(np.float32) y_scale = np.asarray([2.0, 3.0, 4.0], dtype=np.float32) y_zero_point = make_tensor( "y_zero_point", TensorProto.FLOAT4E2M1, y_scale.shape, np.zeros_like(y_scale), ) y = make_tensor( "y", TensorProto.FLOAT4E2M1, x.shape, [0, 1, 2, 4, -6, -6, 2, 3, 0, -0.5, -1, -2], ) expect( node, inputs=[x, y_scale, y_zero_point], outputs=[y], name="test_quantizelinear_float4e2m1", ) @staticmethod def export_blocked_asymmetric() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale", "y_zero_point"], outputs=["y"], axis=1, block_size=2, ) x = np.array( [ [6.0, 12.0, 50.0, 5.0], [1.0, 8.0, 4.0, 5.0], [0.0, 20.0, 10.0, 4.0], ], dtype=np.float32, ) y_scale = np.array( [ [1.5, 2.5], [3.0, 4.9], [5.1, 6.9], ], dtype=np.float32, ) y_zero_point = np.array( [ [0, 1], [1, 0], [2, 3], ], dtype=np.uint8, ) # x.shape = (3, 4) # y_scale.shape = (3, 2) assert y_scale.shape == y_zero_point.shape block_axis = 1 # The block shape is [x.shape[i] // y_scale.shape[i] for i in range(len(x.shape))] = (1, 2) assert all( x.shape[i] == y_scale.shape[i] for i in range(len(x.shape)) if i != block_axis ) assert x.shape[block_axis] % y_scale.shape[block_axis] == 0 repeats = x.shape[block_axis] // y_scale.shape[block_axis] # Create element-wise scale and zero point y_scale_elementwise = np.repeat(y_scale, repeats=repeats, axis=block_axis) y_zero_point_elementwise = np.repeat( y_zero_point, repeats=repeats, axis=block_axis ) y = np.rint(x / y_scale_elementwise + y_zero_point_elementwise).astype(np.uint8) expect( node, inputs=[x, y_scale, y_zero_point], outputs=[y], name="test_quantizelinear_blocked_asymmetric", ) @staticmethod def export_blocked_symmetric() -> None: node = onnx.helper.make_node( "QuantizeLinear", inputs=["x", "y_scale"], outputs=["y"], axis=1, block_size=2, output_dtype=TensorProto.INT16, ) x = np.array( [ [6.0, -8, -10, 5.0], [1.0, 8.0, 4.0, 5.0], [0.0, 20.0, 10.0, 4.0], ], dtype=np.float32, ) y_scale = np.array( [ [1.5, 2.5], [3.0, 4.9], [5.1, 6.9], ], dtype=np.float32, ) # x.shape = (3, 4) # y_scale.shape = (3, 2) block_axis = 1 # The block shape is [x.shape[i] // y_scale.shape[i] for i in range(len(x.shape))] = (1, 2) assert all( x.shape[i] == y_scale.shape[i] for i in range(len(x.shape)) if i != block_axis ) assert x.shape[block_axis] % y_scale.shape[block_axis] == 0 repeats = x.shape[block_axis] // y_scale.shape[block_axis] # Create element-wise scale and zero point y_scale_elementwise = np.repeat(y_scale, repeats=repeats, axis=block_axis) y_val = np.clip( np.rint(x / y_scale_elementwise), a_min=-32768, a_max=32767 ).astype(np.int16) y = make_tensor( "y", TensorProto.INT16, x.shape, y_val, ) expect( node, inputs=[x, y_scale], outputs=[y], name="test_quantizelinear_blocked_symmetric", )