Skip to content

Commit

Permalink
[WIP] DP fusion pass works for auto scheduler, but not for other back…
Browse files Browse the repository at this point in the history
…ends incluidng CuDNN and TensorRT
  • Loading branch information
MadFunMaker committed Apr 8, 2021
1 parent 419173b commit 7eb61f7
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 273 deletions.
30 changes: 4 additions & 26 deletions python/tvm/relay/transform/backend_operator/backend_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
from .op_type import OpType, optype_to_pattern, relayop_to_varnames

# It gives the path of backend_op.py no matter where you import this file
cur_dir_path = Path(__file__).parent.absolute()
RES_LOG = f"{cur_dir_path}/logs/runtime_results.log"
# cur_dir_path = Path(__file__).parent.absolute()
# RES_LOG = f"{cur_dir_path}/logs/runtime_results.log"

# redirect stdout to this log so it is not intertwined with by TVM backend log output
# sys.stdout = open(RES_LOG, 'w')
Expand Down Expand Up @@ -83,29 +83,6 @@ def get_cost(self, expr):
mean_cost, std_cost = cost_info
return mean_cost

# library class representing all backend operators
class BackendOpLib(object):
def __init__(self, measured_configs_lib):
# list of all backend operators
self._measured_configs = measured_configs_lib
self.all_backendops = []
# dictionary that maps each pattern to list of backend ops represented by the pattern
self.pattern_to_backendops = defaultdict(list)

# add a backend operator to the library
def add_backendop(self, name, target, op_type, max_depth, constraint_func = no_constraints_func):
backendop = BackendOp(name, target, op_type, max_depth, self._measured_configs, constraint_func)
self.all_backendops.append(backendop)
self.pattern_to_backendops[backendop.get_pattern()].append(backendop)

# return list of backend operators matching a pattern
def get_backendops(self, pattern):
return self.pattern_to_backendops[pattern]

# return list of all patterns for backend operators
def get_all_patterns(self):
return list(self.pattern_to_backendops.keys())

# extract the subgraph of the expr that matches the pattern (only the top layers of the recursive relay expr).
# Since there might be multiple branches, we traverse each branch by "max_depth" steps, and rewrite the child nodes
# of the last node to free variables. However, when there are multiple branches, only the rewrite at the end of the
Expand Down Expand Up @@ -170,7 +147,8 @@ def helper(expr, depth):
# given a pattern and a relay expr matching that pattern, return the cheapest backend operator
# satisfying the constraints and its cost. Return None if no backend operators satisfy constraints.
def get_optimal_backendop(b_op_lib, expr, pattern, target = None):

assert type(target) == list

backendops = b_op_lib.get_backendops(pattern)

cheapest_op, min_cost = None, float('inf')
Expand Down
164 changes: 164 additions & 0 deletions python/tvm/relay/transform/backend_operator/backend_op_lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
from tvm import relay
from collections import defaultdict

from .backend_op import BackendOp, get_optimal_backendop
from .op_config import Config, MeasuredConfigs

from ..workloads.onnx_workloads import get_network_from_onnx
from .utils import no_constraints_func
from .target import Target
from .op_type import OpType

def add_all_backend_ops_to_lib(b_op_lib, target):
t_name = target.name()

for op_type in OpType:
# Skip diamond pattern for now
if op_type == OpType.DIAMOND:
continue

op_name, op_depth = op_type.name(), op_type.depth()
b_op_lib._add_backendop(f"{t_name}_{op_name}", target, op_type, op_depth)


def add_all_backend_ops_to_lib_except_fused(b_op_lib, target):
t_name = target.name()
op_to_skip = [OpType.DIAMOND, OpType.ADD] # OpType.CONV2D_BN, OpType.CONV2D_BN_RELU,
# OpType.BN_RELU, OpType.CONV2D_BIAS_ADD_RELU

for op_type in OpType:
# Skip diamond pattern for now
if op_type in op_to_skip:
continue

op_name, op_depth = op_type.name(), op_type.depth()
b_op_lib._add_backendop(f"{t_name}_{op_name}", target, op_type, op_depth)

class BackendOpCostEvaluator:
__instance = None

@staticmethod
def get():
""" Static access method. """
if BackendOpCostEvaluator.__instance == None:
BackendOpCostEvaluator()
return BackendOpCostEvaluator.__instance

def __init__(self):
""" Virtually private constructor. """
if BackendOpCostEvaluator.__instance != None:
raise Exception("This class is a singleton!")

