166 lines
5.8 KiB
Python
166 lines
5.8 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you 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.
|
|
# pylint: disable=invalid-name
|
|
"""Default legalization function for quantize/dequantize operators."""
|
|
|
|
import tvm
|
|
from tvm import te, tirx
|
|
from tvm.ir import Call
|
|
from tvm.runtime import DataTypeCode
|
|
|
|
from ...block_builder import BlockBuilder
|
|
from ...expr import Expr
|
|
from .common import _try_convert_to_scalar_const, register_legalize
|
|
|
|
|
|
def clip_cast(val, dtype):
|
|
const_min = tvm.tirx.min_value(dtype)
|
|
const_max = tvm.tirx.max_value(dtype)
|
|
return te.max(te.min(val, const_max), const_min).astype(dtype)
|
|
|
|
|
|
def is_const_scalar(x):
|
|
return isinstance(x, tvm.tirx.IntImm | tvm.tirx.FloatImm)
|
|
|
|
|
|
def _is_singleton_qparam(qparam: te.Tensor) -> bool:
|
|
"""Return True if qparam is a tensor with all dimensions equal to 1."""
|
|
if not isinstance(qparam, te.Tensor):
|
|
return False
|
|
if len(qparam.shape) == 0:
|
|
return True
|
|
for dim in qparam.shape:
|
|
if not isinstance(dim, tirx.IntImm) or dim.value != 1:
|
|
return False
|
|
return True
|
|
|
|
|
|
@register_legalize("relax.quantize")
|
|
def _quantize(bb: BlockBuilder, call: Call) -> Expr:
|
|
"""
|
|
Lower relax.quantize into the sequence of simple operations.
|
|
Quantization formula is defined as: out = clip(round(input / scale) + zp, min_val, max_val)
|
|
"""
|
|
axis = call.attrs.axis
|
|
out_dtype = call.attrs.out_dtype
|
|
|
|
def te_quantize(
|
|
data: te.Tensor,
|
|
scale: te.Tensor | tirx.IntImm | tirx.FloatImm,
|
|
zp: te.Tensor | tirx.IntImm | tirx.FloatImm,
|
|
):
|
|
scale_singleton = _is_singleton_qparam(scale) if isinstance(scale, te.Tensor) else False
|
|
zp_singleton = _is_singleton_qparam(zp) if isinstance(zp, te.Tensor) else False
|
|
|
|
def quantize_compute(*indices):
|
|
if is_const_scalar(scale):
|
|
scale_value = scale
|
|
elif scale_singleton:
|
|
scale_value = scale[(0,) * len(scale.shape)]
|
|
else:
|
|
scale_value = scale[indices[axis]]
|
|
|
|
if is_const_scalar(zp):
|
|
zp_value = zp
|
|
elif zp_singleton:
|
|
zp_value = zp[(0,) * len(zp.shape)]
|
|
else:
|
|
zp_value = zp[indices[axis]]
|
|
scaled = data[indices] / scale_value
|
|
round_val = (te.round(scaled) if "int" in out_dtype else scaled) + zp_value
|
|
return clip_cast(round_val, out_dtype)
|
|
|
|
output_shape = data.shape
|
|
return te.compute(output_shape, quantize_compute, name="quantized")
|
|
|
|
return bb.call_te(
|
|
te_quantize,
|
|
call.args[0],
|
|
_try_convert_to_scalar_const(call.args[1]),
|
|
_try_convert_to_scalar_const(call.args[2]),
|
|
primfunc_name_hint="quantize",
|
|
)
|
|
|
|
|
|
@register_legalize("relax.dequantize")
|
|
def _dequantize(bb: BlockBuilder, call: Call) -> Expr:
|
|
"""
|
|
Lower relax.dequantize into the sequence of simple operations.
|
|
Dequantization formula is defined as: out = scale * (input - zp)
|
|
Compute datatype: float32
|
|
|
|
Example of lowering:
|
|
|
|
dtype = ["int32"|"float32"]
|
|
|
|
qnn.dequantize(data, scale, zp, "float32") -->
|
|
sub = subtract(cast(data, dtype), zp)
|
|
out = multiply(cast(sub, "float32"), scale)
|
|
|
|
qnn.dequantize(data, scale, zp, "float16") -->
|
|
sub = subtract(cast(data, dtype), zp)
|
|
mul = multiply(cast(sub, "float32"), cast(scale, "float32"))
|
|
clipped_out = clip(mul, float32(-65504.0), float32(65504.0))
|
|
out = cast(clipped_out, "float16")
|
|
"""
|
|
axis = call.attrs.axis
|
|
out_dtype = call.attrs.out_dtype
|
|
|
|
def te_dequantize(
|
|
data: te.Tensor,
|
|
scale: te.Tensor | tirx.IntImm | tirx.FloatImm,
|
|
zp: te.Tensor | tirx.IntImm | tirx.FloatImm,
|
|
):
|
|
scale_singleton = _is_singleton_qparam(scale) if isinstance(scale, te.Tensor) else False
|
|
zp_singleton = _is_singleton_qparam(zp) if isinstance(zp, te.Tensor) else False
|
|
|
|
def dequantize_compute(*indices):
|
|
if is_const_scalar(scale):
|
|
scale_value = scale
|
|
elif scale_singleton:
|
|
scale_value = scale[(0,) * len(scale.shape)]
|
|
else:
|
|
scale_value = scale[indices[axis]]
|
|
|
|
if is_const_scalar(zp):
|
|
zp_value = zp
|
|
elif zp_singleton:
|
|
zp_value = zp[(0,) * len(zp.shape)]
|
|
else:
|
|
zp_value = zp[indices[axis]]
|
|
dtype = (
|
|
"float32"
|
|
if data.dtype.matches_code(DataTypeCode.FLOAT, DataTypeCode.BFLOAT)
|
|
else "int32"
|
|
)
|
|
sub = data[indices].astype(dtype) - zp_value
|
|
out = sub * scale_value.astype("float32")
|
|
if out_dtype == "float32":
|
|
return out
|
|
return clip_cast(out, out_dtype)
|
|
|
|
output_shape = data.shape
|
|
return te.compute(output_shape, dequantize_compute, name="dequantized")
|
|
|
|
return bb.call_te(
|
|
te_dequantize,
|
|
call.args[0],
|
|
_try_convert_to_scalar_const(call.args[1]),
|
|
_try_convert_to_scalar_const(call.args[2]),
|
|
primfunc_name_hint="dequantize",
|
|
)
|