HardSigmoid#

参考:HardSigmoid

HardSigmoid 函数

\[ \operatorname{HardSigmoid}(x) = \max(0, \min(1, \alpha x + \beta)), \]

通常情况 \(\alpha=0.2\)\(\beta=0.5\)

from pathlib import Path
temp_dir = Path(".temp")
temp_dir.mkdir(exist_ok=True)
model_path = f"{temp_dir}/HardSigmoid.onnx" # 模型存储路径

构建模型:

from onnxscript import opset20 as op
from onnxscript import ir
from onnxscript import script
from onnxscript import FLOAT
import onnx

@script()
def model(x: FLOAT[1, 3, 4, 4]) -> FLOAT[1, 3, 4, 4]:
    x = op.Add(x, x)
    # y = op.HardSigmoid(x)
    # x = op.Mul(x, y)
    # x = op.HardSigmoid(x)
    return x
    # return op.HardSwish(x,)

onnx.save_model(model.to_model_proto(), model_path,)
model: Already defined.

转换为 Relay 模型#

from tvm.driver.tvmc.frontends import load_model

model = load_model(model_path)
model.mod.show()
def @main(%x: Tensor[(1, 3, 4, 4), float32] /* ty=Tensor[(1, 3, 4, 4), float32] span=n0.x:0:0 */) -> Tensor[(1, 3, 4, 4), float32] {
  add(%x, %x) /* ty=Tensor[(1, 3, 4, 4), float32] span=n0:0:0 */
}

构建模板#

from tvm.relay.dataflow_pattern import (
    TupleGetItemPattern, is_op, wildcard,
    is_constant, rewrite,
    DFPatternCallback
)
import tvm
from tvm.relay import op as _op
from tvm.relay import transform as _transform
from tvm import relay

def make_hard_sigmoid_pattern():
    r"""匹配 ONNX HardSigmoid 算子的模式"""
    x = wildcard()
    alpha = is_constant()
    x = is_op("multiply")(x, alpha)
    beta = is_constant()
    x = is_op("add")(x, beta)
    x = is_op("clip")(x)
    return x
compiler = "ccompiler"
pattern_table = [(f"{compiler}.hard_sigmoid", make_hard_sigmoid_pattern())]


seq = tvm.transform.Sequential(
    [
        _transform.MergeComposite(pattern_table),
        # _transform.AnnotateTarget(compiler),
        # _transform.MergeCompilerRegions(),
        # _transform.PartitionGraph(),
        # _transform.InferType(),
        # _transform.Inline(),
        # _transform.DefuseOps()
    ]
)
mod = seq(model.mod)
mod.show()
def @main(%x: Tensor[(1, 3, 4, 4), float32] /* ty=Tensor[(1, 3, 4, 4), float32] span=n0.x:0:0 */) -> Tensor[(1, 3, 4, 4), float32] {
  add(%x, %x) /* ty=Tensor[(1, 3, 4, 4), float32] span=n0:0:0 */
}
import numpy as np

def get_calibration_dataset(mod, input_name):
    dataset = []
    input_shape = [int(x) for x in mod["main"].checked_type.arg_types[0].shape]
    for i in range(5):
        data = np.random.uniform(size=input_shape)
        dataset.append({input_name: data})
    return dataset
dataset = get_calibration_dataset(mod, "x")
with tvm.transform.PassContext(opt_level=3):
    with relay.quantize.qconfig(
        skip_conv_layers=[],
        # calibrate_mode="kl_divergence", 
        weight_scale="max",
        round_for_shift=True,
        # rounding="TONEAREST", # "UPWARD" or "TONEAREST"
        # calibrate_skip_layers=[],
        skip_dense_layer=False,
    ):
        qmod = relay.quantize.quantize(model.mod, model.params, dataset)
qmod.show()
def @main(%x: Tensor[(1, 3, 4, 4), float32] /* ty=Tensor[(1, 3, 4, 4), float32] span=n0.x:0:0 */) -> Tensor[(1, 3, 4, 4), float32] {
  add(%x, %x) /* ty=Tensor[(1, 3, 4, 4), float32] span=n0:0:0 */
}