This is an automated email from the ASF dual-hosted git repository.

masahi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new 9da026194f [OpenCL][Adreno] Fix conv2d when output channels < 4 
(#14996)
9da026194f is described below

commit 9da026194f3220a76c1e0be6ab53d477133fde0b
Author: Egor Churaev <[email protected]>
AuthorDate: Thu Jun 1 08:45:38 2023 +0300

    [OpenCL][Adreno] Fix conv2d when output channels < 4 (#14996)
    
    When the number of output channels is less than 4, then we cannot pack
    such convolution to textures, although we can repack and extend tensors
    from 4d to 5d in runtime.
    
    It is happened because function `PropBoundToInputs` is invoked for all
    stages in when InferBound pass or LowerSchedule function is called.
    
    `PropBoundToInputs` has a logic in it that helps to developer to avoid
    out of bound access. And based on the output shape, it propagates it to
    inputs.
    
    Imagine that we want to transform a 4d tensor with 3 channels to 5d,
    extend its number of channels to 4 and then transform it back to 4d
    tensor with number of channels equal to 3. Example below:
    ```
    [1, 3, 6, 6] -> [1, 1, 6, 6, 4] -> [1, 3, 6, 6]
    ```
    In this case, we might write a boundary check in the repacking and
    extending compute function to handle the case when the iterator by the
    channels is out of bounds for the intermediate tensor.
    
    To avoid such problem, `PropBoundToInputs` has a logic which propagates
    a bounds from output tensor to input. In case when it is a possible
    situation that the compute has out of bounds access, then the range is
    decreased to value when such situation cannot be achieved. That means
    that for the example above the loop by channels which should filling
    intermediate tensor will iterate in range [0, 2] instead of [0, 3].
    
    As it was mentioned, to avoid such problem we use buffers and cuda
    schedules instead of textures for cases when number of output channels
    is less than 4 and we cannot pack such tensor to texture. I evaluated
    performance of such approach and it doesn't introduce any performance
    degradation. For such small convolutions performance with buffers even a
    bit better than with textures.
---
 python/tvm/relay/op/strategy/adreno.py             | 47 ++++++++++++++----
 .../opencl_texture/test_conv2d_nchw_texture.py     | 30 ++++++++++++
 .../opencl_texture/test_conv2d_nhwc_texture.py     | 30 ++++++++++++
 .../test_depthwise_conv2d_nchw_texture.py          | 31 ++++++++++++
 .../test_depthwise_conv2d_nhwc_texture.py          | 31 ++++++++++++
 .../unittest/test_te_schedule_bound_inference.py   | 56 +++++++++++++++-------
 6 files changed, 197 insertions(+), 28 deletions(-)

diff --git a/python/tvm/relay/op/strategy/adreno.py 
b/python/tvm/relay/op/strategy/adreno.py
index 712b66e246..c180eeec74 100644
--- a/python/tvm/relay/op/strategy/adreno.py
+++ b/python/tvm/relay/op/strategy/adreno.py
@@ -42,9 +42,18 @@ def conv2d_strategy_adreno(attrs, inputs, out_type, target):
             or (data_layout == "NCHW" and kernel_layout == "OIHW4o")
         ):
             if len(kernel.shape) == 4:
-                _, _, kh, kw = get_const_tuple(kernel.shape)
+                oc, _, kh, kw = get_const_tuple(kernel.shape)
             else:
-                _, _, kh, kw, _ = get_const_tuple(kernel.shape)
+                oc, _, kh, kw, _ = get_const_tuple(kernel.shape)
+            # We cannot use textures for case than number of channels is less 
than 4.
+            # So, we use compute functions from cuda.
+            if len(kernel.shape) == 4 and oc < 4:
+                strategy.add_implementation(
+                    wrap_compute_conv2d(topi.cuda.conv2d_nchw),
+                    wrap_topi_schedule(topi.cuda.schedule_conv2d_nchw),
+                    name="conv2d_nchw.cuda",
+                )
+                return strategy
             if (
                 (2 < kh < 8 and 2 < kw < 8 and kh == kw)
                 and (stride_h == 1 and stride_w == 1)
@@ -69,9 +78,18 @@ def conv2d_strategy_adreno(attrs, inputs, out_type, target):
             or (data_layout == "NHWC" and kernel_layout == "HWIO4o")
         ):
             if len(kernel.shape) == 4:
-                kh, kw, _, _ = get_const_tuple(kernel.shape)
+                kh, kw, _, oc = get_const_tuple(kernel.shape)
             else:
-                kh, kw, _, _, _ = get_const_tuple(kernel.shape)
+                kh, kw, _, oc, _ = get_const_tuple(kernel.shape)
+            # We cannot use textures for case than number of channels is less 
than 4.
+            # So, we use compute functions from cuda.
+            if len(kernel.shape) == 4 and oc < 4:
+                strategy.add_implementation(
+                    wrap_compute_conv2d(topi.gpu.conv2d_nhwc),
+                    wrap_topi_schedule(topi.gpu.schedule_conv2d_nhwc),
+                    name="conv2d_nhwc.gpu",
+                )
+                return strategy
             if (
                 (2 < kh < 8 and 2 < kw < 8 and kh == kw)
                 and (stride_h == 1 and stride_w == 1)
@@ -125,12 +143,21 @@ def conv2d_strategy_adreno(attrs, inputs, out_type, 
target):
             if (data_layout == "NCHW" and kernel_layout == "OIHW") or (
                 data_layout == "NCHW4c" and kernel_layout == "OIHW4o"
             ):
-                strategy.add_implementation(
-                    wrap_compute_conv2d(topi.adreno.depthwise_conv2d_nchwc),
-                    
wrap_topi_schedule(topi.adreno.schedule_depthwise_conv2d_nchwc),
-                    name="depthwise_conv2d_nchwc.image2d",
-                    plevel=10,
-                )
+                # We cannot use textures for case than number of channels is 
less than 4.
+                # So, we use compute functions from cuda.
+                if len(kernel.shape) == 4 and oc < 4:
+                    strategy.add_implementation(
+                        wrap_compute_conv2d(topi.cuda.depthwise_conv2d_nchw),
+                        
wrap_topi_schedule(topi.cuda.schedule_depthwise_conv2d_nchw),
+                        name="depthwise_conv2d_nchw.cuda",
+                    )
+                else:
+                    strategy.add_implementation(
+                        
wrap_compute_conv2d(topi.adreno.depthwise_conv2d_nchwc),
+                        
wrap_topi_schedule(topi.adreno.schedule_depthwise_conv2d_nchwc),
+                        name="depthwise_conv2d_nchwc.image2d",
+                        plevel=10,
+                    )
             elif (data_layout == "NHWC" and kernel_layout == "HWOI") or (
                 data_layout == "NHWC4c" and kernel_layout == "HWOI4o"
             ):
diff --git a/tests/python/relay/opencl_texture/test_conv2d_nchw_texture.py 
b/tests/python/relay/opencl_texture/test_conv2d_nchw_texture.py
index c5b58a7a8a..cd5a992421 100644
--- a/tests/python/relay/opencl_texture/test_conv2d_nchw_texture.py
+++ b/tests/python/relay/opencl_texture/test_conv2d_nchw_texture.py
@@ -1289,5 +1289,35 @@ def test_injective_nwo_inputs2(remote, target, dtype):
     )
 
 
[email protected]_opencl
[email protected]_targets("opencl -device=adreno")
+def test_conv2d_to_3_channels(remote, target, dtype):
+    input_shape = (1, 256, 200, 200)
+    filter_shape = (3, 256, 1, 1)
+    A = relay.var("data", shape=input_shape, dtype=dtype)
+    B = relay.var("weight", shape=filter_shape, dtype=dtype)
+
+    D = relay.nn.conv2d(
+        A,
+        B,
+        data_layout="NCHW",
+        kernel_layout="OIHW",
+        padding=[0, 0, 0, 0],
+        out_dtype=dtype,
+        channels=3,
+        kernel_size=(1, 1),
+    )
+    mod = relay.Function([A, B], D)
+    np.random.seed(0)
+    initializer = relay.testing.init.Xavier()
+    filter_data = np.zeros(filter_shape).astype(dtype)
+    initializer("weight", filter_data)
+    params1 = {
+        "weight": tvm.nd.array(filter_data),
+    }
+
+    build_run_compare(remote, mod, params1, {"data": input_shape}, {"data": 
dtype}, target, [])
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git a/tests/python/relay/opencl_texture/test_conv2d_nhwc_texture.py 
b/tests/python/relay/opencl_texture/test_conv2d_nhwc_texture.py
index f2bfc91174..5f69e777d9 100644
--- a/tests/python/relay/opencl_texture/test_conv2d_nhwc_texture.py
+++ b/tests/python/relay/opencl_texture/test_conv2d_nhwc_texture.py
@@ -737,5 +737,35 @@ def test_conv2d_winograd_non_rect(remote, target, dtype):
     assert len(matches) > 0
 
 
[email protected]_opencl
[email protected]_targets("opencl -device=adreno")
+def test_conv2d_to_3_channels(remote, target, dtype):
+    input_shape = (1, 200, 200, 256)
+    filter_shape = (1, 1, 256, 3)
+    A = relay.var("data", shape=input_shape, dtype=dtype)
+    B = relay.var("weight", shape=filter_shape, dtype=dtype)
+
+    D = relay.nn.conv2d(
+        A,
+        B,
+        data_layout="NHWC",
+        kernel_layout="HWIO",
+        padding=[0, 0, 0, 0],
+        out_dtype=dtype,
+        channels=3,
+        kernel_size=(1, 1),
+    )
+    mod = relay.Function([A, B], D)
+    np.random.seed(0)
+    initializer = relay.testing.init.Xavier()
+    filter_data = np.zeros(filter_shape).astype(dtype)
+    initializer("weight", filter_data)
+    params1 = {
+        "weight": tvm.nd.array(filter_data),
+    }
+
+    build_run_compare(remote, mod, params1, {"data": input_shape}, {"data": 
dtype}, target, [])
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git 
a/tests/python/relay/opencl_texture/test_depthwise_conv2d_nchw_texture.py 
b/tests/python/relay/opencl_texture/test_depthwise_conv2d_nchw_texture.py
index 00e2c5a8c0..2c729a36eb 100644
--- a/tests/python/relay/opencl_texture/test_depthwise_conv2d_nchw_texture.py
+++ b/tests/python/relay/opencl_texture/test_depthwise_conv2d_nchw_texture.py
@@ -192,5 +192,36 @@ def test_depthwise_conv2d_repack_bias_nchw(remote, target, 
dtype):
     build_run_compare(remote, mod, params1, {"data": input_shape}, {"data": 
dtype}, target)
 
 
[email protected]_opencl
[email protected]_targets("opencl -device=adreno")
+def test_conv2d_to_3_channels(remote, target, dtype):
+    input_shape = (1, 3, 200, 200)
+    filter_shape = (3, 1, 1, 1)
+    A = relay.var("data", shape=input_shape, dtype=dtype)
+    B = relay.var("weight", shape=filter_shape, dtype=dtype)
+
+    D = relay.nn.conv2d(
+        A,
+        B,
+        data_layout="NCHW",
+        kernel_layout="OIHW",
+        padding=[0, 0, 0, 0],
+        out_dtype=dtype,
+        channels=3,
+        groups=3,
+        kernel_size=(1, 1),
+    )
+    mod = relay.Function([A, B], D)
+    np.random.seed(0)
+    initializer = relay.testing.init.Xavier()
+    filter_data = np.zeros(filter_shape).astype(dtype)
+    initializer("weight", filter_data)
+    params1 = {
+        "weight": tvm.nd.array(filter_data),
+    }
+
+    build_run_compare(remote, mod, params1, {"data": input_shape}, {"data": 
dtype}, target, [])
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git 
a/tests/python/relay/opencl_texture/test_depthwise_conv2d_nhwc_texture.py 
b/tests/python/relay/opencl_texture/test_depthwise_conv2d_nhwc_texture.py
index 7d7f640294..28f0f4cefa 100644
--- a/tests/python/relay/opencl_texture/test_depthwise_conv2d_nhwc_texture.py
+++ b/tests/python/relay/opencl_texture/test_depthwise_conv2d_nhwc_texture.py
@@ -225,5 +225,36 @@ def test_depthwise_conv2d_1_513_513_3x3_3_3_1(remote, 
target, dtype):
     build_run_compare(remote, mod, params1, {"data": input_shape}, {"data": 
dtype}, target)
 
 
[email protected]_opencl
[email protected]_targets("opencl -device=adreno")
+def test_conv2d_to_3_channels(remote, target, dtype):
+    input_shape = (1, 200, 200, 3)
+    filter_shape = (1, 1, 3, 1)
+    A = relay.var("data", shape=input_shape, dtype=dtype)
+    B = relay.var("weight", shape=filter_shape, dtype=dtype)
+
+    D = relay.nn.conv2d(
+        A,
+        B,
+        data_layout="NHWC",
+        kernel_layout="HWOI",
+        padding=[0, 0, 0, 0],
+        out_dtype=dtype,
+        channels=3,
+        groups=3,
+        kernel_size=(1, 1),
+    )
+    mod = relay.Function([A, B], D)
+    np.random.seed(0)
+    initializer = relay.testing.init.Xavier()
+    filter_data = np.zeros(filter_shape).astype(dtype)
+    initializer("weight", filter_data)
+    params1 = {
+        "weight": tvm.nd.array(filter_data),
+    }
+
+    build_run_compare(remote, mod, params1, {"data": input_shape}, {"data": 
dtype}, target, [])
+
+
 if __name__ == "__main__":
     tvm.testing.main()
diff --git a/tests/python/unittest/test_te_schedule_bound_inference.py 
b/tests/python/unittest/test_te_schedule_bound_inference.py
index 03e9886314..c246ee9f41 100644
--- a/tests/python/unittest/test_te_schedule_bound_inference.py
+++ b/tests/python/unittest/test_te_schedule_bound_inference.py
@@ -471,22 +471,42 @@ def test_bound_simplification_failure():
     _check(te.compute((10,), lambda i: A[i]))
 
 
+def test_bound_block():
+    def _check(shape, expected, block_size=4):
+        N, C, H, W = shape
+        tail = C % block_size
+        chunks = C // block_size
+        if tail != 0:
+            chunks += 1
+        A = te.placeholder((N, C, H, W), name="A")
+        pad_value = tvm.tir.const(0, A.dtype)
+
+        def _reorder_data_nchw(*indices):
+            condition = []
+            condition.append(indices[1] == chunks - 1)
+            condition.append(indices[4] >= tail)
+            condition = tvm.tir.all(*condition)
+            return tvm.tir.if_then_else(
+                condition,
+                pad_value,
+                A[indices[0], indices[1] * block_size + indices[4], 
indices[2], indices[3]],
+            )
+
+        repack = te.compute((N, chunks, H, W, block_size), _reorder_data_nchw, 
name="repack")
+        B = te.compute(
+            (N, C, H, W),
+            lambda n, c, h, w: repack[n, c // block_size, h, w, c % 
block_size],
+            name="back_repack",
+        )
+        s = te.create_schedule([B.op])
+        bounds = tvm.te.schedule.InferBound(s)
+        # Block for intermediate compute function should be equal to 4 for all 
cases except than number of channels is less than 4
+        assert bounds[repack.op.axis[4]].extent.value == expected
+
+    _check((1, 4, 6, 6), 4)
+    _check((1, 7, 6, 6), 4)
+    _check((1, 3, 6, 6), 3)
+
+
 if __name__ == "__main__":
-    test_bound_nest_thread()
-    test_bound1()
-    test_bound_nest_group()
-    test_bound_group_schedule()
-    test_bound_scan()
-    test_bound3()
-    test_bound_rfactor()
-    test_bound_blur()
-    test_bound_conv1d()
-    test_bound2()
-    test_gemm_bound()
-    test_bound_warp()
-    test_bound_tensor_compute_op()
-    test_bound_simplification_failure()
-    test_bound_fusesplit1()
-    test_bound_fusesplit2()
-    test_bound_split_divisible()
-    test_bound_tile_divisible()
+    tvm.testing.main()

Reply via email to