BackendOpCostEvaluator.__instance = self

def log_backend_op_perf(self, b_op_lib, expr, target):
assert type(target) != list

for pattern in b_op_lib.get_all_patterns():
if pattern.get_pattern().match(expr):
print("PATTERN:\n", pattern.get_pattern())
res = get_optimal_backendop(b_op_lib, expr, pattern, [target])
if res == None:
print("No satisfying backend operators")
else:
op, cost = res
print("best backendop: %s, cost: %.5f ms" % (op, cost))


# traverse all subgraphs of a computation graph and evaluate all matchings between backend operators and subgraphs
def log_network_backend_ops_perf_on_target(self, b_op_lib, target, network_expr, batch_size=1):
# Read from ONNX is deprecated because type information is not available.
# mod, _, _, _ = get_network_from_onnx(network_name, batch_size=batch_size)
# relay.analysis.post_order_visit(mod['main'], lambda expr: self.log_backend_op_perf(b_op_lib, expr, target))
relay.analysis.post_order_visit(network_expr, lambda expr: self.log_backend_op_perf(b_op_lib, expr, target))


# library class (singleton) representing all backend operators
class BackendOpLib(object):
__instance = None

@staticmethod
def get():
""" Static access method. """
if BackendOpLib.__instance == None:
BackendOpLib()
return BackendOpLib.__instance

def __init__(self):
""" Virtually private constructor. """
if BackendOpLib.__instance != None:
raise Exception("This class is a singleton!")

# list of all backend operators
self._measured_configs = MeasuredConfigs()
self._measured_configs.load_from_log()

self.all_backendops = []
# dictionary that maps each pattern to list of backend ops represented by the pattern
self.pattern_to_backendops = defaultdict(list)

self._add_all_backendops()

BackendOpLib.__instance = self

# Note that we only support ResNet50 for now
def _add_all_backendops(self):
# CUDNN
self._add_backendop("cudnn_conv2d", Target.CUDNN, OpType.CONV2D, 1)
self._add_backendop("cudnn_relu", Target.CUDNN, OpType.RELU, 1)
self._add_backendop("cudnn_add", Target.CUDNN, OpType.ADD, 1)

# self._add_backendop("cudnn_softmax", Target.CUDNN, OpType.SOFTMAX, 1)
# self._add_backendop("cudnn_biasadd", Target.CUDNN, OpType.BIAS_ADD, 1)
# self._add_backendop("cudnn_bn", Target.CUDNN, OpType.BN, 1)

# measure_cost doesn't work, we need to fix this later.
# self._add_backendop("cudnn_maxpool2d", Target.CUDNN, OpType.MAX_POOL2D, 1)

# conv_bias_add_relu --> ResNet doesn't have this pattern, so it wouldn't be measured
# self._add_backendop("cudnn_conv2d+biasadd+relu", Target.CUDNN, OpType.CONV2D_BIAS_ADD_RELU, 3)

# TENSORRT
add_all_backend_ops_to_lib(self, Target.TENSORRT)

# CUBLAS
# TODO: Add patterns. matmul, batch matmul
# self._add_backendop("cublas_dense", Target.CUBLAS, OpType.DENSE, 1)

# TVM_GPU
add_all_backend_ops_to_lib(self, Target.TVM_GPU_AUTOSCH)
# add_all_backend_ops_to_lib_except_fused(backendop_lib, Target.TVM_GPU)

# TVM_GPU_NO_TUNING
add_all_backend_ops_to_lib(self, Target.TVM_GPU_NO_TUNING)
# add_all_backend_ops_to_lib_except_fused(backendop_lib, Target.TVM_GPU_NO_TUNING)

# TVM_CPU; Exclude it for GPU testing
# Fix: Extend this to automatically select backend library based on HW info
# add_all_backend_ops_to_lib(backendop_lib, Target.TVM_CPU)

# add a backend operator to the library
def _add_backendop(self, name, target, op_type, max_depth, constraint_func = no_constraints_func):
backendop = BackendOp(name, target, op_type, max_depth, self._measured_configs, constraint_func)
self.all_backendops.append(backendop)
self.pattern_to_backendops[backendop.get_pattern()].append(backendop)

# return list of backend operators matching a pattern

def measure_backend_ops(self, network_expr, targets, batch_size):
assert type(targets) == list

