728 lines
22 KiB
Python
728 lines
22 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, unused-argument, pointless-exception-statement
|
|
"""Pattern table for CLML backend"""
|
|
|
|
import tvm
|
|
from tvm import IRModule, relax, tirx
|
|
from tvm.ir.transform import PassContext, module_pass
|
|
from tvm.relax import transform
|
|
from tvm.relax.dpl.pattern import (
|
|
GlobalVarPattern,
|
|
TuplePattern,
|
|
is_const,
|
|
is_op,
|
|
is_tuple_get_item,
|
|
wildcard,
|
|
)
|
|
from tvm.relax.expr import TupleGetItem, VarBinding
|
|
from tvm.relax.expr_functor import PyExprMutator, mutator
|
|
from tvm.relax.transform import PatternCheckContext
|
|
|
|
from ..pattern_registry import register_patterns
|
|
|
|
|
|
def _dtype_str(dtype):
|
|
return str(dtype.dtype) if isinstance(dtype, tvm.ir.PrimType) else str(dtype)
|
|
|
|
|
|
@mutator
|
|
class AppendReshapeToBNRewriter(PyExprMutator):
|
|
"""
|
|
Append Reshape Operator to BatchNorm Pass Rewriter Pass
|
|
|
|
- Automatically appends a reshape operation after BatchNorm operators
|
|
- Resolves fusion issues for custom backends where BatchNorm output
|
|
might explicitly access the first elment of the Tuple
|
|
|
|
Algo:
|
|
Identifies BatchNorm operators in the computational graph
|
|
When BatchNorm's first output is accessed via TupleGetItem
|
|
Automatically inserts a reshape operation to match input shape
|
|
|
|
"""
|
|
|
|
def __init__(self, mod):
|
|
super().__init__(mod)
|
|
self.bn_vars = {}
|
|
|
|
def visit_tuple_getitem_(self, op: TupleGetItem):
|
|
tuple_value = op.tuple_value
|
|
reshape_op = tvm.ir.Op.get("relax.reshape")
|
|
|
|
if isinstance(tuple_value, relax.Var) and tuple_value in self.bn_vars:
|
|
bn_call = self.bn_vars[tuple_value]
|
|
if op.index == 0:
|
|
bn_out = relax.TupleGetItem(bn_call, 0)
|
|
input_shape = bn_call.args[0].ty.shape
|
|
return relax.Call(reshape_op, [bn_out, input_shape])
|
|
|
|
return super().visit_tuple_getitem_(op)
|
|
|
|
def visit_var_binding_(self, binding: VarBinding):
|
|
if isinstance(binding.value, relax.Call) and binding.value.op.name == "relax.nn.batch_norm":
|
|
self.bn_vars[binding.var] = binding.value
|
|
return super().visit_var_binding_(binding)
|
|
|
|
|
|
@transform.function_pass(opt_level=0, name="AppendReshapeToBN")
|
|
class AppendReshapeToBNRewriterPass:
|
|
def transform_function(
|
|
self, func: relax.Function, mod: IRModule, _ctx: tvm.transform.PassContext
|
|
) -> relax.Function:
|
|
updated_func = AppendReshapeToBNRewriter(mod).visit_expr(func)
|
|
updated_func = relax.analysis.remove_all_unused(updated_func)
|
|
return updated_func
|
|
|
|
|
|
def clml_sdk_version():
|
|
"""Utility function to get clml version.
|
|
|
|
Probes the FFI registry for the OpenCLML version registered by the
|
|
CLML backend at build time. Returns 2 when CLML is not present.
|
|
"""
|
|
# Registry: "relax.get_openclml_version" — returns the CLML SDK version
|
|
# that TVM was built against; registered unconditionally in codegen.cc.
|
|
# Grep hint: grep -rn 'relax.get_openclml_version' src/
|
|
get_version = tvm.get_global_func("relax.get_openclml_version", allow_missing=True)
|
|
if get_version is None:
|
|
return 2
|
|
return int(get_version())
|
|
|
|
|
|
def is_clml_runtime_enabled():
|
|
"""Check if the CLML graph runtime is present.
|
|
|
|
Returns
|
|
-------
|
|
ret: bool
|
|
True if present, False if not.
|
|
"""
|
|
check_enabled = tvm.get_global_func("relax.op.is_openclml_runtime_enabled", True)
|
|
if check_enabled:
|
|
return check_enabled()
|
|
return False
|
|
|
|
|
|
def _check_default(context: PatternCheckContext) -> bool:
|
|
return True
|
|
|
|
|
|
def clml_pattern_table():
|
|
"""Get the CLML pattern table."""
|
|
|
|
def _check_conv2d(context: PatternCheckContext) -> bool:
|
|
if "root" in context.annotated_expr:
|
|
root_call = context.annotated_expr["root"]
|
|
if root_call.op.name == "relax.nn.conv2d":
|
|
input_layout = root_call.attrs.data_layout
|
|
weight_layout = root_call.attrs.kernel_layout
|
|
if input_layout != "NCHW" or weight_layout != "OIHW":
|
|
return False
|
|
if root_call.op.name == "relax.nn.conv2d_transpose":
|
|
input_layout = root_call.attrs.data_layout
|
|
weight_layout = root_call.attrs.kernel_layout
|
|
if input_layout != "NCHW" or weight_layout != "OIHW":
|
|
return False
|
|
|
|
if "data" in context.annotated_expr:
|
|
input_expr = context.annotated_expr["data"]
|
|
input_dtype = _dtype_str(input_expr.ty.dtype)
|
|
if input_dtype not in ["float32", "float16"]:
|
|
return False
|
|
|
|
if "weight" in context.annotated_expr:
|
|
weight_expr = context.annotated_expr["weight"]
|
|
weight_dtype = _dtype_str(weight_expr.ty.dtype)
|
|
if weight_dtype not in ["float32", "float16"]:
|
|
return False
|
|
|
|
return True
|
|
|
|
def populate_patterns(patterns, name, op, annotations, *args):
|
|
ret = {}
|
|
for k, v in patterns.items():
|
|
ret_ann = v["annotation"].copy()
|
|
ret_ann.update(annotations)
|
|
ret[name + "." + k] = {"pattern": op(v["pattern"], *args), "annotation": ret_ann.copy()}
|
|
|
|
return ret
|
|
|
|
def conv_pattern():
|
|
"""Create a convolution pattern."""
|
|
data = wildcard()
|
|
weight = wildcard()
|
|
bias = is_const()
|
|
bn_scale = is_const()
|
|
bn_bias = is_const()
|
|
bn_mean = is_const()
|
|
bn_var = is_const()
|
|
|
|
annotations = {
|
|
"data": data,
|
|
"weight": weight,
|
|
}
|
|
|
|
patterns = {}
|
|
patterns["nn.conv2d"] = {
|
|
"pattern": is_op("relax.nn.conv2d")(data, weight),
|
|
"annotation": annotations.copy(),
|
|
}
|
|
|
|
pad_annotations = annotations.copy()
|
|
patterns["pad.nn.conv2d"] = {
|
|
"pattern": is_op("relax.nn.conv2d")(is_op("relax.nn.pad")(data), weight),
|
|
"annotation": pad_annotations,
|
|
}
|
|
|
|
patterns["nn.conv2d_transpose"] = {
|
|
"pattern": is_op("relax.nn.conv2d_transpose")(data, weight),
|
|
"annotation": annotations.copy(),
|
|
}
|
|
patterns.update(
|
|
populate_patterns(patterns, "bias", is_op("relax.add"), {"bias": bias}, bias)
|
|
)
|
|
patterns.update(
|
|
populate_patterns(
|
|
patterns,
|
|
"bn",
|
|
is_op("relax.nn.batch_norm"),
|
|
{
|
|
"bn_scale": bn_scale,
|
|
"bn_bias": bn_bias,
|
|
"bn_mean": bn_mean,
|
|
"bn_var": bn_var,
|
|
},
|
|
bn_scale,
|
|
bn_bias,
|
|
bn_mean,
|
|
bn_var,
|
|
)
|
|
)
|
|
tuple_patterns = {}
|
|
for k, v in patterns.items():
|
|
tuple_annotation = v["annotation"].copy()
|
|
tuple_patterns["tuple" + "." + k] = {
|
|
"pattern": is_tuple_get_item(v["pattern"], 0),
|
|
"annotation": tuple_annotation,
|
|
}
|
|
patterns.update(tuple_patterns)
|
|
|
|
relu_patterns = populate_patterns(patterns, "relu", is_op("relax.nn.relu"), {})
|
|
clip_patterns = populate_patterns(patterns, "clip", is_op("relax.clip"), {})
|
|
patterns.update(relu_patterns)
|
|
patterns.update(clip_patterns)
|
|
|
|
conv_patterns = []
|
|
for k, v in patterns.items():
|
|
ret_annotations = v["annotation"]
|
|
ret_annotations["root"] = v["pattern"]
|
|
conv_patterns.append(
|
|
("openclml." + (k), v["pattern"], ret_annotations.copy(), _check_conv2d)
|
|
)
|
|
return conv_patterns[::-1]
|
|
|
|
def _check_maxpool2d(context: PatternCheckContext) -> bool:
|
|
root = context.annotated_expr.get("root")
|
|
if root is None or not isinstance(root, relax.Call):
|
|
return False
|
|
|
|
if root.op.name != "relax.nn.max_pool2d":
|
|
return False
|
|
|
|
if "data" not in context.annotated_expr:
|
|
return False
|
|
|
|
data = context.annotated_expr["data"]
|
|
input_shape = data.ty.shape
|
|
|
|
if len(input_shape) != 4:
|
|
return False
|
|
|
|
if any(dim <= 0 for dim in input_shape):
|
|
return False
|
|
|
|
pool_size = root.attrs.pool_size
|
|
if len(pool_size) != 2:
|
|
return False
|
|
if any(size <= 0 for size in pool_size):
|
|
return False
|
|
|
|
strides = root.attrs.strides
|
|
if len(strides) != 2:
|
|
return False
|
|
if any(stride <= 0 for stride in strides):
|
|
return False
|
|
|
|
dilation = root.attrs.dilation
|
|
if len(dilation) != 2:
|
|
return False
|
|
if any(d <= 0 for d in dilation):
|
|
return False
|
|
|
|
padding = root.attrs.padding
|
|
if len(padding) != 4:
|
|
return False
|
|
if any(p < 0 for p in padding):
|
|
return False
|
|
|
|
return True
|
|
|
|
def maxpool_pattern():
|
|
"""Create Pool Pattern"""
|
|
data = wildcard()
|
|
annotations = {
|
|
"data": data,
|
|
}
|
|
patterns = {}
|
|
patterns["nn.max_pool2d"] = {
|
|
"pattern": is_op("relax.nn.max_pool2d")(data),
|
|
"annotation": annotations.copy(),
|
|
}
|
|
|
|
pool_patterns = []
|
|
for k, v in patterns.items():
|
|
ret_annotations = v["annotation"]
|
|
ret_annotations["root"] = v["pattern"]
|
|
pool_patterns.append(
|
|
("openclml." + (k), v["pattern"], ret_annotations.copy(), _check_maxpool2d)
|
|
)
|
|
return pool_patterns
|
|
|
|
def _check_avgpool2d(context: PatternCheckContext) -> bool:
|
|
root = context.annotated_expr.get("root")
|
|
if root is None or not isinstance(root, relax.Call):
|
|
return False
|
|
|
|
if root.op.name != "relax.nn.avg_pool2d":
|
|
return False
|
|
|
|
if "data" not in context.annotated_expr:
|
|
return False
|
|
|
|
data = context.annotated_expr["data"]
|
|
input_shape = data.ty.shape
|
|
|
|
if len(input_shape) != 4:
|
|
return False
|
|
|
|
if any(dim <= 0 for dim in input_shape):
|
|
return False
|
|
|
|
pool_size = root.attrs.pool_size
|
|
if len(pool_size) != 2:
|
|
return False
|
|
if any(size <= 0 for size in pool_size):
|
|
return False
|
|
|
|
strides = root.attrs.strides
|
|
if len(strides) != 2:
|
|
return False
|
|
if any(stride <= 0 for stride in strides):
|
|
return False
|
|
|
|
padding = root.attrs.padding
|
|
if len(padding) != 4:
|
|
return False
|
|
if any(p < 0 for p in padding):
|
|
return False
|
|
|
|
return True
|
|
|
|
def avgpool_pattern():
|
|
data = wildcard()
|
|
annotations = {
|
|
"data": data,
|
|
}
|
|
patterns = {}
|
|
patterns["nn.avg_pool2d"] = {
|
|
"pattern": is_op("relax.nn.avg_pool2d")(data),
|
|
"annotation": annotations.copy(),
|
|
}
|
|
|
|
pool_patterns = []
|
|
for k, v in patterns.items():
|
|
ret_annotations = v["annotation"]
|
|
ret_annotations["root"] = v["pattern"]
|
|
pool_patterns.append(
|
|
("openclml." + (k), v["pattern"], ret_annotations.copy(), _check_avgpool2d)
|
|
)
|
|
return pool_patterns
|
|
|
|
def _check_global_avgpool(context: PatternCheckContext) -> bool:
|
|
root = context.annotated_expr.get("root")
|
|
if root is None or not isinstance(root, relax.Call):
|
|
return False
|
|
|
|
if root.op.name != "relax.mean":
|
|
return False
|
|
|
|
if "data" not in context.annotated_expr:
|
|
return False
|
|
|
|
data = context.annotated_expr["data"]
|
|
input_shape = data.ty.shape
|
|
|
|
if len(input_shape) != 4:
|
|
return False
|
|
|
|
if input_shape[1] <= 0 or input_shape[2] <= 0 or input_shape[3] <= 0:
|
|
return False
|
|
|
|
if not hasattr(root.attrs, "axis"):
|
|
return False
|
|
|
|
axis = root.attrs.axis
|
|
if not (len(axis) == 2 and axis[0] == 2 and axis[1] == 3):
|
|
return False
|
|
|
|
return True
|
|
|
|
def global_avgpool_pattern():
|
|
"""Create Pool Pattern"""
|
|
data = wildcard()
|
|
pattern = is_op("relax.mean")(data).has_attr({"axis": [2, 3]})
|
|
|
|
annotations = {
|
|
"data": data,
|
|
"root": pattern,
|
|
}
|
|
|
|
return [
|
|
("openclml.nn.global_avg_pool2d", pattern, annotations, _check_global_avgpool),
|
|
]
|
|
|
|
def _check_reshape(context: PatternCheckContext) -> bool:
|
|
root = context.annotated_expr.get("root")
|
|
if root is None or not isinstance(root, relax.Call):
|
|
return False
|
|
|
|
if root.op.name != "relax.reshape":
|
|
return False
|
|
|
|
shape_arg = root.args[1]
|
|
if not isinstance(shape_arg, relax.Expr):
|
|
return False
|
|
|
|
return True
|
|
|
|
def reshape_pattern():
|
|
"""Create Reshape Pattern"""
|
|
|
|
pattern = is_op("relax.reshape")(wildcard(), wildcard())
|
|
annotations = {
|
|
"root": pattern,
|
|
}
|
|
return [("openclml.reshape", pattern, annotations, _check_reshape)]
|
|
|
|
def _check_batchnorm(context: PatternCheckContext) -> bool:
|
|
root = context.annotated_expr.get("root")
|
|
if root is None or not isinstance(root, relax.Call):
|
|
return False
|
|
|
|
if root.op.name != "relax.reshape":
|
|
return False
|
|
|
|
required_params = ["moving_var", "gamma", "moving_mean", "beta"]
|
|
for param in required_params:
|
|
if param not in context.annotated_expr:
|
|
return False
|
|
|
|
params = {
|
|
"moving_var": context.annotated_expr["moving_var"],
|
|
"gamma": context.annotated_expr["gamma"],
|
|
"moving_mean": context.annotated_expr["moving_mean"],
|
|
"beta": context.annotated_expr["beta"],
|
|
}
|
|
|
|
for param in params.values():
|
|
if not isinstance(param, relax.expr.Constant):
|
|
return False
|
|
|
|
base_shape = None
|
|
for param in params.values():
|
|
shape = param.ty.shape
|
|
dtype = _dtype_str(param.ty.dtype)
|
|
|
|
if dtype not in {"float32"}:
|
|
return False
|
|
|
|
# Initialize base_shape if not set
|
|
if base_shape is None:
|
|
base_shape = shape
|
|
continue
|
|
|
|
# All parameters should have same shape
|
|
if len(shape) != len(base_shape):
|
|
return False
|
|
if any(s1 != s2 for s1, s2 in zip(shape, base_shape)):
|
|
return False
|
|
|
|
return True
|
|
|
|
def batch_norm_pattern():
|
|
"""Create a batch norm pattern."""
|
|
data = wildcard()
|
|
bn_scale = is_const()
|
|
bn_bias = is_const()
|
|
bn_mean = is_const()
|
|
bn_var = is_const()
|
|
|
|
pattern = is_op("relax.nn.batch_norm")(data, bn_scale, bn_bias, bn_mean, bn_var)
|
|
pattern = is_tuple_get_item(pattern, 0)
|
|
pattern = is_op("relax.reshape")(pattern, wildcard())
|
|
|
|
annotations = {
|
|
"gamma": bn_scale,
|
|
"beta": bn_bias,
|
|
"moving_mean": bn_mean,
|
|
"moving_var": bn_var,
|
|
"root": pattern,
|
|
}
|
|
|
|
return [
|
|
("openclml.nn.batch_norm", pattern, annotations, _check_batchnorm),
|
|
]
|
|
|
|
def _check_binary_op(context: PatternCheckContext) -> bool:
|
|
def _check_arg(input_expr):
|
|
input_dtype = _dtype_str(input_expr.ty.dtype)
|
|
input_shape = input_expr.ty.shape
|
|
if len(input_shape) == 0:
|
|
return False
|
|
|
|
# Avoid any operators with dtype Int64
|
|
if input_dtype == "int64":
|
|
return False
|
|
|
|
# No support for batch> 1
|
|
if input_shape[0] > 1:
|
|
return False
|
|
|
|
return True
|
|
|
|
def compare_shapes(lhs_shape, rhs_shape):
|
|
if len(lhs_shape) != len(rhs_shape):
|
|
return False
|
|
for lhs_dim, rhs_dim in zip(lhs_shape, rhs_shape):
|
|
if lhs_dim != rhs_dim:
|
|
return False
|
|
return True
|
|
|
|
lhs_shape = None
|
|
rhs_shape = None
|
|
if "lhs" in context.annotated_expr:
|
|
lhs = context.annotated_expr["lhs"]
|
|
lhs_shape = lhs.ty.shape
|
|
if not _check_arg(lhs):
|
|
return False
|
|
|
|
if "rhs" in context.annotated_expr:
|
|
rhs = context.annotated_expr["rhs"]
|
|
rhs_shape = rhs.ty.shape
|
|
if not _check_arg(rhs):
|
|
return False
|
|
|
|
# Checking for BinaryOps ( False for unaryOp )
|
|
if (
|
|
"lhs" in context.annotated_expr
|
|
and "rhs" in context.annotated_expr
|
|
and not compare_shapes(lhs_shape, rhs_shape)
|
|
):
|
|
return False
|
|
|
|
return True
|
|
|
|
def binary_op_pattern():
|
|
"""Create a binary op pattern."""
|
|
|
|
def make_pattern(op):
|
|
lhs = wildcard()
|
|
rhs = wildcard()
|
|
pattern = is_op(op)(lhs, rhs)
|
|
annotations = {"lhs": lhs, "rhs": rhs}
|
|
return ("openclml." + op, pattern, annotations, _check_binary_op)
|
|
|
|
binary_ops = [
|
|
"relax.add",
|
|
"relax.subtract",
|
|
"relax.multiply",
|
|
"relax.divide",
|
|
"relax.maximum",
|
|
"relax.minimum",
|
|
]
|
|
|
|
return [make_pattern(op) for op in binary_ops]
|
|
|
|
def unary_op_pattern():
|
|
"""Create a unary op pattern."""
|
|
|
|
def make_pattern(op):
|
|
lhs = wildcard()
|
|
pattern = is_op(op)(lhs)
|
|
annotations = {"lhs": lhs}
|
|
return ("openclml." + op, pattern, annotations, _check_binary_op)
|
|
|
|
unary_ops = [
|
|
"relax.nn.softmax",
|
|
"relax.nn.relu",
|
|
"relax.clip",
|
|
]
|
|
|
|
return [make_pattern(op) for op in unary_ops]
|
|
|
|
return [
|
|
*conv_pattern(),
|
|
*batch_norm_pattern(),
|
|
*binary_op_pattern(),
|
|
*unary_op_pattern(),
|
|
*maxpool_pattern(),
|
|
*avgpool_pattern(),
|
|
*global_avgpool_pattern(),
|
|
*reshape_pattern(),
|
|
]
|
|
|
|
|
|
clml_patterns = clml_pattern_table()
|
|
register_patterns(clml_patterns)
|
|
|
|
|
|
@module_pass(opt_level=0, name="OpenCLMLOffLoad")
|
|
class OpenCLMLOffLoad:
|
|
"""The pass sequence used for CLML offload"""
|
|
|
|
def transform_module(self, mod: IRModule, ctx: PassContext) -> IRModule:
|
|
"""The transform"""
|
|
|
|
clml_layouts = {
|
|
"relax.nn.conv2d": ["NCHW", "OIHW"],
|
|
"relax.nn.conv2d_transpose": ["NCHW", "OIHW"],
|
|
}
|
|
seq = tvm.transform.Sequential(
|
|
[
|
|
transform.ConvertLayout(clml_layouts),
|
|
transform.Normalize(),
|
|
transform.FoldBatchnormToConv2D(),
|
|
AppendReshapeToBNRewriterPass(),
|
|
transform.FoldConstant(),
|
|
transform.FuseOpsByPattern(clml_pattern_table()),
|
|
transform.MergeCompositeFunctions(),
|
|
transform.RunCodegen(),
|
|
],
|
|
)
|
|
mod = seq(mod)
|
|
return mod
|
|
|
|
|
|
def _check_dequantize_matmul(ctx: relax.transform.PatternCheckContext) -> bool:
|
|
_input = ctx.annotated_expr["lhs"]
|
|
root = ctx.annotated_expr["root"]
|
|
wdq = ctx.annotated_expr["w_decoded"]
|
|
w_pack = ctx.annotated_expr["w_encoded"]
|
|
|
|
if _dtype_str(ctx.annotated_expr["lhs"].ty.dtype) != "float16":
|
|
return False
|
|
if not isinstance(wdq, relax.Call):
|
|
return False
|
|
g_var = wdq.args[0]
|
|
if not (isinstance(g_var, relax.GlobalVar) and "dequantize" in g_var.name_hint):
|
|
return False
|
|
|
|
if not (
|
|
(len(root.ty.shape) == 3)
|
|
and isinstance(root.ty.shape[0], tirx.IntImm)
|
|
and (_dtype_str(root.ty.dtype) == "float16")
|
|
and (root.ty.shape[0] == 1)
|
|
):
|
|
return False
|
|
|
|
if not (
|
|
(len(wdq.ty.shape) == 2)
|
|
and (w_pack.ty.shape[-1] == root.ty.shape[-1])
|
|
and (wdq.ty.shape[-2] == _input.ty.shape[-1])
|
|
):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def dequantize_matmul_patterns():
|
|
"""Returns a list of supported decode -> matmul patterns."""
|
|
|
|
def _dequantize_matmul_pattern(name):
|
|
scales = wildcard()
|
|
x = wildcard()
|
|
w_packed = wildcard()
|
|
|
|
w_decoded = is_op("relax.call_tir")(
|
|
GlobalVarPattern(),
|
|
TuplePattern([w_packed, scales]),
|
|
)
|
|
matmul = is_op("relax.matmul")(x, w_decoded)
|
|
|
|
annotations = {
|
|
"root": matmul,
|
|
"lhs": x,
|
|
"w_encoded": w_packed,
|
|
"w_decoded": w_decoded,
|
|
"scales": scales,
|
|
}
|
|
|
|
return name, matmul, annotations, _check_dequantize_matmul
|
|
|
|
return [
|
|
_dequantize_matmul_pattern("openclml.dequant_matmul"),
|
|
]
|
|
|
|
|
|
clml_llm_patterns = [
|
|
*dequantize_matmul_patterns(),
|
|
]
|
|
register_patterns(clml_llm_patterns)
|
|
|
|
|
|
@tvm.transform.module_pass(opt_level=0, name="OpenCLMLOffLoadForLLM")
|
|
class OpenCLMLOffLoadForLLM:
|
|
"""A compiler pass that partition the graph with dequant Matmul to CLML backend offload."""
|
|
|
|
def __init__(self, target: tvm.target.Target) -> None:
|
|
"""Initializer.
|
|
Parameters
|
|
----------
|
|
target : tvm.target.Target
|
|
Target device.
|
|
"""
|
|
self.target = target
|
|
|
|
def transform_module(
|
|
self,
|
|
mod: IRModule,
|
|
_ctx: tvm.transform.PassContext,
|
|
) -> IRModule:
|
|
"""Apply required passed to transform"""
|
|
|
|
if "adreno" in self.target.keys and (clml_sdk_version() >= 5):
|
|
mod = tvm.transform.Sequential(
|
|
[
|
|
transform.Normalize(),
|
|
transform.FuseOpsByPattern(clml_llm_patterns, annotate_codegen=True),
|
|
transform.RunCodegen(),
|
|
]
|
|
)(mod)
|
|
|
|
return mod
|