Skip to content

Commit 227bf7f

Browse files
[TOPI] [Relay] Sparse Conv2d Implementation for 3x3 kernels (#8605)
* [topi] add spconv2d_3x3 nhwc * [relay] sparse_conv2d: add kernel_size attr * [relay] add strategy for spconv2d_3x3 nhwc * [relay] pass to convert spconv2d with const args * [relay] convert sparse conv2d pass fixes * use array for sparse conv2d attr * fixup 1x1 tests; new 3x3 tests
1 parent f4f525d commit 227bf7f

File tree

12 files changed

+548
-50
lines changed

12 files changed

+548
-50
lines changed

include/tvm/relay/attrs/nn.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,12 +1066,16 @@ struct SparseTransposeAttrs : public tvm::AttrsNode<SparseTransposeAttrs> {
10661066
/*! \brief Attributes for sparse_dense operator */
10671067
struct SparseConv2DAttrs : public tvm::AttrsNode<SparseConv2DAttrs> {
10681068
std::string layout;
1069+
Array<IndexExpr> kernel_size;
10691070

10701071
TVM_DECLARE_ATTRS(SparseConv2DAttrs, "relay.attrs.SparseConv2DAttrs") {
10711072
TVM_ATTR_FIELD(layout).set_default("NHWC").describe(
10721073
"Dimension ordering of input data. Can be 'NCHW', 'NHWC'"
10731074
"'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
10741075
"dimensions respectively.");
1076+
TVM_ATTR_FIELD(kernel_size)
1077+
.set_default(Array<IndexExpr>{1, 1})
1078+
.describe("Kernel size for SparseConv2D, 1x1 or 3x3. ");
10751079
}
10761080
};
10771081

python/tvm/autotvm/measure/measure_methods.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,14 @@ def ref_input(self):
254254

255255
@ref_input.setter
256256
def ref_input(self, val):
257-
warnings.warn(
258-
"You are specifying fixed input for tuning the operator. "
259-
"Be sure your input always fits the operator. Some "
260-
"operators may conduct layout transformation during tuning, "
261-
"thus can lead to unexpected behaviors. ",
262-
RuntimeWarning,
263-
)
257+
if val is not None:
258+
warnings.warn(
259+
"You are specifying fixed input for tuning the operator. "
260+
"Be sure your input always fits the operator. Some "
261+
"operators may conduct layout transformation during tuning, "
262+
"thus can lead to unexpected behaviors. ",
263+
RuntimeWarning,
264+
)
264265
self._ref_input = val
265266

266267
def set_task(self, task):

python/tvm/relay/analysis/sparse_conv2d.py

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ def _search_conv2d_op_weight(expr):
5454
return _ffi_api.search_conv2d_op_weight(expr)
5555

5656

57-
def process_params(expr, params, block_size, sparsity_threshold, layout):
57+
def process_params(
58+
expr, params, block_size, sparsity_threshold, layout, kernel_size, reg_task_input=True
59+
):
5860
"""Process parameters of conv2d from dense to sparse.
5961
6062
Parameters
@@ -86,14 +88,18 @@ def process_params(expr, params, block_size, sparsity_threshold, layout):
8688
for name in weight_names:
8789
name = str(name)
8890
w_np = params[name].numpy()
89-
# currently only support conv2d_1*1
90-
if not (
91-
(w_np.shape[0] == 1 and w_np.shape[1] == 1)
92-
or (w_np.shape[2] == 1 and w_np.shape[3] == 1)
93-
):
91+
92+
if layout == "NHWC": # HWIO
93+
weight_kernel = (w_np.shape[0], w_np.shape[1])
94+
elif layout == "NCHW": # OIHW
95+
weight_kernel = (w_np.shape[2], w_np.shape[3])
96+
if weight_kernel[0] != weight_kernel[1]:
9497
continue
95-
sparsity = 1.0 - (np.count_nonzero(w_np) / w_np.size)
96-
if sparsity >= sparsity_threshold:
98+
99+
if weight_kernel[0] == kernel_size == 1:
100+
sparsity = 1.0 - (np.count_nonzero(w_np) / w_np.size)
101+
if sparsity < sparsity_threshold:
102+
continue
97103
if layout == "NHWC":
98104
w_np = w_np.squeeze().T
99105
elif layout == "NCHW":
@@ -108,19 +114,31 @@ def process_params(expr, params, block_size, sparsity_threshold, layout):
108114
)
109115
else:
110116
sparse_weight_data = sparse_weight.data
117+
elif weight_kernel[0] == kernel_size == 3:
118+
if layout == "NHWC": # HWIO
119+
w_np = w_np.reshape((-1, w_np.shape[-1])).T
120+
elif layout == "NCHW": # OIHW
121+
w_np = w_np.reshape((w_np.shape[0], -1))
122+
sparse_weight = sp.bsr_matrix(w_np, blocksize=block_size)
123+
if 1 - (sparse_weight.nnz / w_np.size) < sparsity_threshold:
124+
continue
125+
sparse_weight_data = sparse_weight.data
126+
else:
127+
continue
111128

112-
# remove dense weight
113-
del params[name]
114-
memo.weight_name.append(name)
115-
memo.weight_shape.append(
116-
list(sparse_weight_data.shape)
117-
+ list(sparse_weight.indices.shape)
118-
+ list(sparse_weight.indptr.shape)
119-
)
120-
params[name + ".data"] = tvm.nd.array(sparse_weight_data)
121-
params[name + ".indices"] = tvm.nd.array(sparse_weight.indices)
122-
params[name + ".indptr"] = tvm.nd.array(sparse_weight.indptr)
123-
129+
# remove dense weight
130+
del params[name]
131+
memo.weight_name.append(name)
132+
memo.weight_shape.append(
133+
list(sparse_weight_data.shape)
134+
+ list(sparse_weight.indices.shape)
135+
+ list(sparse_weight.indptr.shape)
136+
)
137+
params[name + ".data"] = tvm.nd.array(sparse_weight_data)
138+
params[name + ".indices"] = tvm.nd.array(sparse_weight.indices)
139+
params[name + ".indptr"] = tvm.nd.array(sparse_weight.indptr)
140+
141+
if reg_task_input:
124142
prefix = "sparse_conv2d_bsr_%d_%d_%d_%d_%d_%d_" % (
125143
w_np.shape[0],
126144
w_np.shape[1],

python/tvm/relay/data_dep_optimization/bsr_conv2d.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
from .utils import _run_opt_pass
2424

2525

26-
def convert(func, params, blocksize, sparsity_threshold, layout="NHWC"):
27-
"""Convert a dense func and according parameters to block sparse
26+
def convert(func, params, blocksize, sparsity_threshold, layout="NHWC", kernel_size=1):
27+
"""Convert a conv2d func and according parameters to block sparse
2828
2929
Parameters
3030
----------
@@ -49,10 +49,46 @@ def convert(func, params, blocksize, sparsity_threshold, layout="NHWC"):
4949
params: Dict[Srting, tvm.nd.array]
5050
New params with BSR matrix for mutated Expr
5151
"""
52-
weight_info = process_params(func, params, blocksize, sparsity_threshold, layout)
52+
weight_info = process_params(func, params, blocksize, sparsity_threshold, layout, kernel_size)
5353
new_func = _run_opt_pass(
5454
func,
55-
relay.transform.Conv2dToSparse(weight_info.weight_name, weight_info.weight_shape, layout),
55+
relay.transform.Conv2dToSparse(
56+
weight_info.weight_name, weight_info.weight_shape, layout, kernel_size
57+
),
5658
)
5759

5860
return new_func, params
61+
62+
63+
def convert2(func, params, blocksize, sparsity_threshold, layout, kernel_size):
64+
"""Convert a freezed conv2d func to block sparse
65+
66+
Parameters
67+
----------
68+
func : relay.Expr
69+
Expr will be optimized to sparse operation, with params freezed
70+
params : Dict[Srting, tvm.nd.array]
71+
Parameters of the Expr (not used in this pass)
72+
blocksize : Tuple(int, int)
73+
Blocksize for BSR matrix
74+
sparsity_threshold : float
75+
Minimal sparsity requirement for converting.
76+
If weight sparsity is lower than this threshold,
77+
the dense operation will be kept.
78+
layout : str
79+
layout of network
80+
kernel_size : int
81+
kernel size of the conv2d, for filtering
82+
83+
Returns
84+
-------
85+
new_func: relay.Expr
86+
Mutated Expr with sparse operations
87+
88+
params: Dict[Srting, tvm.nd.array]
89+
New params with BSR matrix for mutated Expr (not modified)
90+
"""
91+
new_func = _run_opt_pass(
92+
func, relay.transform.Conv2dToSparse2(layout, kernel_size, blocksize, sparsity_threshold)
93+
)
94+
return new_func, params

python/tvm/relay/op/nn/_nn.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,11 @@ def compute_sparse_transpose(attrs, inputs, out_type):
198198
@reg.register_compute("nn.sparse_conv2d")
199199
def compute_sparse_conv2d(attrs, inputs, out_type):
200200
"""Compute definition of sparse_conv2d"""
201-
return [topi.nn.sparse_conv2d(inputs[0], inputs[1], inputs[2], inputs[3], attrs["layout"])]
201+
return [
202+
topi.nn.sparse_conv2d(
203+
inputs[0], inputs[1], inputs[2], inputs[3], attrs["layout"], attrs["kernel_size"]
204+
)
205+
]
202206

203207

204208
reg.register_strategy("nn.sparse_conv2d", strategy.sparse_conv2d_strategy)

python/tvm/relay/op/strategy/x86.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,31 @@ def sparse_dense_strategy_cpu(attrs, inputs, out_type, target):
565565
return strategy
566566

567567

568+
@sparse_conv2d_strategy.register("cpu")
569+
def sparse_conv2d_strategy_cpu(attrs, inputs, out_type, target):
570+
"""sparse conv2d x86 strategy"""
571+
strategy = _op.OpStrategy()
572+
if attrs["kernel_size"][0] == 1:
573+
strategy.add_implementation(
574+
wrap_compute_sparse_conv2d(topi.nn.sparse_conv2d),
575+
wrap_topi_schedule(topi.generic.schedule_sparse_conv2d),
576+
name="sparse_conv2d.generic",
577+
)
578+
elif attrs["kernel_size"][0] == 3:
579+
if attrs["layout"] == "NHWC":
580+
strategy.add_implementation(
581+
wrap_compute_sparse_conv2d(topi.x86.spconv2d_3x3_nhwc),
582+
wrap_topi_schedule(topi.x86.schedule_spconv2d_3x3_nhwc),
583+
name="conv3x3_spNHWC.x86",
584+
)
585+
elif attrs["layout"] == "NCHW":
586+
strategy.add_implementation(
587+
wrap_compute_sparse_conv2d(topi.x86.spconv2d_3x3_nchw),
588+
wrap_topi_schedule(topi.x86.schedule_spconv2d_3x3_nchw),
589+
)
590+
return strategy
591+
592+
568593
@roi_align_strategy.register("cpu")
569594
def roi_align_strategy_cpu(attrs, inputs, out_type, target):
570595
"""roi_align x86 strategy"""

python/tvm/relay/transform/transform.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,7 @@ def DenseToSparse(weight_name, weight_shape):
10931093
return _ffi_api.DenseToSparse(weight_name, weight_shape)
10941094

10951095

1096-
def Conv2dToSparse(weight_name, weight_shape, layout):
1096+
def Conv2dToSparse(weight_name, weight_shape, layout, kernel_size):
10971097
"""
10981098
Rewrite qualified ```nn.conv2d operation``` to ```nn.sparse_conv2d```
10991099
@@ -1113,7 +1113,27 @@ def Conv2dToSparse(weight_name, weight_shape, layout):
11131113
ret : tvm.transform.Pass
11141114
The registered DenseToSparse pass.
11151115
"""
1116-
return _ffi_api.Conv2dToSparse(weight_name, weight_shape, layout)
1116+
return _ffi_api.Conv2dToSparse(weight_name, weight_shape, layout, kernel_size)
1117+
1118+
1119+
def Conv2dToSparse2(layout, kernel_size, blocksize, sparsity_threshold):
1120+
"""
1121+
Rewrite freezed ```nn.conv2d``` operation to ```nn.sparse_conv2d```
1122+
1123+
Parameters
1124+
----------
1125+
layout : str
1126+
layout of data
1127+
1128+
kernel_size : int
1129+
kernel size of conv2d
1130+
1131+
Returns
1132+
-------
1133+
ret : tvm.transform.Pass
1134+
The registered DenseToSparse pass.
1135+
"""
1136+
return _ffi_api.Conv2dToSparse2(layout, kernel_size, *blocksize, sparsity_threshold)
11171137

11181138

11191139
def SimplifyFCTranspose(target_weight_name):

python/tvm/topi/nn/sparse.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,9 @@ def _compute_block(i, nb_j, j, h, w): # pylint: disable=C0103
566566
)
567567

568568

569-
def sparse_conv2d(dense_data, sparse_data, sparse_indices, sparse_indptr, layout="NHWC"):
569+
def sparse_conv2d(
570+
dense_data, sparse_data, sparse_indices, sparse_indptr, layout="NHWC", kernel_size=1
571+
):
570572
"""
571573
Computes sparse-conv2d(1*1) of ``data`` and
572574
``(weight_data, weight_indices, weight_indptr)``
@@ -598,14 +600,15 @@ def sparse_conv2d(dense_data, sparse_data, sparse_indices, sparse_indptr, layout
598600
4-D with shape [M, H, W, N] (layout=NHWC)
599601
4-D with shape [M, N, H ,W] (layout=NCHW)
600602
"""
601-
if layout == "NHWC":
602-
return _sparse_conv2d_bsr_compute_nhwc(
603-
dense_data, sparse_data, sparse_indices, sparse_indptr
604-
)
605-
elif layout == "NCHW":
606-
return _sparse_conv2d_bsr_compute_nchw(
607-
dense_data, sparse_data, sparse_indices, sparse_indptr
608-
)
603+
if kernel_size == 1:
604+
if layout == "NHWC":
605+
return _sparse_conv2d_bsr_compute_nhwc(
606+
dense_data, sparse_data, sparse_indices, sparse_indptr
607+
)
608+
elif layout == "NCHW":
609+
return _sparse_conv2d_bsr_compute_nchw(
610+
dense_data, sparse_data, sparse_indices, sparse_indptr
611+
)
609612
else:
610613
raise ValueError("Unsupport Layout %s" % layout)
611614

0 commit comments

Comments
 (0)