Files
wehub-resource-sync 26446540fa
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

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