for target in targets:
BackendOpCostEvaluator.get().log_network_backend_ops_perf_on_target(self, target, network_expr, batch_size)

# return list of backend operators matching a pattern
def get_backendops(self, pattern):
return self.pattern_to_backendops[pattern]

# return list of all patterns for backend operators
def get_all_patterns(self):
return list(self.pattern_to_backendops.keys())

# save newly measured op perfs to the log
def save_to_log(self):
return self._measured_configs.save_to_log()
8 changes: 6 additions & 2 deletions python/tvm/relay/transform/backend_operator/op_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pathlib import Path
from .utils import extract_attrs, get_data_shape
import pickle
from os import path

cur_dir_path = Path(__file__).parent.absolute()
COST_LOG = f"{cur_dir_path}/logs/operator_cost.log"
Expand Down Expand Up @@ -52,10 +53,13 @@ def save_to_log(self):
with open(COST_LOG, 'wb+') as log:
pickle.dump(self.measured_configs, log)

# If log doesn't exist, it uses default empty dictionary.
def load_from_log(self):
try:
with open(COST_LOG, 'rb') as log:
self.measured_configs = pickle.load(log)
if path.exists(COST_LOG):
with open(COST_LOG, 'rb') as log:
print("Cost configurations loaded")
self.measured_configs = pickle.load(log)
except:
# pass
raise Exception(f'{COST_LOG} is not valid')
71 changes: 39 additions & 32 deletions python/tvm/relay/transform/backend_operator/op_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,22 @@ class OpType(Enum):
# ID, name, depth
ADD = (0, 'add', 1)
CONV2D = (1, 'conv2d', 1)
BN = (2, 'bn', 1)
RELU = (3, 'relu', 1,)
SOFTMAX = (4, 'softmax', 1)
BIAS_ADD = (5, 'biasadd', 1)
DENSE = (6, 'dense', 1)
BATCH_FLATTEN = (7, 'batchflatten', 1)
GLOBAL_AVG_POOL2D = (8, 'globalavgpool2d', 1)
MAX_POOL2D = (9, 'maxpool2d', 1)
CONV2D_BN = (10, 'conv2d+bn', 2)
BN_RELU = (11, 'bn+relu', 2)
CONV2D_BN_RELU = (12, 'conv2d+bn+relu', 3)
DIAMOND = (13, 'diamond', 6) # Not sure yet if it works well for DP
CONV2D_BIAS_ADD_RELU = (14, 'conv2d+biasadd+relu', 3)
RELU = (2, 'relu', 1,)
CONV2D_RELU = (15, 'conv2d+relu', 2)

# Other ops than ResNet50
DIAMOND = (13, 'diamond', 6) # Not sure yet if it works well for DP
# BN = (3, 'bn', 1)
# SOFTMAX = (4, 'softmax', 1)
# BIAS_ADD = (5, 'biasadd', 1)
# DENSE = (6, 'dense', 1)
# BATCH_FLATTEN = (7, 'batchflatten', 1)
# GLOBAL_AVG_POOL2D = (8, 'globalavgpool2d', 1)
# MAX_POOL2D = (9, 'maxpool2d', 1)
# CONV2D_BN = (10, 'conv2d+bn', 2)
# BN_RELU = (11, 'bn+relu', 2)
# CONV2D_BN_RELU = (12, 'conv2d+bn+relu', 3)
# CONV2D_BIAS_ADD_RELU = (14, 'conv2d+biasadd+relu', 3)

