Files
paddlepaddle--paddle/test/ir/inference/test_quant_linear_fuse_pass.py
2026-07-13 12:40:42 +08:00

264 lines
9.2 KiB
Python

# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# 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.
import unittest
from functools import partial
import hypothesis.strategies as st
import numpy as np
from auto_scan_test import PassAutoScanTest
from program_config import OpConfig, ProgramConfig, TensorConfig
from paddle.base import core
@unittest.skipIf(
not core.is_compiled_with_cuda(),
"QuantLinear only supports cuda kernel.",
)
class TestQuantLinearFusePass(PassAutoScanTest):
r"""
x_var y_var(persistable)
| |
quantize_linear dequantize_linear
| |
quantize_linear_out_var dequantize_linear_out_var
| /
dequantize_linear /
| /
dequantize_linear_out_var /
\ /
\ /
\ /
\ /
\ /
\ /
\ /
\ /
matmul_v2
|
matmul_v2_out_var bias_var(persistable)
\ /
elementwise_add
"""
def sample_predictor_configs(self, program_config):
# for gpu
config = self.create_inference_config(
use_gpu=True, passes=["quant_linear_fuse_pass"]
)
yield config, ["quant_linear"], (0.4, 0.3)
def is_program_valid(self, prog_config):
input_num_col_dims = len(prog_config.inputs["input_x"].shape) - 1
add_x_rank = input_num_col_dims + 1
add_y_rank = len(prog_config.weights["bias"].shape)
axis = prog_config.ops[4].attrs["axis"]
if add_x_rank == add_y_rank:
if axis != -1 or axis != 0:
return False
return True
def sample_program_config(self, draw):
# 1. Generate input:X of matmul_v2
input_shape = draw(
st.lists(
st.integers(min_value=1, max_value=4), min_size=2, max_size=4
)
)
input_x = np.random.random(input_shape).astype(np.float32)
def generate_input_x():
return input_x
# 2. Generate quant dequant scale and zeropoint
def generate_input_scale():
scale = 1.0 / np.max(input_x)
return np.array(scale).astype(np.float32)
def generate_dequant_scale():
dequant_scale = np.max(input_x)
return np.array(dequant_scale).astype(np.float32)
def generate_quant_dequant_zeropoint():
return np.array(0.0).astype(np.float32)
def generate_weight_dequant_zeropoint():
return np.zeros(weight_shape[-1]).astype(np.float32)
# 3. Generate shape of input:Y of matmul_v2
weight_shape = draw(
st.lists(
st.integers(min_value=1, max_value=4), min_size=2, max_size=2
)
)
# follow the behavior of the input_num_col_dims attr of quant_linear
input_num_col_dims = len(input_shape) - 1
weight_shape[0] = int(np.prod(input_shape[input_num_col_dims:]))
def round_array_with_ties_to_even(x):
xLower = np.floor(x)
xUpper = np.ceil(x)
dLower = x - xLower
dUpper = xUpper - x
x[(dLower == dUpper) & (xLower % 2 == 0)] = xLower[
(dLower == dUpper) & (xLower % 2 == 0)
]
x[(dLower == dUpper) & (xLower % 2 != 0)] = xUpper[
(dLower == dUpper) & (xLower % 2 != 0)
]
x[dLower < dUpper] = xLower[dLower < dUpper]
x[dLower > dUpper] = xUpper[dLower > dUpper]
def round_array(x):
x[x > 0] = np.ceil(x[x > 0])
x[x <= 0] = np.floor(x[x <= 0])
weights = np.random.random(weight_shape).astype("float32")
# 4. Generate the weight_dequant_scale
def generate_weight_dequant_scale():
return np.max(weights, axis=0)
# 5. Generate the weight which is float type but stores int8 value(align with the behavior of PaddleSlim)
def generate_input_weights(
quant_round_type=0, quant_max_bound=127, quant_min_bound=-127
):
# scale_weights = 1.0 / np.max(weights, axis=0)
scale_weights = 1.0 / generate_weight_dequant_scale()
quant_weights = quant_max_bound * scale_weights * weights
if quant_round_type == 0:
round_array_with_ties_to_even(quant_weights)
else:
round_array(quant_weights)
quant_weights[quant_weights > quant_max_bound] = quant_max_bound
quant_weights[quant_weights < quant_min_bound] = quant_min_bound
return quant_weights
# 6. Generate shape of Output of matmul_v2
mul_out_shape = input_shape[:input_num_col_dims] + weight_shape[1:]
# 7. Generate the bias shape
bias_shape = [mul_out_shape[-1]]
has_relu = draw(st.booleans())
quantize_linear_op = OpConfig(
"quantize_linear",
inputs={
"X": ["input_x"],
"Scale": ["quant_scale"],
"ZeroPoint": ["quant_zero_point"],
},
outputs={"Y": ["quantize_linear_op_out"]},
attrs={"quant_axis": -1, "bit_length": 8, "round_type": 0},
)
dequantize_linear_op = OpConfig(
"dequantize_linear",
inputs={
"X": ["quantize_linear_op_out"],
"Scale": ["dequant_scale"],
"ZeroPoint": ["dequant_zero_point"],
},
outputs={"Y": ["dequantize_linear_op_out"]},
attrs={"quant_axis": -1, "bit_length": 8, "round_type": 0},
)
weight_dequantize_linear_op = OpConfig(
"dequantize_linear",
inputs={
"X": ["input_weight"],
"Scale": ["weight_dequant_scale"],
"ZeroPoint": ["weight_dequant_zero_point"],
},
outputs={"Y": ["weight_dequantize_linear_op_out"]},
attrs={"quant_axis": 1, "bit_length": 8, "round_type": 0},
)
matmul_v2_op = OpConfig(
"matmul_v2",
inputs={
"X": ["dequantize_linear_op_out"],
"Y": ["weight_dequantize_linear_op_out"],
},
outputs={"Out": ["matmul_v2_op_out"]},
)
elementwise_add_op = OpConfig(
"elementwise_add",
inputs={"X": ["matmul_v2_op_out"], "Y": ["bias"]},
outputs={"Out": ["elementwise_add_op_out"]},
axis=-1,
)
ops = [
quantize_linear_op,
dequantize_linear_op,
weight_dequantize_linear_op,
matmul_v2_op,
elementwise_add_op,
]
if has_relu:
relu_op = OpConfig(
"relu",
inputs={"X": ["elementwise_add_op_out"]},
outputs={"Out": ["relu_out"]},
)
ops.append(relu_op)
program_config = ProgramConfig(
ops=ops,
weights={
"input_weight": TensorConfig(
data_gen=partial(generate_input_weights)
),
"bias": TensorConfig(shape=bias_shape),
"quant_scale": TensorConfig(
data_gen=partial(generate_input_scale)
),
"dequant_scale": TensorConfig(
data_gen=partial(generate_dequant_scale)
),
"weight_dequant_scale": TensorConfig(
data_gen=partial(generate_weight_dequant_scale)
),
"quant_zero_point": TensorConfig(
data_gen=partial(generate_quant_dequant_zeropoint)
),
"dequant_zero_point": TensorConfig(
data_gen=partial(generate_quant_dequant_zeropoint)
),
"weight_dequant_zero_point": TensorConfig(
data_gen=partial(generate_weight_dequant_zeropoint)
),
},
inputs={
"input_x": TensorConfig(data_gen=partial(generate_input_x))
},
outputs=ops[-1].outputs["Out"],
)
return program_config
def test(self):
self.run_and_statistics(
quant=False,
max_examples=30,
passes=["quant_linear_fuse_pass"],
)
if __name__ == "__main__":
unittest.main()