203 lines
6.9 KiB
Python
203 lines
6.9 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.
|
|
|
|
"""Pattern table for cuDNN backend"""
|
|
|
|
import operator
|
|
from functools import partial, reduce
|
|
|
|
import tvm
|
|
from tvm import relax
|
|
from tvm.relax import PyExprMutator, expr_functor, transform
|
|
from tvm.relax.transform import PatternCheckContext
|
|
|
|
from ..pattern_registry import get_patterns_with_prefix, register_patterns
|
|
from ..patterns import make_conv2d_pattern, make_stacked_attention_pattern
|
|
from ..utils import has_leaking_intermediate_variables
|
|
|
|
|
|
def _is_supported_dtype(lhs_dtype, rhs_dtype):
|
|
"""Check if dtypes in the given workload are supported by cuDNN BYOC."""
|
|
return (lhs_dtype == "float16" and rhs_dtype == "float16") or (
|
|
lhs_dtype == "float32" and rhs_dtype == "float32"
|
|
)
|
|
|
|
|
|
def _is_supported_format(data_layout, kernel_layout):
|
|
"""Check if layouts in the given workload are supported by cuDNN BYOC."""
|
|
return (data_layout == "NHWC" and kernel_layout == "OHWI") or (
|
|
data_layout == "NCHW" and kernel_layout == "OIHW"
|
|
)
|
|
|
|
|
|
def _check_conv2d(context: PatternCheckContext) -> bool:
|
|
if has_leaking_intermediate_variables(context):
|
|
return False
|
|
# Retrieve the annotated expression from context
|
|
conv2d_call = context.annotated_expr["root"]
|
|
input_expr = context.annotated_expr["input"]
|
|
weight_expr = context.annotated_expr["weight"]
|
|
|
|
# Check if the data types of input and weights are supported by cuDNN BYOC
|
|
input_dtype = input_expr.ty.dtype
|
|
weight_dtype = weight_expr.ty.dtype
|
|
if not _is_supported_dtype(input_dtype, weight_dtype):
|
|
return False
|
|
|
|
input_layout = conv2d_call.attrs.data_layout
|
|
weight_layout = conv2d_call.attrs.kernel_layout
|
|
if not _is_supported_format(input_layout, weight_layout):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def _check_stacked_attention(context: PatternCheckContext, layout: str) -> bool:
|
|
"""Check if the given stacked attention workload can be offloaded to cuDNN."""
|
|
if has_leaking_intermediate_variables(context):
|
|
return False
|
|
if layout == "BS3NH":
|
|
if not context.annotated_expr["stacked_qkv"].ty.ndim == 3:
|
|
return False
|
|
if "split" in context.annotated_expr:
|
|
split_op = context.annotated_expr["split"]
|
|
if not split_op.attrs.axis == 2:
|
|
return False
|
|
elif layout == "SBN3H":
|
|
if not context.annotated_expr["stacked_qkv"].ty.ndim == 4:
|
|
return False
|
|
if "split" in context.annotated_expr:
|
|
split_op = context.annotated_expr["split"]
|
|
if not split_op.attrs.axis == 3:
|
|
return False
|
|
else:
|
|
raise NotImplementedError(f"Unsupported layout: {layout}")
|
|
return True
|
|
|
|
|
|
register_patterns(
|
|
[
|
|
(
|
|
"cudnn.conv2d.nhwc_ohwi",
|
|
*make_conv2d_pattern(
|
|
with_bias=False,
|
|
),
|
|
_check_conv2d,
|
|
),
|
|
(
|
|
"cudnn.conv2d.nhwc_ohwi_bias",
|
|
*make_conv2d_pattern(
|
|
with_bias=True,
|
|
),
|
|
_check_conv2d,
|
|
),
|
|
(
|
|
"cudnn.conv2d.nhwc_ohwi_bias_relu",
|
|
*make_conv2d_pattern(
|
|
with_bias=True,
|
|
activation="relax.nn.relu",
|
|
),
|
|
_check_conv2d,
|
|
),
|
|
(
|
|
"cudnn.attention.BS3NH",
|
|
*make_stacked_attention_pattern(start_op="split", layout="BS3NH"),
|
|
partial(_check_stacked_attention, layout="BS3NH"),
|
|
),
|
|
(
|
|
"cudnn.attention.SBN3H",
|
|
*make_stacked_attention_pattern(start_op="split", layout="SBN3H"),
|
|
partial(_check_stacked_attention, layout="SBN3H"),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
def partition_for_cudnn(mod):
|
|
"""
|
|
Partition the input module into cuDNN-supported subgraphs.
|
|
|
|
Parameters
|
|
----------
|
|
mod: tvm.IRModule
|
|
The IRModule to be partitioned.
|
|
|
|
Returns
|
|
-------
|
|
mod: tvm.IRModule
|
|
The resulting IRModule, containing partitioned subgraphs to be
|
|
offloaded to the cuDNN backend.
|
|
"""
|
|
|
|
patterns = get_patterns_with_prefix("cudnn")
|
|
return tvm.transform.Sequential(
|
|
[
|
|
transform.FuseOpsByPattern(patterns, bind_constants=False, annotate_codegen=True),
|
|
annotate_workspace,
|
|
transform.AllocateWorkspace(),
|
|
]
|
|
)(mod)
|
|
|
|
|
|
def _shape_1d(shape):
|
|
return reduce(operator.mul, shape, 1)
|
|
|
|
|
|
@expr_functor.mutator
|
|
class WorkspaceAnnotator(PyExprMutator):
|
|
"""Annotate a workspace requirement for each cuDNN-offloaded function."""
|
|
|
|
def __init__(self, mod):
|
|
super().__init__(mod)
|
|
|
|
def visit_function_(self, f):
|
|
if "Composite" not in f.attrs:
|
|
body = super().visit_expr(f.body)
|
|
new_f = relax.Function(f.params, body, f.ret_ty, f.is_pure, f.attrs, f.span)
|
|
|
|
if "global_symbol" in f.attrs and "cudnn" in f.attrs["global_symbol"]:
|
|
composite_func = body.blocks[0].bindings[0].value
|
|
if "WorkspaceSize" in composite_func.attrs:
|
|
return new_f.with_attr("WorkspaceSize", composite_func.attrs["WorkspaceSize"])
|
|
|
|
return new_f
|
|
|
|
if "attention" in f.attrs["Composite"] and "cudnn" in f.attrs["Composite"]:
|
|
# Workspace is needed only for larger head sizes, but for simplicity we always allocate.
|
|
out_dtype = f.ret_ty.dtype
|
|
out_size_1d = _shape_1d(f.ret_ty.shape)
|
|
# This needs to be in sync with the actual value that the kernel expects.
|
|
workspace_size_bytes = out_size_1d * {"float16": 2, "float32": 4}[out_dtype]
|
|
if not isinstance(workspace_size_bytes, int | tvm.tirx.expr.IntImm):
|
|
# Tempororay workaround for dynamic shape workload. Will be removed when
|
|
# workspace for dynamic shape workload is implemented.
|
|
workspace_size_bytes = 8
|
|
return f.with_attr("WorkspaceSize", workspace_size_bytes)
|
|
|
|
return f
|
|
|
|
|
|
@tvm.transform.module_pass(opt_level=0)
|
|
def annotate_workspace(mod, _):
|
|
"""Pass to annotate a workspace requirement for each cuDNN-offloaded function."""
|
|
annotator = WorkspaceAnnotator(mod)
|
|
for name, f in mod.functions_items():
|
|
if isinstance(f, relax.Function):
|
|
new_f = annotator.visit_expr(f)
|
|
mod.update_func(name, new_f)
|
|
return mod
|