def identifier(self):
return self.value[0]
Expand All @@ -38,32 +40,37 @@ def depth(self):
optype_to_pattern = {
OpType.ADD : Pattern(is_op('add')(wildcard(), wildcard())),
OpType.CONV2D : Pattern(is_op("nn.conv2d")(wildcard(), wildcard())),
OpType.BN : Pattern(is_tuple_get_item(is_op("nn.batch_norm")(wildcard(), wildcard(), wildcard(), wildcard(), wildcard()), 0)),
OpType.RELU : Pattern(is_op("nn.relu")(wildcard())),
OpType.SOFTMAX : Pattern(is_op("nn.softmax")(wildcard())),
OpType.BIAS_ADD : Pattern(is_op("nn.bias_add")(wildcard(), wildcard())),
OpType.DENSE : Pattern(is_op("nn.dense")(wildcard(), wildcard())),
OpType.BATCH_FLATTEN : Pattern(is_op("nn.batch_flatten")(wildcard())),
OpType.GLOBAL_AVG_POOL2D : Pattern(is_op("nn.global_avg_pool2d")(wildcard())),
OpType.MAX_POOL2D : Pattern(is_op("nn.max_pool2d")(wildcard())),
OpType.CONV2D_BN : Pattern(is_tuple_get_item(is_op("nn.batch_norm")(is_op("nn.conv2d")(wildcard(), wildcard()), wildcard(), wildcard(), wildcard(), wildcard()), 0)),
OpType.BN_RELU : Pattern(is_op("nn.relu")(is_tuple_get_item(is_op("nn.batch_norm")(wildcard(), wildcard(), wildcard(), wildcard(), wildcard()), 0))),
OpType.CONV2D_BN_RELU : Pattern(is_op("nn.relu")(is_tuple_get_item(is_op("nn.batch_norm")(is_op("nn.conv2d")(wildcard(), wildcard()), wildcard(), wildcard(), wildcard(), wildcard()), 0))),
OpType.CONV2D_RELU : Pattern(is_op("nn.relu")(is_op("nn.conv2d")(wildcard(), wildcard()))),

# Other ops than ResNet50
OpType.DIAMOND : get_diamond(),
OpType.CONV2D_BIAS_ADD_RELU : Pattern(is_op("nn.relu")(is_op("nn.bias_add")(is_op("nn.conv2d")(wildcard(), wildcard()), wildcard()))),
# OpType.BN : Pattern(is_tuple_get_item(is_op("nn.batch_norm")(wildcard(), wildcard(), wildcard(), wildcard(), wildcard()), 0)),
# OpType.SOFTMAX : Pattern(is_op("nn.softmax")(wildcard())),
# OpType.BIAS_ADD : Pattern(is_op("nn.bias_add")(wildcard(), wildcard())),
# OpType.DENSE : Pattern(is_op("nn.dense")(wildcard(), wildcard())),
# OpType.BATCH_FLATTEN : Pattern(is_op("nn.batch_flatten")(wildcard())),
# OpType.GLOBAL_AVG_POOL2D : Pattern(is_op("nn.global_avg_pool2d")(wildcard())),
# OpType.MAX_POOL2D : Pattern(is_op("nn.max_pool2d")(wildcard())),
# OpType.CONV2D_BN : Pattern(is_tuple_get_item(is_op("nn.batch_norm")(is_op("nn.conv2d")(wildcard(), wildcard()), wildcard(), wildcard(), wildcard(), wildcard()), 0)),
# OpType.BN_RELU : Pattern(is_op("nn.relu")(is_tuple_get_item(is_op("nn.batch_norm")(wildcard(), wildcard(), wildcard(), wildcard(), wildcard()), 0))),
# OpType.CONV2D_BN_RELU : Pattern(is_op("nn.relu")(is_tuple_get_item(is_op("nn.batch_norm")(is_op("nn.conv2d")(wildcard(), wildcard()), wildcard(), wildcard(), wildcard(), wildcard()), 0))),
# OpType.CONV2D_BIAS_ADD_RELU : Pattern(is_op("nn.relu")(is_op("nn.bias_add")(is_op("nn.conv2d")(wildcard(), wildcard()), wildcard()))),
}

# maps relay operator type to names of input vars.
relayop_to_varnames = {
"add" : ["data", "data"],
"nn.conv2d" : ["data", "weight"],
"nn.batch_norm" : ["data", "bn_data_gamma", "bn_data_beta", "bn_data_moving_mean", "bn_data_moving_var"],
"nn.relu" : ["data"],
"nn.softmax" : ["data"],
"nn.bias_add" : ["data", "bias"],
"nn.dense" : ["data", "weight"],
"nn.batch_flatten" : ["data"],
"nn.global_avg_pool2d" : ["data"],
"nn.max_pool2d" : ["data"],
"nn.relu": ["data"],

# Other ops than ResNet50
# "nn.batch_norm" : ["data", "bn_data_gamma", "bn_data_beta", "bn_data_moving_mean", "bn_data_moving_var"],
# "nn.softmax" : ["data"],
# "nn.bias_add" : ["data", "bias"],
# "nn.dense" : ["data", "weight"],
# "nn.batch_flatten" : ["data"],
# "nn.global_avg_pool2d" : ["data"],
# "nn.max_pool2d" : ["data"],
}

Loading

0 comments on commit 7eb61f7

Please sign in to comment.