Skip to content

Commit 9808108

Browse files
authored
[Relax] Legalize dilated conv_transpose (#19842)
## Why relax.nn.conv{1,2,3}d_transpose with dilation > 1 silently bailed in legalize and then crashed in VM codegen with an opaque error. ## How - Lower dilation > 1 by zero-filling (dilating) the kernel, then reusing the existing TOPI transposed-conv compute (1D/2D/3D). - Unsupported non-NCHW layouts and out_layout != data_layout keep their existing passthrough (left for downstream/BYOC codegen such as CLML), unchanged. - Add a 2D-dilation structural test. Signed-off-by: Guan-Ming (Wesley) Chiu <105915352+guan404ming@users.noreply.github.com>
1 parent 168819f commit 9808108

2 files changed

Lines changed: 96 additions & 45 deletions

File tree

python/tvm/relax/transform/legalize_ops/nn.py

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -164,24 +164,23 @@ def _nn_conv1d_transpose(bb: BlockBuilder, call: Call) -> Expr:
164164
"and kernel layout other than IOW, so cannot be legalized by TOPI"
165165
)
166166
return call
167-
dilation = call.attrs.dilation
168-
if len(dilation) != 1 or dilation[0] != 1:
169-
logging.info(
170-
"TOPI conv1d_transpose does not support dilations other than 1, "
171-
"and thus cannot be legalized by TOPI"
167+
strides = [int(s) for s in call.attrs.strides]
168+
padding = [int(p) for p in call.attrs.padding]
169+
output_padding = [int(o) for o in call.attrs.output_padding]
170+
groups = int(call.attrs.groups)
171+
out_dtype = call.ty.dtype
172+
dilation = [int(d) for d in call.attrs.dilation]
173+
174+
def te_conv1d_transpose(data, kernel):
175+
# Dilated transposed conv == transposed conv with a spatially dilated (zero-filled) kernel.
176+
if any(d != 1 for d in dilation):
177+
kernel = topi.nn.dilate(kernel, [1, 1, dilation[0]], name="kernel_dilate")
178+
return topi.nn.group_conv1d_transpose_ncw(
179+
data, kernel, strides, padding, out_dtype, output_padding, groups
172180
)
173-
return call
174181

175182
return bb.call_te(
176-
topi.nn.group_conv1d_transpose_ncw,
177-
call.args[0],
178-
call.args[1],
179-
stride=call.attrs.strides,
180-
padding=call.attrs.padding,
181-
out_dtype=call.ty.dtype,
182-
output_padding=call.attrs.output_padding,
183-
groups=call.attrs.groups,
184-
primfunc_name_hint="conv1d_transpose",
183+
te_conv1d_transpose, call.args[0], call.args[1], primfunc_name_hint="conv1d_transpose"
185184
)
186185

187186

@@ -199,24 +198,23 @@ def _nn_conv2d_transpose(bb: BlockBuilder, call: Call) -> Expr:
199198
"and kernel layout other than IOHW, so cannot be legalized by TOPI"
200199
)
201200
return call
202-
dilation = call.attrs.dilation
203-
if len(dilation) != 2 or any(d != 1 for d in dilation):
204-
logging.info(
205-
"TOPI conv2d_transpose does not support dilations other than 1, "
206-
"and thus cannot be legalized by TOPI"
201+
strides = [int(s) for s in call.attrs.strides]
202+
padding = [int(p) for p in call.attrs.padding]
203+
output_padding = [int(o) for o in call.attrs.output_padding]
204+
groups = int(call.attrs.groups)
205+
out_dtype = call.ty.dtype
206+
dilation = [int(d) for d in call.attrs.dilation]
207+
208+
def te_conv2d_transpose(data, kernel):
209+
# Dilated transposed conv == transposed conv with a spatially dilated (zero-filled) kernel.
210+
if any(d != 1 for d in dilation):
211+
kernel = topi.nn.dilate(kernel, [1, 1, dilation[0], dilation[1]], name="kernel_dilate")
212+
return topi.nn.group_conv2d_transpose_nchw(
213+
data, kernel, strides, padding, out_dtype, output_padding, groups
207214
)
208-
return call
209215

210216
return bb.call_te(
211-
topi.nn.group_conv2d_transpose_nchw,
212-
call.args[0],
213-
call.args[1],
214-
stride=call.attrs.strides,
215-
padding=call.attrs.padding,
216-
out_dtype=call.ty.dtype,
217-
output_padding=call.attrs.output_padding,
218-
groups=call.attrs.groups,
219-
primfunc_name_hint="conv2d_transpose",
217+
te_conv2d_transpose, call.args[0], call.args[1], primfunc_name_hint="conv2d_transpose"
220218
)
221219

222220

@@ -236,24 +234,25 @@ def _nn_conv3d_transpose(bb: BlockBuilder, call: Call) -> Expr:
236234
"and kernel layout other than IODHW, so cannot be legalized by TOPI"
237235
)
238236
return call
239-
dilation = call.attrs.dilation
240-
if len(dilation) != 3 or any(d != 1 for d in dilation):
241-
logging.info(
242-
"TOPI conv3d_transpose does not support dilations other than 1, "
243-
"and thus cannot be legalized by TOPI"
237+
strides = [int(s) for s in call.attrs.strides]
238+
padding = [int(p) for p in call.attrs.padding]
239+
output_padding = [int(o) for o in call.attrs.output_padding]
240+
groups = int(call.attrs.groups)
241+
out_dtype = call.ty.dtype
242+
dilation = [int(d) for d in call.attrs.dilation]
243+
244+
def te_conv3d_transpose(data, kernel):
245+
# Dilated transposed conv == transposed conv with a spatially dilated (zero-filled) kernel.
246+
if any(d != 1 for d in dilation):
247+
kernel = topi.nn.dilate(
248+
kernel, [1, 1, dilation[0], dilation[1], dilation[2]], name="kernel_dilate"
249+
)
250+
return topi.nn.group_conv3d_transpose_ncdhw(
251+
data, kernel, strides, padding, out_dtype, output_padding, groups
244252
)
245-
return call
246253

247254
return bb.call_te(
248-
topi.nn.group_conv3d_transpose_ncdhw,
249-
call.args[0],
250-
call.args[1],
251-
strides=call.attrs.strides,
252-
padding=call.attrs.padding,
253-
out_dtype=call.ty.dtype,
254-
output_padding=call.attrs.output_padding,
255-
groups=call.attrs.groups,
256-
primfunc_name_hint="conv3d_transpose",
255+
te_conv3d_transpose, call.args[0], call.args[1], primfunc_name_hint="conv3d_transpose"
257256
)
258257

259258

tests/python/relax/test_transform_legalize_ops_nn.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,58 @@ def conv2d_transpose(var_rxplaceholder: T.handle, var_rxplaceholder_1: T.handle,
725725
tvm.ir.assert_structural_equal(mod, Expected)
726726

727727

728+
def test_conv2d_transpose_dilation():
729+
# fmt: off
730+
@tvm.script.ir_module
731+
class Conv2dTranspose:
732+
@R.function
733+
def main(x: R.Tensor((1, 1, 3, 3), "float32"), w: R.Tensor((1, 1, 2, 2), "float32")):
734+
gv = R.nn.conv2d_transpose(x, w, dilation=(2, 2))
735+
return gv
736+
737+
@I.ir_module(s_tir=True)
738+
class Expected:
739+
@T.prim_func(private=True, s_tir=True)
740+
def conv2d_transpose(x: T.Buffer((T.int64(1), T.int64(1), T.int64(3), T.int64(3)), "float32"), w: T.Buffer((T.int64(1), T.int64(1), T.int64(2), T.int64(2)), "float32"), compute: T.Buffer((T.int64(1), T.int64(1), T.int64(5), T.int64(5)), "float32")):
741+
T.func_attr({"tirx.noalias": True})
742+
data_dilate = T.sblock_alloc_buffer((T.int64(1), T.int64(1), T.int64(3), T.int64(3)))
743+
data_pad = T.sblock_alloc_buffer((T.int64(1), T.int64(1), T.int64(7), T.int64(7)))
744+
kernel_dilate = T.sblock_alloc_buffer((T.int64(1), T.int64(1), T.int64(3), T.int64(3)))
745+
kernel_transform = T.sblock_alloc_buffer((T.int64(1), T.int64(1), T.int64(3), T.int64(3)))
746+
for i0, i1, i2, i3 in T.grid(T.int64(1), T.int64(1), T.int64(3), T.int64(3)):
747+
with T.sblock("data_dilate"):
748+
v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
749+
data_dilate[v_i0, v_i1, v_i2, v_i3] = x[v_i0, v_i1, v_i2, v_i3]
750+
for i0, i1, i2, i3 in T.grid(T.int64(1), T.int64(1), T.int64(7), T.int64(7)):
751+
with T.sblock("data_pad"):
752+
v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
753+
data_pad[v_i0, v_i1, v_i2, v_i3] = T.if_then_else(T.int64(2) <= v_i2 and v_i2 < T.int64(5) and T.int64(2) <= v_i3 and v_i3 < T.int64(5), data_dilate[v_i0, v_i1, v_i2 - T.int64(2), v_i3 - T.int64(2)], T.float32(0.0))
754+
for i0, i1, i2, i3 in T.grid(T.int64(1), T.int64(1), T.int64(3), T.int64(3)):
755+
with T.sblock("kernel_dilate"):
756+
v_i0, v_i1, v_i2, v_i3 = T.axis.remap("SSSS", [i0, i1, i2, i3])
757+
kernel_dilate[v_i0, v_i1, v_i2, v_i3] = T.if_then_else(v_i2 % T.int64(2) == T.int64(0) and v_i3 % T.int64(2) == T.int64(0), w[v_i0, v_i1, v_i2 // T.int64(2), v_i3 // T.int64(2)], T.float32(0.0))
758+
for o, i, h, w_1 in T.grid(T.int64(1), T.int64(1), T.int64(3), T.int64(3)):
759+
with T.sblock("kernel_transform"):
760+
v_o, v_i, v_h, v_w = T.axis.remap("SSSS", [o, i, h, w_1])
761+
kernel_transform[v_o, v_i, v_h, v_w] = kernel_dilate[v_i, v_o, T.int64(2) - v_h, T.int64(2) - v_w]
762+
for b, c, h, w_1, dc, dh, dw in T.grid(T.int64(1), T.int64(1), T.int64(5), T.int64(5), T.int64(1), T.int64(3), T.int64(3)):
763+
with T.sblock("compute"):
764+
v_b, v_c, v_h, v_w, v_dc, v_dh, v_dw = T.axis.remap("SSSSRRR", [b, c, h, w_1, dc, dh, dw])
765+
with T.init():
766+
compute[v_b, v_c, v_h, v_w] = T.float32(0.0)
767+
compute[v_b, v_c, v_h, v_w] = compute[v_b, v_c, v_h, v_w] + data_pad[v_b, v_dc, v_h + v_dh, v_w + v_dw] * kernel_transform[v_c, v_dc, v_dh, v_dw]
768+
769+
@R.function
770+
def main(x: R.Tensor((1, 1, 3, 3), dtype="float32"), w: R.Tensor((1, 1, 2, 2), dtype="float32")) -> R.Tensor((1, 1, 5, 5), dtype="float32"):
771+
cls = Expected
772+
gv = R.call_tir(cls.conv2d_transpose, (x, w), out_ty=R.Tensor((1, 1, 5, 5), dtype="float32"))
773+
return gv
774+
# fmt: on
775+
776+
mod = LegalizeOps()(Conv2dTranspose)
777+
tvm.ir.assert_structural_equal(mod, Expected)
778+
779+
728780
def test_max_pool2d():
729781
# fmt: off
730782
@tvm.script.ir_module

0 commit comments

Comments
 (0)