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 e6c9d69aaf [QNN] Implement quantized avg_pool2d (#15057)
e6c9d69aaf is described below

commit e6c9d69aafb3ba481797dedc5cc945e2506a8352
Author: Krzysztof Parzyszek <[email protected]>
AuthorDate: Thu Jun 15 07:01:07 2023 -0500

    [QNN] Implement quantized avg_pool2d (#15057)
    
    * [QNN] Implement quantized avg_pool2d
    
    * qnn.avg_pool2d is integrated into Relay and integration tests are added
    * FQ2I is modified to pick up qnn.avg_pool2d
    * Canonicalization and layout conversion for qnn.avg_pool2d are implemented
    * Dynamic PoolArea computation is implemented for both qnn and slice_op 
avg_pool2d
      to exclude padding area in averaging calculation when 
count_include_pad=False
    
    Co-authored-by: Fateme Hosseini <[email protected]>
    Co-authored-by: Jyotsna Verma <[email protected]>
    Co-authored-by: Anirudh Sundar <[email protected]>
    Co-authored-by: Venkat Rasagna Reddy Komatireddy <[email protected]>
    
    * Add missing clip operation before cast
    
    * Address review comments
    
    Merge test_wo_qnn_canonicalization.py into test_qnn_op_integration.py.
    
    * Fix typo: tvm.testing.parameters -> tvm.testing.parameter
    
    ---------
    
    Co-authored-by: Fateme Hosseini <[email protected]>
    Co-authored-by: Jyotsna Verma <[email protected]>
    Co-authored-by: Anirudh Sundar <[email protected]>
    Co-authored-by: Venkat Rasagna Reddy Komatireddy <[email protected]>
---
 python/tvm/relay/qnn/op/_qnn.py                    |   3 +
 python/tvm/relay/qnn/op/layout_conversions.py      |  35 ++
 python/tvm/relay/qnn/op/qnn.py                     |  66 +++
 python/tvm/relay/qnn/strategy/generic.py           |  39 ++
 python/tvm/relay/qnn/strategy/hexagon.py           |  24 +
 .../transform/fake_quantization_to_integer.py      |  57 +-
 python/tvm/topi/hexagon/compute_poolarea.py        | 143 +++++
 python/tvm/topi/hexagon/qnn/__init__.py            |   2 +-
 python/tvm/topi/hexagon/qnn/avg_pool2d.py          | 408 +++++++++++----
 python/tvm/topi/hexagon/qnn/nn.py                  |  47 +-
 python/tvm/topi/hexagon/slice_ops/__init__.py      |   2 +-
 python/tvm/topi/hexagon/slice_ops/avg_pool2d.py    | 224 +++++---
 python/tvm/topi/hexagon/utils.py                   |  65 ++-
 src/relay/op/nn/pooling.cc                         |  38 +-
 src/relay/op/nn/pooling.h                          |   4 +-
 src/relay/op/nn/pooling_common.h                   |  78 +++
 src/relay/qnn/op/avg_pool2d.cc                     | 223 ++++++++
 .../test_hexagon/test_qnn_op_integration.py        | 576 +++++++++++++++++++++
 .../test_hexagon/test_wo_qnn_canonicalization.py   | 475 -----------------
 .../topi/slice_op/test_avg_pool2d_slice.py         | 412 +++++++--------
 20 files changed, 1968 insertions(+), 953 deletions(-)

diff --git a/python/tvm/relay/qnn/op/_qnn.py b/python/tvm/relay/qnn/op/_qnn.py
index 2ad2445cd3..f036e6cf84 100644
--- a/python/tvm/relay/qnn/op/_qnn.py
+++ b/python/tvm/relay/qnn/op/_qnn.py
@@ -167,3 +167,6 @@ register_strategy("qnn.contrib_dense_pack", 
strategy.qnn_dense_pack_strategy)
 # qnn.batch_matmul
 register_strategy("qnn.batch_matmul", strategy.qnn_batch_matmul_strategy)
 register_pattern("qnn.batch_matmul", OpPattern.OUT_ELEMWISE_FUSABLE)
+
+# qnn.avg_pool2d
+register_strategy("qnn.avg_pool2d", strategy.qnn_avg_pool2d_strategy)
diff --git a/python/tvm/relay/qnn/op/layout_conversions.py 
b/python/tvm/relay/qnn/op/layout_conversions.py
index 668cafb8ae..5879936031 100644
--- a/python/tvm/relay/qnn/op/layout_conversions.py
+++ b/python/tvm/relay/qnn/op/layout_conversions.py
@@ -126,3 +126,38 @@ def convert_qnn_conv2d_transpose(attrs, inputs, tinfos, 
desired_layouts):
         return relay.qnn.op.conv2d_transpose(*inputs, **new_attrs)
 
     raise ValueError(f"Layout {desired_data_layout} is not yet supported")
+
+
[email protected]_convert_op_layout("qnn.avg_pool2d")
+def convert_qnn_avg_pool2d(attrs, inputs, tinfos, desired_layouts):
+    """Convert Layout pass registration for QNN avg_pool2d op.
+
+    Parameters
+    ----------
+    attrs : tvm.ir.Attrs
+        Attributes of current avg_pool2d
+    inputs : list of tvm.relay.Expr
+        The args of the Relay expr to be legalized
+    tinfos : list of types
+        List of input and output types
+    desired_layouts : list of layout strings
+        List of layouts defining our desired
+        layout for the data input.
+
+    Returns
+    -------
+    result : tvm.relay.Expr
+        The transformed expr
+    """
+    # pylint: disable=import-outside-toplevel
+    from tvm import relay
+
+    assert len(desired_layouts) == 1, "A desired layout is expected for 
qnn.avg_pool2d's input"
+    desired_data_layout = desired_layouts[0]
+    if desired_data_layout == "NCHW" or desired_data_layout == "NHWC":
+        new_attrs = dict(attrs)
+        new_attrs["layout"] = str(desired_data_layout)
+        new_attrs["out_layout"] = str(desired_data_layout)
+        return relay.qnn.op.avg_pool2d(*inputs, **new_attrs)
+
+    raise ValueError(f"Layout {desired_data_layout} is not yet supported")
diff --git a/python/tvm/relay/qnn/op/qnn.py b/python/tvm/relay/qnn/op/qnn.py
index 0e73a6889f..eb64b56e82 100644
--- a/python/tvm/relay/qnn/op/qnn.py
+++ b/python/tvm/relay/qnn/op/qnn.py
@@ -1249,3 +1249,69 @@ def leaky_relu(x, alpha, input_scale, input_zero_point, 
output_scale, output_zer
 
 def softmax(x, scale, zero_point, output_scale, output_zero_point, axis=-1):
     return _make.softmax(x, axis, scale, zero_point, output_scale, 
output_zero_point)
+
+
+def avg_pool2d(
+    data,
+    input_scale,
+    input_zero_point,
+    output_scale,
+    output_zero_point,
+    pool_size,
+    strides,
+    padding,
+    dilation,
+    ceil_mode=False,
+    count_include_pad=True,
+    layout="NHWC",
+    out_layout="",
+):
+
+    """Quantized avg_pool2d
+
+    Parameters
+    ----------
+    data : relay.Expr
+        The quantized input tensor.
+    input_scale: float
+        The scale of the input quantized expr.
+    input_zero_point: int
+        The zero point of input quantized expr.
+    output_scale: flaot
+        The scale of the output quantized expr.
+    output_zero_point: int
+       The zero point of output quantized expr.
+    pool_size : relay.Expr
+        The pool_size
+    strides : relay.Expr
+        The strides
+    padding : relay.Expr
+        The padding size
+    dilation : relay.Expr
+        The dilation size
+    ceil_mode : bool, optional
+        Whether to use ceil or floor for calculating the output shape
+    count_include_pad : bool, optional
+        Determines if padding should be taken into account in the computation
+    layout: string, optinal
+    out_layout: string, optional
+    Returns
+    -------
+    result : relay.Expr
+        The computed result.
+    """
+    return _make.avg_pool2d(
+        data,
+        input_scale,
+        input_zero_point,
+        output_scale,
+        output_zero_point,
+        pool_size,
+        strides,
+        padding,
+        dilation,
+        ceil_mode,
+        count_include_pad,
+        layout,
+        out_layout,
+    )
diff --git a/python/tvm/relay/qnn/strategy/generic.py 
b/python/tvm/relay/qnn/strategy/generic.py
index 3ebf8edd36..4c5884ffdc 100644
--- a/python/tvm/relay/qnn/strategy/generic.py
+++ b/python/tvm/relay/qnn/strategy/generic.py
@@ -157,6 +157,36 @@ def wrap_topi_qnn_dense(topi_compute):
     return wrapper
 
 
+def wrap_compute_qnn_avg_pool2d(topi_compute):
+    """Wrap qnn.avg_pool2d topi compute"""
+
+    def wrapper(attrs, inputs, out_type):
+        kernel = attrs.pool_size
+        strides = attrs.strides
+        padding = attrs.padding
+        dilation = attrs.dilation
+        count_include_pad = attrs.count_include_pad
+        oshape = out_type.shape
+        odtype = out_type.dtype
+        args = [
+            inputs[0],
+            kernel,
+            strides,
+            padding,
+            dilation,
+            count_include_pad,
+            oshape,
+            odtype,
+            inputs[1],
+            inputs[2],
+            inputs[3],
+            inputs[4],
+        ]
+        return [topi_compute(*args)]
+
+    return wrapper
+
+
 def wrap_topi_concatenate(topi_compute):
     """Wrap TOPI compute which use qnn.concatenate attrs"""
 
@@ -280,3 +310,12 @@ def qnn_batch_matmul_strategy(attrs, inputs, out_type, 
target):
         "qnn.batch_matmul is currently only supported with Hexagon. "
         "Please run QNN Canonicalize pass to decompose this op into supported 
ops."
     )
+
+
+@override_native_generic_func("qnn_avg_pool2d_strategy")
+def qnn_avg_pool2d_strategy(attrs, inputs, out_type, target):
+    """qnn.avg_pool2d generic strategy"""
+    raise RuntimeError(
+        "qnn.avg_pool2d is currently only supported with Hexagon. "
+        "Please run QNN Canonicalize pass to decompose this op into supported 
ops."
+    )
diff --git a/python/tvm/relay/qnn/strategy/hexagon.py 
b/python/tvm/relay/qnn/strategy/hexagon.py
index d17b0da6cf..3edbce34e3 100644
--- a/python/tvm/relay/qnn/strategy/hexagon.py
+++ b/python/tvm/relay/qnn/strategy/hexagon.py
@@ -201,3 +201,27 @@ def qnn_batch_matmul_strategy_hexagon(attrs, inputs, 
out_type, target):
         name="qnn_batch_matmul.hexagon",
     )
     return strategy
+
+
+@qnn_avg_pool2d_strategy.register(["hexagon"])
+def qnn_avg_pool2d_strategy_hexagon(attrs, inputs, out_type, target):
+    """qnn.avg_pool2d strategy for Hexagon"""
+    data_layout = attrs.layout
+    if data_layout == "NHWC":
+        strategy = _op.OpStrategy()
+        strategy.add_implementation(
+            
wrap_compute_qnn_avg_pool2d(topi.hexagon.qnn.qnn_avg_pool2d_wrapper_compute_NHWC),
+            wrap_topi_schedule(topi.hexagon.qnn.schedule_qnn_avg_pool2d),
+            name="qnn_avg_pool2d.hexagon",
+        )
+        return strategy
+    elif data_layout == "NCHW":
+        strategy = _op.OpStrategy()
+        strategy.add_implementation(
+            
wrap_compute_qnn_avg_pool2d(topi.hexagon.qnn.qnn_avg_pool2d_wrapper_compute_NCHW),
+            wrap_topi_schedule(topi.hexagon.qnn.schedule_qnn_avg_pool2d),
+            name="qnn_avg_pool2d.hexagon",
+        )
+        return strategy
+    else:
+        raise RuntimeError("Unsupported strategy for qnn.avg_pool2d")
diff --git a/python/tvm/relay/transform/fake_quantization_to_integer.py 
b/python/tvm/relay/transform/fake_quantization_to_integer.py
index 4c9a3f7cd0..5e289b0c93 100644
--- a/python/tvm/relay/transform/fake_quantization_to_integer.py
+++ b/python/tvm/relay/transform/fake_quantization_to_integer.py
@@ -111,6 +111,32 @@ register_unary_identity("min")
 register_unary_identity("image.resize2d")
 
 
+@register_fake_quantization_to_integer("nn.avg_pool2d")
+def avgpool2d(expr, type_map):
+    """Rewrite an avgpool op"""
+    attrs = {**expr.attrs}
+    arg = expr.args[0]
+    t = type_map[arg]
+    out_t = type_map[expr]
+
+    out = relay.qnn.op.avg_pool2d(
+        arg,
+        t.scale,
+        t.zero_point,
+        out_t.scale,
+        out_t.zero_point,
+        attrs["pool_size"],
+        attrs["strides"],
+        attrs["padding"],
+        attrs["dilation"],
+        attrs["ceil_mode"],
+        attrs["count_include_pad"],
+        attrs["layout"],
+    )
+
+    return [out, TensorAffineType(out_t.scale, out_t.zero_point, out_t.dtype, 
out_t.axis)]
+
+
 @register_fake_quantization_to_integer("nn.adaptive_avg_pool1d")
 def adaptive_avgpool1d(expr, type_map):
     """Rewrite an adaptive avgpool op"""
@@ -138,37 +164,6 @@ def adaptive_avgpool1d(expr, type_map):
     return [out, TensorAffineType(out_t.scale, out_t.zero_point, "int32", 
out_t.axis)]
 
 
-@register_fake_quantization_to_integer("nn.avg_pool2d")
-def avgpool2d(expr, type_map):
-    """Rewrite a avgpool op"""
-    arg = expr.args[0]
-    t = type_map[arg]
-    out_t = type_map[expr]
-    # Cast (or requantize) to int32.
-    if not (
-        approx_equal(t.scale, out_t.scale)
-        and approx_equal(t.zero_point, out_t.zero_point)
-        and tvm.ir.structural_equal(t.dtype, out_t.dtype)
-    ):
-        arg = relay.qnn.op.requantize(
-            arg,
-            t.scale,
-            t.zero_point,
-            out_t.scale,
-            out_t.zero_point,
-            out_dtype="int32",
-            axis=t.axis,
-        )
-    else:
-        arg = relay.op.cast(arg, "int32")
-    out = relay.op.nn.avg_pool2d(arg, **expr.attrs)
-    if out_t.dtype != "int32":
-        # Cast back to output dtype to preserve input dtype == output dtype 
for AvgPool2d.
-        out = relay.op.clip(out, a_min=np.iinfo(out_t.dtype).min, 
a_max=np.iinfo(out_t.dtype).max)
-        out = relay.op.cast(out, out_t.dtype)
-    return [out, TensorAffineType(out_t.scale, out_t.zero_point, out_t.dtype, 
out_t.axis)]
-
-
 @register_fake_quantization_to_integer("nn.global_avg_pool2d")
 def global_avgpool2d(expr, type_map):
     """Rewrite a global_avgpool op"""
diff --git a/python/tvm/topi/hexagon/compute_poolarea.py 
b/python/tvm/topi/hexagon/compute_poolarea.py
new file mode 100644
index 0000000000..6ba50c4a96
--- /dev/null
+++ b/python/tvm/topi/hexagon/compute_poolarea.py
@@ -0,0 +1,143 @@
+# 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.
+# pylint: disable=invalid-name, unused-variable, unused-argument, 
too-many-locals
+
+"""Compute PoolArea size which is used to exclude the zero-padding elements in 
the averaging
+   calculation.
+"""
+
+from tvm import te, tir
+
+
+def compute_PoolArea(i, j, ih, iw, kh, kw, sh, sw, dh, dw, pad_top, pad_left):
+    """
+    Parameters
+    ----------
+    i,j:
+        index of output tensor along H and W axis
+        This is equal to the starting point of the sliding window for which 
the average is computed
+    ih, iw:
+        input data size along H and W axis
+    kh, kw:
+        Kernel size along H and W axis
+    sh, sw:
+        Stride size along H and W axis
+    dh, dw:
+        Dilation size along H and W axis
+    pad_top, pad_left:
+        Pad size on Top and left side of input data
+
+    # PoolArea refers to the area of that portion of each sliding window which 
only includes
+    # the input data and not the padded area.
+
+    # Motivation: The following example shows the location of the first 
sliding window (at i=0, j=0)
+    # on a 6*6 array, with kernel=[3,3] and padding=[1, 1, 1, 1].
+    # The input data elements are shown with (X) and padding data with (0).
+    # As shown, the number of non-padding elements that should be used for 
computing
+    # the average of values inside this window is 4, while the windows area is 
3*3=9.
+    # To compute the PoolArea, we have to move the top/left edge of the window 
down/right
+    # to exclude zero-padding elements. The edge adjustment can be formulated 
as
+    #    top_edge = max(i , pad_top)
+    #    left_edge= max(j , pad_left)
+    # Note that pad_top and pad_left represent point 0 of the input data along 
i and j direction.
+    # In this example, bottom_edge and right_edge of the PoolArea do not need 
any adjustment,
+    # because there is no padding data on those side of the window.
+    # However, as we slide the window down and to the right, the window might 
go
+    # beyond the input data boundaries (ih and iw). In these cases, 
bottom/right edge should be
+    # moved up/left to be located inside the input data.
+    # This can be formulated as
+    #    bottom_edge = min(i + kh, ih + pad_top)
+    #    left_edge   = min(j + kw, iw + pad_left)
+    # Having all the edges,
+    #    PoolArea = (bottom_edge - top_edge) * (right_edge - left_edge)
+
+    #    _______
+    #    |0 0 0|0 0 0 0 0                         0 0 0 0 0 0 0 0
+    #    |     |                                 _______
+    #    |0 X X|X X X X 0                        |0 X X|X X X X 0
+    #    |     |                                 |     |
+    #    |0 X X|X X X X 0        ====>           |0 X X|X X X X 0
+    #    |_____|                                 |_____|
+    #    0 X X X X X X 0                          0 X X X X X X 0
+    #    0 X X X X X X 0                          0 X X X X X X 0
+    #    0 X X X X X X 0                          0 X X X X X X 0
+    #    0 X X X X X X 0                          0 X X X X X X 0
+    #    0 0 0 0 0 0 0 0                          0 0 0 0 0 0 0 0
+
+
+    # The above equations are derived under the assumption of having default 
value (1)
+    # for stride and dilation. However, we need to expand them to support 
non-default
+    # stride and dilation values.
+    # Stride impacts the starting location of the sliding windows, so i and j 
should be
+    # replaced by (i * sh) and j by (j * sw) in the equations.
+    # Dilation changes the window size, making k kernel elements scattered 
into a d*(k - 1) + 1
+    # window.
+    # Non-1 dilation means that, we need to divide the adjusted window size by 
the dilation value
+    # to find out how many kernel elements inside the sliding window are 
inside the input data
+    # boundaries:
+    #    top_edge= max(i * sh , pad_top)
+    #    left_edge= max(j * sw , pad_left)
+    #    bottom_edge = min(i * sh + (kh - 1) * dh + 1, ih + pad_top)
+    #    left_edge   = min(j * sw + (kw - 1) * dw + 1, data_w + pad_left)
+    #    PoolArea = ceil_div((bottom_edge - top_edge), dh) * 
ceil_div((right_edge - left_edge), dw)
+    #
+    # Finally, we need to address one corner case related to the non-default 
dilation:
+    # Consider the following example along W axis, where iw = 3, kw = 3 and dw 
= 2.
+    # The first figure on the left shows the sliding window of size 5 starting 
at index 0,
+    # and the first figure on the right shows the same example with sliding 
window at index 1.
+    # The second row of figures show the PoolArea after adjusting the edges
+    # (both left_edge - right_edge = 3)
+    # The third row of figures show the location of dialated kernel points(*).
+    # As shown, although the distance between left and right edge in both 
cases is 3 and
+    # dilation is 2 and ceil_div(3,2)=2, the right PoolArea only includes 1 
kernel point.
+
+    #  Sliding Window:                       |0 0 X X X |0                     
    0 |0 X X X  0|
+    #  PoolArea(after edge adjustment):       0 0|X X X |0                     
    0  0|X X X| 0
+    #  location of dilated kernel points:     * 0|* X * |0                     
    0  *|X * X| 0
+    #  PoolArea (dilated_point_aware):        * 0|* X * |0                     
    0  * X|* X| 0
+
+    # To address this issue, instead of moving the left_edge to bring it just 
inside the input
+    # data boundary, we should move the edge to the right untill we get to the 
first dilated kernel
+    # point inside the input data boundary.
+    # The third row of figures shows how this row adjustment can solve the 
problem.
+    # So the problem is reduced to finding the the first dilated kernel point 
inside the data
+    # boundary.# For that, we can find the number of dialted points which are 
mapped to the padded
+    # area and find the location of the next one which should be inside the 
input data:
+    #    num_of_prev_points = (pad_top - i * sh - 1) // dh
+    #    next_point_index = i * sh + (num_prev_points + 1) * dh
+    #
+    # With that, Top_edge and left_edge can be reformulated as:
+    #    if i*sh - pad_top < 0:
+    #        top_edge = i * sh + ((pad_top - i * sh - 1) // dh + 1) * dh
+    #    else:
+    #        top_edge = i * sh
+    #
+    #    if j * sw - pad_left < 0:
+    #        left_edge = j * sw + ((pad_left - j * sw - 1) // dw + 1) * dw
+    #    else:
+    #        left_edge= j * sw
+
+    """
+    top_edge = tir.if_then_else(
+        tir.all(i * sh - pad_top < 0), i * sh + ((pad_top - i * sh - 1) // dh 
+ 1) * dh, i * sh
+    )
+    bottom_edge = te.min(i * sh + (kh - 1) * dh + 1, ih + pad_top)
+    left_edge = tir.if_then_else(
+        tir.all(j * sw - pad_left < 0), j * sw + ((pad_left - j * sw - 1) // 
dw + 1) * dw, j * sw
+    )
+    right_edge = te.min(j * sw + (kw - 1) * dw + 1, iw + pad_left)
+    return -((bottom_edge - top_edge) // -dh) * -((right_edge - left_edge) // 
-dw)
diff --git a/python/tvm/topi/hexagon/qnn/__init__.py 
b/python/tvm/topi/hexagon/qnn/__init__.py
index ba7d64b6b5..f7c4502301 100644
--- a/python/tvm/topi/hexagon/qnn/__init__.py
+++ b/python/tvm/topi/hexagon/qnn/__init__.py
@@ -18,7 +18,7 @@
 """ Computes and schedules for Hexagon quantized ops """
 
 from .adaptive_avg_pool1d import *
-from .avg_pool2d import qnn_avg_pool2d_compute, qnn_avg_pool2d_schedule
+from .avg_pool2d import *
 from .conv2d_alter_op import *
 from .dense_alter_op import *
 from .dequantize import dequantize_compute, dequantize_schedule
diff --git a/python/tvm/topi/hexagon/qnn/avg_pool2d.py 
b/python/tvm/topi/hexagon/qnn/avg_pool2d.py
index 4aac15cbdc..1370ad36e4 100644
--- a/python/tvm/topi/hexagon/qnn/avg_pool2d.py
+++ b/python/tvm/topi/hexagon/qnn/avg_pool2d.py
@@ -16,64 +16,144 @@
 # under the License.
 # pylint: disable=invalid-name, unused-variable, unused-argument, 
too-many-locals
 
-""" Compute and schedule for quantized avg_pool2d op
-
-Please note the following assumptions made by the implementation:
-
-1) The input must be padded in advance to account for 'padding'. In addition,
-   both input and output must be padded as per the physical buffer layout.
-2) The current implementation assumes 'count_include_pad' to be 'True'. It can 
be
-   modified to support 'False' case but the element count for the pooling 
window
-   must be pre-computed and provided as an input to reduce the run-time 
overhead.
-3) 'padding' is ignored. It must be handled outside of the sliced op.
-4) Please note that this implementation will not work if the output includes 
any
-   physical layout related padding as it can result into out-of-bound access
-   for the input.
-"""
+""" Compute and schedule for quantized avg_pool2d op """
 
+import tvm
 from tvm import te
 from tvm import tir
-from ..utils import get_layout_transform_fn, get_fixed_point_value
+from ..utils import (
+    get_layout_transform_fn,
+    get_fixed_point_value,
+    is_scalar,
+    get_const_int_value,
+    get_const_float_value,
+)
+from ...utils import get_const_tuple
+from ...nn.utils import get_pad_tuple
+from ...nn.pad import pad
+from ..compute_poolarea import compute_PoolArea
+
+
+def saturate(x: te.Tensor, dtype: str):
+    """Saturate value for the specified data type"""
+    return te.max(te.min_value(dtype), te.min(x, te.max_value(dtype)))
 
 
-def validate_out_shape(out_shape: list, in_shape: list, kernel: list, stride: 
list, dilation: list):
-    """Validate output shape"""
-    _, oh, ow, _ = out_shape
-    _, ih, iw, _ = in_shape
+def qnn_avg_pool2d_NCHW(
+    data: te.Tensor,
+    kernel: list,
+    stride: list,
+    padding: list,
+    dilation: list,
+    count_include_pad: bool,
+    oshape: list,
+    odtype: str,
+    # quantization params:
+    input_scale: float,
+    input_zero_point: int,
+    output_scale: float,
+    output_zero_point: int,
+):
+    """Compute for quantized avg_pool2d"""
     kh, kw = kernel
+    rh = te.reduce_axis((0, kh), name="rh")
+    rw = te.reduce_axis((0, kw), name="rw")
+
+    if odtype == "uint8":
+        temp_dtype = "uint16"
+    elif odtype == "int8":
+        temp_dtype = "int16"
+    else:
+        raise RuntimeError(f"Unsupported output dtype, {odtype}'")
+
     sh, sw = stride
     dh, dw = dilation
-    if ih < (oh - 1) * sh + dh * (kh - 1) + 1:
-        raise RuntimeError("Output height is too large")
-    if iw < (ow - 1) * sw + dw * (kw - 1) + 1:
-        raise RuntimeError("Output width is too large")
 
+    scale = input_scale / output_scale
+    scale_fixed_point, rsh = get_fixed_point_value(scale, "int16")
+    corr = (output_zero_point << rsh) - input_zero_point * scale_fixed_point
 
-def saturate(x: te.Tensor, dtype: str):
-    """Saturate value for the specified data type"""
-    return te.max(te.min_value(dtype), te.min(x, te.max_value(dtype)))
+    dilated_kh = (kh - 1) * dh + 1
+    dilated_kw = (kw - 1) * dw + 1
+
+    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
+        get_const_tuple(padding), (dilated_kh, dilated_kw)
+    )
+
+    # DOPAD
+    if pad_top != 0 or pad_down != 0 or pad_left != 0 or pad_right != 0:
+        pad_before = (0, 0, pad_top, pad_left)
+        pad_after = (0, 0, pad_down, pad_right)
+        data_pad = pad(data, pad_before, pad_after, 
pad_value=input_zero_point, name="data_pad")
+    else:
+        # By definition when True, zero-padding will be included in the 
averaging calculation
+        # This is equivalent to PoolArea = (kh * kw)
+        count_include_pad = True
+        data_pad = data
+
+    Sum = te.compute(
+        oshape,
+        lambda b, c, h, w: te.sum(
+            data_pad[b, c, h * sh + dh * rh, w * sw + dw * 
rw].astype(temp_dtype), axis=[rh, rw]
+        ),
+        name="pool_sum",
+    )
+
+    if not count_include_pad:
+        # Compute PoolArea using unpadded input tensor
+        _, _, oh, ow = oshape
+        _, _, ih, iw = data.shape
 
+        PoolArea = te.compute(
+            (oh, ow),
+            lambda i, j: compute_PoolArea(i, j, ih, iw, kh, kw, sh, sw, dh, 
dw, pad_top, pad_left),
+            name="pool_area",
+        )
 
-def qnn_avg_pool2d_compute(
+        ScaleWithArea = te.compute(
+            (oh, ow),
+            lambda i, j: (scale_fixed_point // PoolArea[i, j]).astype("int32"),
+            name="scale_with_area",
+        )
+
+        Avg = te.compute(
+            oshape,
+            lambda b, c, h, w: saturate(
+                ((Sum[b, c, h, w] * ScaleWithArea[h, w]) + corr + (1 << (rsh - 
1))) >> rsh, odtype
+            ).astype(odtype),
+            name="pool_avg",
+        )
+    else:
+        ScaleWithArea = scale_fixed_point // (kh * kw)
+        Avg = te.compute(
+            oshape,
+            lambda b, c, h, w: saturate(
+                ((Sum[b, c, h, w] * ScaleWithArea) + corr + (1 << (rsh - 1))) 
>> rsh, odtype
+            ).astype(odtype),
+            name="pool_avg",
+        )
+    return Avg
+
+
+def qnn_avg_pool2d_NHWC(
     data: te.Tensor,
     kernel: list,
     stride: list,
+    padding: list,
     dilation: list,
+    count_include_pad: bool,
     oshape: list,
     odtype: str,
     # quantization params:
-    input_zero_point: int,
     input_scale: float,
-    output_zero_point: int,
+    input_zero_point: int,
     output_scale: float,
+    output_zero_point: int,
 ):
     """Compute for quantized avg_pool2d"""
     kh, kw = kernel
     rh = te.reduce_axis((0, kh), name="rh")
     rw = te.reduce_axis((0, kw), name="rw")
-    ob, oh, ow, oc = oshape
-    if isinstance(ob, int):
-        validate_out_shape(oshape, data.shape, kernel, stride, dilation)
 
     if odtype == "uint8":
         temp_dtype = "uint16"
@@ -85,86 +165,217 @@ def qnn_avg_pool2d_compute(
     sh, sw = stride
     dh, dw = dilation
 
-    PoolArea = kh * kw
-
     scale = input_scale / output_scale
     scale_fixed_point, rsh = get_fixed_point_value(scale, "int16")
-    scale_with_area = scale_fixed_point // PoolArea
     corr = (output_zero_point << rsh) - input_zero_point * scale_fixed_point
 
+    dilated_kh = (kh - 1) * dh + 1
+    dilated_kw = (kw - 1) * dw + 1
+    # Compute Area
+
+    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
+        get_const_tuple(padding), (dilated_kh, dilated_kw)
+    )
+    # DOPAD
+    if pad_top != 0 or pad_down != 0 or pad_left != 0 or pad_right != 0:
+        pad_before = (0, pad_top, pad_left, 0)
+        pad_after = (0, pad_down, pad_right, 0)
+        data_pad = pad(data, pad_before, pad_after, 
pad_value=input_zero_point, name="data_pad")
+    else:
+        # By definition when True, zero-padding will be included in the 
averaging calculation
+        # This is equivalent to PoolArea = (kh * kw)
+        count_include_pad = True
+        data_pad = data
+
     Sum = te.compute(
         oshape,
         lambda b, h, w, c: te.sum(
-            data[b, h * sh + dh * rh, w * sw + dw * rw, c].astype(temp_dtype), 
axis=[rh, rw]
+            data_pad[b, h * sh + dh * rh, w * sw + dw * rw, 
c].astype(temp_dtype), axis=[rh, rw]
         ),
-        name="sum",
+        name="pool_sum",
     )
 
-    Avg = te.compute(
-        oshape,
-        lambda b, h, w, c: saturate(
-            ((Sum[b, h, w, c] * scale_with_area) + corr) >> rsh, odtype
-        ).astype(odtype),
-        name="avg",
-    )
+    if not count_include_pad:
+        # Compute PoolArea using unpadded input tensor
+        _, oh, ow, _ = oshape
+        _, ih, iw, _ = data.shape
+
+        PoolArea = te.compute(
+            (oh, ow),
+            lambda i, j: compute_PoolArea(i, j, ih, iw, kh, kw, sh, sw, dh, 
dw, pad_top, pad_left),
+            name="pool_area",
+        )
+
+        ScaleWithArea = te.compute(
+            (oh, ow),
+            lambda i, j: tir.if_then_else(
+                tir.all(PoolArea[i, j] > 0),
+                (scale_fixed_point // PoolArea[i, j]).astype("int32"),
+                0,
+            ),
+            name="scale_with_area",
+        )
+
+        Avg = te.compute(
+            oshape,
+            lambda b, h, w, c: saturate(
+                ((Sum[b, h, w, c] * ScaleWithArea[h, w]) + corr + (1 << (rsh - 
1))) >> rsh, odtype
+            ).astype(odtype),
+            name="pool_avg",
+        )
+    else:
+        ScaleWithArea = scale_fixed_point // (kh * kw)
+        Avg = te.compute(
+            oshape,
+            lambda b, h, w, c: saturate(
+                ((Sum[b, h, w, c] * ScaleWithArea) + corr + (1 << (rsh - 1))) 
>> rsh, odtype
+            ).astype(odtype),
+            name="pool_avg",
+        )
+
     return Avg
 
 
-def schedule_nhwc_8h8w32c(outs: te.Tensor, ins: te.Tensor, output_layout: str, 
input_layout: str):
-    """Schedule for input and output layout nhwc-8h8w32c"""
+def qnn_avg_pool2d_wrapper_compute_NCHW(
+    data: te.Tensor,
+    kernel: list,
+    stride: list,
+    padding: list,
+    dilation: list,
+    count_include_pad: bool,
+    oshape: list,
+    odtype: str,
+    # quantization params:
+    input_scale: float,
+    input_zero_point: int,
+    output_scale: float,
+    output_zero_point: int,
+):
+    """Extract qnn params"""
+    if (
+        is_scalar(input_scale)
+        and is_scalar(output_scale)
+        and is_scalar(input_zero_point)
+        and is_scalar(output_zero_point)
+    ):
+        iscale = get_const_float_value(input_scale)
+        oscale = get_const_float_value(output_scale)
+        izero_point = get_const_int_value(input_zero_point)
+        ozero_point = get_const_int_value(output_zero_point)
+        return qnn_avg_pool2d_NCHW(
+            data,
+            kernel,
+            stride,
+            padding,
+            dilation,
+            count_include_pad,
+            oshape,
+            odtype,
+            iscale,
+            izero_point,
+            oscale,
+            ozero_point,
+        )
+    else:
+        raise RuntimeError("quantization parameters should be scalar tensors")
+
+
+def qnn_avg_pool2d_wrapper_compute_NHWC(
+    data: te.Tensor,
+    kernel: list,
+    stride: list,
+    padding: list,
+    dilation: list,
+    count_include_pad: bool,
+    oshape: list,
+    odtype: str,
+    # quantization params:
+    input_scale: float,
+    input_zero_point: int,
+    output_scale: float,
+    output_zero_point: int,
+):
+    """Extract qnn params"""
+    if (
+        is_scalar(input_scale)
+        and is_scalar(output_scale)
+        and is_scalar(input_zero_point)
+        and is_scalar(output_zero_point)
+    ):
+        iscale = get_const_float_value(input_scale)
+        oscale = get_const_float_value(output_scale)
+        izero_point = get_const_int_value(input_zero_point)
+        ozero_point = get_const_int_value(output_zero_point)
+        return qnn_avg_pool2d_NHWC(
+            data,
+            kernel,
+            stride,
+            padding,
+            dilation,
+            count_include_pad,
+            oshape,
+            odtype,
+            iscale,
+            izero_point,
+            oscale,
+            ozero_point,
+        )
+    else:
+        raise RuntimeError("quantization parameters should be scalar tensors")
+
+
+def schedule_qnn_avg_pool2d(outs):
+    """Schedule for qnn.avg_pool2d
+    Parameters
+    ----------
+    outs: Array of Tensor
+          The computation graph description of qnn.avg_pool2d
+          in the format of an array of tensors.
+
+    Returns
+    -------
+    sch: Schedule
+        The computation schedule for the op.
+    """
+    outs = [outs] if isinstance(outs, tvm.te.tensor.Tensor) else outs
+    s = tvm.te.create_schedule([x.op for x in outs])
+    tvm.te.schedule.AutoInlineInjective(s)
+    return s
+
+
+def schedule_8h8w32c(outs: te.Tensor, ins: te.Tensor, output_layout: str, 
input_layout: str):
+    """Schedule for input and output layout 8h8w32c"""
+
     func = te.create_prim_func([ins, outs])
     s = tir.Schedule(func)
-    Sum = s.get_block("sum")
-    Avg = s.get_block("avg")
-
+    Sum = s.get_block("pool_sum")
+    Avg = s.get_block("pool_avg")
+    mem_scope = "global.vtcm"
+    sum_read = s.cache_read(Sum, 0, mem_scope)
+    avg_read = s.cache_read(Avg, 0, mem_scope)
+    avg_write = s.cache_write(Avg, 0, mem_scope)
     input_transform_fn = get_layout_transform_fn(input_layout)
     output_transform_fn = get_layout_transform_fn(output_layout)
-    s.transform_layout(Sum, ("read", 0), input_transform_fn)
-    s.transform_layout(Avg, ("write", 0), output_transform_fn)
-
-    # Schedule 'Avg'
-    # Split and reorder the axes to iterate over the output tensor chunks.
-    # Each chunk consists for 2048 bytes with 32 channels being the fastest
-    # changing axis, followed by 8 width and then 8 height.
-    # The width is split by a factor of 4 and then fused with 32 channels
-    # to provide full vector length of data for the output tensor chunks.
-    # NOTE: These schedules are a work in progress and may require
-    # adjustments in future as some of the missing features for 2-d tensors
-    # become available.
-    n, h, w, c = s.get_loops(Avg)
-    ho, hi = s.split(h, [None, 8])
-    wo, wi = s.split(w, [None, 8])
-    wio, wii = s.split(wi, [None, 4])
-    co, ci = s.split(c, [None, 32])
-    s.reorder(n, ho, wo, co, hi, wio, wii, ci)
-    wii_ci = s.fuse(wii, ci)
-    s.vectorize(wii_ci)
-
-    # Schedule 'Sum'
-    s.compute_at(Sum, wio)
-    Sum_axis = s.get_loops(Sum)
-    # Compute for 'Sum' includes reduction along height and width. The axes
-    # are being reordered so that 4 width and 32 channels become the
-    # inner-most loops which then can be fused and vectorized. However,
-    # vectorization of the 2-d tensors doesn't work when reduction is
-    # involved and requires codegen support that is yet to be added.
-    s.reorder(Sum_axis[-2], Sum_axis[-1], Sum_axis[-4], Sum_axis[-3])
-    ci_wii = s.fuse(Sum_axis[-4], Sum_axis[-3])
-    # s.vectorize(ci_wii) # Doesn't work
+    s.transform_layout(Sum, ("read", 0), input_transform_fn, pad_value=0)
+    s.transform_layout(Avg, ("read", 0), input_transform_fn, pad_value=0)
+    s.transform_layout(Avg, ("write", 0), output_transform_fn, pad_value=0)
     return s
 
 
-def schedule_n11c_2048c(outs: te.Tensor, ins: te.Tensor, output_layout: str, 
input_layout: str):
-    """Schedule for output layout: n11c-2048c, input layout: nhwc-8h8w32c"""
+def schedule_2048c(outs: te.Tensor, ins: te.Tensor, output_layout: str, 
input_layout: str):
+    """Schedule for output layout: 2048c, input layout: 8h8w32c"""
     func = te.create_prim_func([ins, outs])
     s = tir.Schedule(func)
-    Sum = s.get_block("sum")
-    Avg = s.get_block("avg")
+    Sum = s.get_block("pool_sum")
+    Avg = s.get_block("pool_avg")
 
+    mem_scope = "global.vtcm"
+    sum_read = s.cache_read(Sum, 0, mem_scope)
+    avg_write = s.cache_write(Avg, 0, mem_scope)
     input_transform_fn = get_layout_transform_fn(input_layout)
     output_transform_fn = get_layout_transform_fn(output_layout)
-    s.transform_layout(Sum, ("read", 0), input_transform_fn)
-    s.transform_layout(Avg, ("write", 0), output_transform_fn)
+    s.transform_layout(Sum, ("read", 0), input_transform_fn, pad_value=0)
+    s.transform_layout(Avg, ("write", 0), output_transform_fn, pad_value=0)
 
     # Schedule 'Avg'
     # Split and reorder the axes to iterate over the output tensor chunks.
@@ -173,7 +384,13 @@ def schedule_n11c_2048c(outs: te.Tensor, ins: te.Tensor, 
output_layout: str, inp
     # NOTE: These schedules are a work in progress and may require
     # adjustments in future as some of the missing features for 2-d tensors
     # become available.
-    n, h, w, c = s.get_loops(Avg)
+
+    if output_layout == "n11c-2048c-2d":
+        _, _, _, c = s.get_loops(Avg)
+    else:
+        _, c, _, _ = s.get_loops(Avg)
+
+    # n, h, w, c = s.get_loops(Avg)
     co, ci = s.split(c, [None, 2048])
     cio, cii = s.split(ci, [None, 128])
     s.vectorize(cii)
@@ -191,15 +408,10 @@ def schedule_n11c_2048c(outs: te.Tensor, ins: te.Tensor, 
output_layout: str, inp
 
 
 def qnn_avg_pool2d_schedule(outs: te.Tensor, ins: te.Tensor, output_layout: 
str, input_layout: str):
-    """Quantized avg_pool2d schedule
+    """Quantized avg_pool2d schedule"""
+    if output_layout == "nhwc-8h8w32c-2d" or output_layout == 
"nchw-8h8w32c-2d":
+        return schedule_8h8w32c(outs, ins, output_layout, input_layout)
+    if output_layout == "n11c-2048c-2d" or output_layout == "nc11-2048c-2d":
+        return schedule_2048c(outs, ins, output_layout, input_layout)
 
-    NOTE: This schedule assumes that both input and output tensors are in the 
form of
-    2d discontiguous buffer and data is already arranged as per the input and 
output layout
-    respectively.
-
-    """
-    if output_layout == "nhwc-8h8w32c-2d":
-        return schedule_nhwc_8h8w32c(outs, ins, output_layout, input_layout)
-    if output_layout == "n11c-2048c-2d":
-        return schedule_n11c_2048c(outs, ins, output_layout, input_layout)
     raise RuntimeError(f"Unexpected layout '{output_layout}'")
diff --git a/python/tvm/topi/hexagon/qnn/nn.py 
b/python/tvm/topi/hexagon/qnn/nn.py
index e60314b827..286c33f53f 100644
--- a/python/tvm/topi/hexagon/qnn/nn.py
+++ b/python/tvm/topi/hexagon/qnn/nn.py
@@ -22,8 +22,14 @@ import numpy as np
 
 import tvm
 from tvm import te, topi
-from ..utils import saturate, get_fixed_point_value
-from ...utils import get_const_tuple, get_const_int, get_const_float
+from ..utils import (
+    saturate,
+    is_scalar,
+    get_const_int_value,
+    get_const_float_value,
+    get_fixed_point_value,
+)
+from ...utils import get_const_tuple
 from ...nn.utils import get_pad_tuple
 from ...nn.pad import pad
 from ... import tag, nn
@@ -38,27 +44,6 @@ def clip_cast(val, dtype):
     return te.max(tvm.te.min(val, const_max), const_min).astype(dtype)
 
 
-# Return True if given expression is scalar constant value.
-def is_scalar(expr):
-    if isinstance(expr, te.Tensor):
-        return expr.ndim == 0 and (isinstance(expr.op.body[0], 
(tvm.tir.FloatImm, tvm.tir.IntImm)))
-    return isinstance(expr, (tvm.tir.FloatImm, tvm.tir.IntImm))
-
-
-def get_const_int_value(expr):
-    if isinstance(expr, te.Tensor):
-        assert isinstance(expr.op.body[0], tvm.tir.IntImm)
-        return expr.op.body[0].value
-    return get_const_int(expr)
-
-
-def get_const_float_value(expr):
-    if isinstance(expr, te.Tensor):
-        assert isinstance(expr.op.body[0], tvm.tir.FloatImm)
-        return expr.op.body[0].value
-    return get_const_float(expr)
-
-
 def get_qnn_param(param, indices, axis):
     # Account scalar and 1D quantization parameters:
     if len(param.shape) == 0:
@@ -68,11 +53,7 @@ def get_qnn_param(param, indices, axis):
     return param[param_idx]
 
 
-def subtract_zero_point(
-    tensor: te.Tensor,
-    zero_point: Union[te.Tensor, tvm.tir.IntImm],
-    name: str,
-):
+def subtract_zero_point(tensor: te.Tensor, zero_point: Union[te.Tensor, 
tvm.tir.IntImm], name: str):
     """
     Subtract zero point from given tensor. If zero point is scalar constant 
and is equal to 0, then
     it can be optimized and return tensor as it is.
@@ -590,10 +571,7 @@ def qnn_conv2d(  # Conv2d inputs
         oshape,
         lambda n, oc, oh, ow: te.sum(
             data_pad[
-                n,
-                ic,
-                oh * height_stride + kh * dilation_h,
-                ow * width_stride + kw * dilation_w,
+                n, ic, oh * height_stride + kh * dilation_h, ow * width_stride 
+ kw * dilation_w
             ].astype("int32")
             * weight[oc, ic, kh, kw].astype("int32"),
             axis=[ic, kh, kw],
@@ -777,10 +755,7 @@ def qnn_depthwise_conv2d(  # Conv2d inputs
         oshape,
         lambda n, oc, oh, ow: te.sum(
             data_pad[
-                n,
-                oc,
-                oh * height_stride + kh * dilation_h,
-                ow * width_stride + kw * dilation_w,
+                n, oc, oh * height_stride + kh * dilation_h, ow * width_stride 
+ kw * dilation_w
             ].astype("int32")
             * te.subtract(weight[oc, 0, kh, kw], 
kernel_zero_point).astype("int32"),
             axis=[kh, kw],
diff --git a/python/tvm/topi/hexagon/slice_ops/__init__.py 
b/python/tvm/topi/hexagon/slice_ops/__init__.py
index 46ae0c5320..b38dd5ecb3 100644
--- a/python/tvm/topi/hexagon/slice_ops/__init__.py
+++ b/python/tvm/topi/hexagon/slice_ops/__init__.py
@@ -17,7 +17,7 @@
 
 """ Computes and Schedules for Hexagon slice ops. """
 
-from .avg_pool2d import avg_pool2d_compute, avg_pool2d_schedule
+from .avg_pool2d import avg_pool2d_NHWC, avg_pool2d_NCHW, avg_pool2d_schedule
 from .max_pool2d import max_pool2d_compute, max_pool2d_STIR_schedule
 from .add_subtract_multiply import *
 from .argmax import argmax_compute, argmax_schedule
diff --git a/python/tvm/topi/hexagon/slice_ops/avg_pool2d.py 
b/python/tvm/topi/hexagon/slice_ops/avg_pool2d.py
index bf6d57b8f7..0c7b00e287 100644
--- a/python/tvm/topi/hexagon/slice_ops/avg_pool2d.py
+++ b/python/tvm/topi/hexagon/slice_ops/avg_pool2d.py
@@ -16,128 +16,214 @@
 # under the License.
 # pylint: disable=invalid-name, unused-variable, unused-argument, 
too-many-locals, pointless-exception-statement
 
-""" Compute and schedule for avg_pool2d slice op
-
-Please note the following assumptions made by the implementation:
-
-1) The input must be padded in advance to account for 'padding'. In addition,
-   both input and output must be padded as per the physical buffer layout.
-2) The current implementation assumes 'count_include_pad' to be 'True'. It can 
be
-   modified to support 'False' case but the element count for the pooling 
window
-   must be pre-computed and provided as an input to reduce the run-time 
overhead.
-3) 'padding' is ignored. It must be handled outside of the sliced op.
-4) Please note that this implementation will not work if the output includes 
any
-   physical layout related padding as it can result into out-of-bound access
-   for the input.
-"""
+""" Compute and schedule for avg_pool2d slice op """
 
 from tvm import te
 from tvm import tir
 from ..utils import get_layout_transform_fn
+from ...utils import get_const_tuple
+from ...nn.utils import get_pad_tuple
+from ...nn.pad import pad
+from ..compute_poolarea import compute_PoolArea
 
 
-def validate_out_shape(out_shape, in_shape, kernel, stride, dilation):
-    """Validate output shape"""
-    _, oh, ow, _ = out_shape
-    _, ih, iw, _ = in_shape
+def avg_pool2d_NCHW(
+    data, kernel, stride, padding, dilation, count_include_pad, oshape, 
odtype="float16"
+):
+    """avg_pool2d compute"""
+    if odtype != "float16":
+        raise RuntimeError(f"Unsupported output dtype '{odtype}'")
     kh, kw = kernel
+    rh = te.reduce_axis((0, kh), name="rh")
+    rw = te.reduce_axis((0, kw), name="rw")
     sh, sw = stride
     dh, dw = dilation
-    if ih < (oh - 1) * sh + dh * (kh - 1) + 1:
-        raise RuntimeError("Output height is too large")
-    if iw < (ow - 1) * sw + dw * (kw - 1) + 1:
-        raise RuntimeError("Output width is too large")
 
+    dilated_kh = (kh - 1) * dh + 1
+    dilated_kw = (kw - 1) * dw + 1
+
+    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
+        get_const_tuple(padding), (dilated_kh, dilated_kw)
+    )
+
+    # DOPAD
 
-def avg_pool2d_compute(A, kernel, stride, dilation, oshape, odtype="float16"):
+    if pad_top != 0 or pad_down != 0 or pad_left != 0 or pad_right != 0:
+        pad_before = (0, 0, pad_top, pad_left)
+        pad_after = (0, 0, pad_down, pad_right)
+        data_pad = pad(data, pad_before, pad_after, name="data_pad")
+    else:
+        # By definition when True, zero-padding will be included in the 
averaging calculation
+        # This is equivalent to PoolArea = (kh * kw)
+        count_include_pad = True
+        data_pad = data
+
+    Sum = te.compute(
+        oshape,
+        lambda b, c, h, w: te.sum(
+            data_pad[b, c, h * sh + dh * rh, w * sw + dw * 
rw].astype("float32"), axis=[rh, rw]
+        ),
+        name="pool_sum",
+    )
+
+    if not count_include_pad:
+        # Compute PoolArea using unpadded input tensor
+        _, _, oh, ow = oshape
+        _, _, ih, iw = data.shape
+
+        PoolArea = te.compute(
+            (oh, ow),
+            lambda i, j: compute_PoolArea(i, j, ih, iw, kh, kw, sh, sw, dh, 
dw, pad_top, pad_left),
+            name="pool_area",
+        )
+
+        InvArea = te.compute(
+            (oh, ow),
+            lambda i, j: tir.if_then_else(
+                tir.all(PoolArea[i, j] > 0), (float(1) / PoolArea[i, j]), 0
+            ),
+            name="inverse_area",
+        )
+
+        Avg = te.compute(
+            oshape,
+            lambda b, c, h, w: (Sum[b, c, h, w] * InvArea[h, 
w]).astype(odtype),
+            name="pool_avg",
+        )
+    else:
+        InvArea = float(1) / (kh * kw)
+        Avg = te.compute(
+            oshape, lambda b, c, h, w: (Sum[b, c, h, w] * 
InvArea).astype(odtype), name="pool_avg"
+        )
+
+    return Avg
+
+
+def avg_pool2d_NHWC(
+    data, kernel, stride, padding, dilation, count_include_pad, oshape, 
odtype="float16"
+):
     """avg_pool2d compute"""
     if odtype != "float16":
-        RuntimeError(f"Unsupported output dtype '{odtype}'")
+        raise RuntimeError(f"Unsupported output dtype '{odtype}'")
     kh, kw = kernel
     rh = te.reduce_axis((0, kh), name="rh")
     rw = te.reduce_axis((0, kw), name="rw")
-    ob, oh, ow, oc = oshape
-    if isinstance(ob, int):
-        validate_out_shape(oshape, A.shape, kernel, stride, dilation)
 
     sh, sw = stride
     dh, dw = dilation
     InvArea = float(1) / (kh * kw)
 
+    dilated_kh = (kh - 1) * dh + 1
+    dilated_kw = (kw - 1) * dw + 1
+
+    pad_top, pad_left, pad_down, pad_right = get_pad_tuple(
+        get_const_tuple(padding), (dilated_kh, dilated_kw)
+    )
+
+    # DOPAD
+    if pad_top != 0 or pad_down != 0 or pad_left != 0 or pad_right != 0:
+        pad_before = (0, pad_top, pad_left, 0)
+        pad_after = (0, pad_down, pad_right, 0)
+        data_pad = pad(data, pad_before, pad_after, name="data_pad")
+    else:
+        # By definition when True, zero-padding will be included in the 
averaging calculation
+        # This is equivalent to PoolArea = (kh * kw)
+        count_include_pad = True
+        data_pad = data
+
     Sum = te.compute(
         oshape,
         lambda b, h, w, c: te.sum(
-            A[b, h * sh + dh * rh, w * sw + dw * rw, c].astype("float32"), 
axis=[rh, rw]
+            data_pad[b, h * sh + dh * rh, w * sw + dw * rw, 
c].astype("float32"), axis=[rh, rw]
         ),
-        name="sum",
-    )
-    Avg = te.compute(
-        oshape, lambda b, h, w, c: (Sum[b, h, w, c] * 
InvArea).astype(A.dtype), name="avg"
+        name="pool_sum",
     )
+
+    if not count_include_pad:
+        # Compute PoolArea using unpadded input tensor
+        _, oh, ow, _ = oshape
+        _, ih, iw, _ = data.shape
+
+        PoolArea = te.compute(
+            (oh, ow),
+            lambda i, j: compute_PoolArea(i, j, ih, iw, kh, kw, sh, sw, dh, 
dw, pad_top, pad_left),
+            name="pool_area",
+        )
+
+        InvArea = te.compute(
+            (oh, ow),
+            lambda i, j: tir.if_then_else(
+                tir.all(PoolArea[i, j] > 0), (float(1) / PoolArea[i, j]), 0
+            ),
+            name="inverse_area",
+        )
+
+        Avg = te.compute(
+            oshape,
+            lambda b, h, w, c: (Sum[b, h, w, c] * InvArea[h, 
w]).astype(odtype),
+            name="pool_avg",
+        )
+    else:
+        InvArea = float(1) / (kh * kw)
+        Avg = te.compute(
+            oshape, lambda b, h, w, c: (Sum[b, h, w, c] * 
InvArea).astype(odtype), name="pool_avg"
+        )
+
     return Avg
 
 
-def schedule_nhwc_8h2w32c2w(outs, ins, output_layout: str, input_layout: str):
-    """Schedule for input and output layout nhwc-8h2w32c2w"""
+def schedule_8h2w32c2w(outs, ins, output_layout: str, input_layout: str):
+    """Schedule for input and output layout 8h2w32c2w"""
     func = te.create_prim_func([ins, outs])
+    print(func)
     s = tir.Schedule(func)
-    Sum = s.get_block("sum")
-    Avg = s.get_block("avg")
+    Sum = s.get_block("pool_sum")
+    Avg = s.get_block("pool_avg")
 
+    mem_scope = "global.vtcm"
+    sum_read = s.cache_read(Sum, 0, mem_scope)
+    avg_write = s.cache_write(Avg, 0, mem_scope)
     input_transform_fn = get_layout_transform_fn(input_layout)
     output_transform_fn = get_layout_transform_fn(output_layout)
-    s.transform_layout(Sum, ("read", 0), input_transform_fn)
-    s.transform_layout(Avg, ("write", 0), output_transform_fn)
-
-    # Schedule 'Avg'
-    n, h, w, c = s.get_loops(Avg)
-    ho, hi = s.split(h, [None, 8])
-    wo, wi = s.split(w, [None, 4])
-    wio, wii = s.split(wi, [None, 2])
-    co, ci = s.split(c, [None, 32])
-    s.reorder(n, ho, wo, co, hi, wio, ci, wii)
-    ci_wii = s.fuse(ci, wii)
-    s.vectorize(ci_wii)
-
-    # Schedule 'Sum'
-    s.compute_at(Sum, wio)
-    Sum_axis = s.get_loops(Sum)
-    s.reorder(Sum_axis[-2], Sum_axis[-1], Sum_axis[-4], Sum_axis[-3])
-    ci_wii = s.fuse(Sum_axis[-4], Sum_axis[-3])
-    # s.vectorize(ci_wii) # Doesn't work
+    s.transform_layout(Sum, ("read", 0), input_transform_fn, pad_value=0.0)
+    s.transform_layout(Avg, ("write", 0), output_transform_fn, pad_value=0.0)
     return s
 
 
-def schedule_n11c_1024c(outs, ins, output_layout: str, input_layout: str):
-    """Schedule for output layout: n11c-1024c, input layout: nhwc-8h2w32c2w"""
+def schedule_1024c(outs, ins, output_layout: str, input_layout: str):
+    """Schedule for output layout: 1024c, input layout: 8h2w32c2w"""
     func = te.create_prim_func([ins, outs])
     s = tir.Schedule(func)
-    Sum = s.get_block("sum")
-    Avg = s.get_block("avg")
+    Sum = s.get_block("pool_sum")
+    Avg = s.get_block("pool_avg")
 
+    mem_scope = "global.vtcm"
+    sum_read = s.cache_read(Sum, 0, mem_scope)
+    avg_write = s.cache_write(Avg, 0, mem_scope)
     input_transform_fn = get_layout_transform_fn(input_layout)
     output_transform_fn = get_layout_transform_fn(output_layout)
-    s.transform_layout(Sum, ("read", 0), input_transform_fn)
-    s.transform_layout(Avg, ("write", 0), output_transform_fn)
+    s.transform_layout(Sum, ("read", 0), input_transform_fn, pad_value=0.0)
+    s.transform_layout(Avg, ("write", 0), output_transform_fn, pad_value=0.0)
 
     # Schedule 'Avg'
-    n, h, w, c = s.get_loops(Avg)
-    co, ci = s.split(c, [None, 1024])
+    if output_layout == "n11c-1024c-2d":
+        n, h, w, c = s.get_loops(Avg)
+    else:
+        n, c, h, w = s.get_loops(Avg)
+    _, ci = s.split(c, [None, 1024])
     cio, cii = s.split(ci, [None, 64])
     s.vectorize(cii)
 
     # Schedule 'Sum'
-    s.compute_at(Sum, cio)
     Sum_axis = s.get_loops(Sum)
     s.reorder(Sum_axis[-2], Sum_axis[-1], Sum_axis[-3])
-    # s.vectorize(Sum_axis[-3]) # Doesn't work
     return s
 
 
 def avg_pool2d_schedule(outs, ins, output_layout: str, input_layout: str):
     """avg_pool2d schedule"""
-    if output_layout == "nhwc-8h2w32c2w-2d":
-        return schedule_nhwc_8h2w32c2w(outs, ins, output_layout, input_layout)
-    if output_layout == "n11c-1024c-2d":
-        return schedule_n11c_1024c(outs, ins, output_layout, input_layout)
+    if output_layout == "nhwc-8h2w32c2w-2d" or output_layout == 
"nchw-8h2w32c2w-2d":
+        return schedule_8h2w32c2w(outs, ins, output_layout, input_layout)
+    if output_layout == "n11c-1024c-2d" or output_layout == "nc11-1024c-2d":
+        return schedule_1024c(outs, ins, output_layout, input_layout)
     raise RuntimeError(f"Unexpected layout '{output_layout}'")
diff --git a/python/tvm/topi/hexagon/utils.py b/python/tvm/topi/hexagon/utils.py
index f017aaebbd..aa1af5de43 100644
--- a/python/tvm/topi/hexagon/utils.py
+++ b/python/tvm/topi/hexagon/utils.py
@@ -24,10 +24,30 @@ import struct
 from typing import Dict, Tuple, Union
 
 import tvm
-from tvm import IRModule, te
+from tvm import IRModule, te, tir
 from tvm.tir import IndexMap, PrimFunc
 
 
+def is_scalar(expr):
+    if isinstance(expr, te.Tensor):
+        return expr.ndim == 0 and (isinstance(expr.op.body[0], (tir.FloatImm, 
tir.IntImm)))
+    return isinstance(expr, (tir.FloatImm, tir.IntImm))
+
+
+def get_const_int_value(expr):
+    if isinstance(expr, te.Tensor):
+        assert isinstance(expr.op.body[0], tir.IntImm)
+        return expr.op.body[0].value
+    return tvm.topi.utils.get_const_int(expr)
+
+
+def get_const_float_value(expr):
+    if isinstance(expr, te.Tensor):
+        assert isinstance(expr.op.body[0], tir.FloatImm)
+        return expr.op.body[0].value
+    return tvm.topi.utils.get_const_float(expr)
+
+
 def n11c_1024c_2d(n, h, w, c):
     """Return index map for n11c_1024 2d layout"""
     return [n, h, w, c // 1024, IndexMap.AXIS_SEPARATOR, c % 1024]
@@ -38,6 +58,11 @@ def n11c_1024c_1d(n, h, w, c):
     return [n, h, w, c // 1024, c % 1024]
 
 
+def nc11_1024c_2d(n, c, h, w):
+    """Return index map for nc11_1024 2d layout"""
+    return [n, c // 1024, IndexMap.AXIS_SEPARATOR, c % 1024, h, w]
+
+
 def nhwc_8h2w32c2w_2d(n, h, w, c):
     """Return index map for nhwc_8h2w32c2w 2d layout"""
     return [n, h // 8, w // 4, c // 32, IndexMap.AXIS_SEPARATOR, h % 8, (w % 
4) // 2, c % 32, w % 2]
@@ -48,6 +73,11 @@ def nhwc_8h2w32c2w_1d(n, h, w, c):
     return [n, h // 8, w // 4, c // 32, h % 8, (w % 4) // 2, c % 32, w % 2]
 
 
+def nchw_8h2w32c2w_2d(n, c, h, w):
+    """Return index map for nchw_8h2w32c2w 2d layout"""
+    return [n, c // 32, h // 8, w // 4, IndexMap.AXIS_SEPARATOR, h % 8, (w % 
4) // 2, c % 32, w % 2]
+
+
 def nhw_32h16w_2d(n, h, w):
     """Return index map for nhw_32h16w 2d layout"""
     return [n, h // 32, w // 16, IndexMap.AXIS_SEPARATOR, h % 32, w % 16]
@@ -88,6 +118,11 @@ def nc_2048c_2d(n, c):
     return [n, c // 2048, IndexMap.AXIS_SEPARATOR, c % 2048]
 
 
+def nc11_2048c_2d(n, c, h, w):
+    """Return index map for nc11_2048c 2d layout"""
+    return [n, c // 2048, IndexMap.AXIS_SEPARATOR, h, w, c % 2048]
+
+
 def nc_1024c_1d(n, c):
     """Return index map for nc_1024c 1d layout"""
     return [n, c // 1024, c % 1024]
@@ -123,11 +158,25 @@ def nhwc_8h8w32c_2d(n, h, w, c):
     return [n, h // 8, w // 8, c // 32, IndexMap.AXIS_SEPARATOR, h % 8, w % 8, 
c % 32]
 
 
+def nhwc_8h8w32c_1d(n, h, w, c):
+    """Return index map for nhwc_8h8w32c 1d layout"""
+    return [n, h // 8, w // 8, c // 32, h % 8, w % 8, c % 32]
+
+
+def nchw_8h8w32c_2d(n, c, h, w):
+    return [n, c // 32, h // 8, w // 8, IndexMap.AXIS_SEPARATOR, h % 8, w % 8, 
c % 32]
+
+
 def n11c_2048c_2d(n, h, w, c):
     """Return index map for n11c_2048c 2d layout"""
     return [n, h, w, c // 2048, IndexMap.AXIS_SEPARATOR, c % 2048]
 
 
+def n11c_2048c_1d(n, h, w, c):
+    """Return index map for n11c_2048c 1 layout"""
+    return [n, h, w, c // 2048, c % 2048]
+
+
 def iohw_16i32o2i_1d(height, width, in_channel, out_channel):
     return [
         in_channel // 32,
@@ -163,12 +212,16 @@ def get_layout_transform_fn(layout):
         return nhwc_8h2w32c2w_2d
     if layout == "nhwc-8h2w32c2w-1d":
         return nhwc_8h2w32c2w_1d
+    if layout == "nchw-8h2w32c2w-2d":
+        return nchw_8h2w32c2w_2d
     if layout == "n11c-1024c-2d":
         return n11c_1024c_2d
     if layout == "n11c-1024c-1d":
         return n11c_1024c_1d
     if layout == "nhwc-1024c-2d":
         return nhwc_1024c_2d
+    if layout == "nc11-1024c-2d":
+        return nc11_1024c_2d
     if layout == "nc-1024-2d":
         return nc_1024_2d
     if layout == "nhw-32h16w-2d":
@@ -201,16 +254,26 @@ def get_layout_transform_fn(layout):
         return nc_2048c_2d
     if layout == "nhwc-8h8w32c-2d":
         return nhwc_8h8w32c_2d
+    if layout == "nhwc-8h8w32c-1d":
+        return nhwc_8h8w32c_1d
+    if layout == "nchw-8h8w32c-2d":
+        return nchw_8h8w32c_2d
     if layout == "n11c-2048c-2d":
         return n11c_2048c_2d
+    if layout == "n11c-2048c-1d":
+        return n11c_2048c_1d
     if layout == "ohwi32o-1d":
         return ohwi32o_1d
+    if layout == "nc11-2048c-2d":
+        return nc11_2048c_2d
     if layout == "ncw-32c64w-2d":
         return ncw_32c64w_2d
     if layout == "nchw-32c8h8w-2d":
         return nchw_32c8h8w_2d
     if layout == "nchw-32c8h4w-2d":
         return nchw_32c8h4w_2d
+    if layout == "nchw-8h8w32c-2d":
+        return nchw_8h8w32c_2d
     raise RuntimeError(f"Unexpected layout '{layout}'")
 
 
diff --git a/src/relay/op/nn/pooling.cc b/src/relay/op/nn/pooling.cc
index 2a8c244051..1cfbab6e66 100644
--- a/src/relay/op/nn/pooling.cc
+++ b/src/relay/op/nn/pooling.cc
@@ -32,6 +32,7 @@
 #include <vector>
 
 #include "../../transforms/infer_layout_utils.h"
+#include "pooling_common.h"
 
 namespace tvm {
 namespace relay {
@@ -40,43 +41,6 @@ namespace relay {
 TVM_REGISTER_NODE_TYPE(MaxPool2DAttrs);
 TVM_REGISTER_NODE_TYPE(AvgPool2DAttrs);
 
-template <typename T>
-InferCorrectLayoutOutput PoolInferCorrectLayout(const Attrs& attrs,
-                                                const Array<Layout>& 
new_in_layouts,
-                                                const Array<Layout>& 
old_in_layouts,
-                                                const Array<tvm::relay::Type>& 
old_in_types) {
-  const auto* attrs_ptr = attrs.as<T>();
-  ICHECK(attrs_ptr);
-  ObjectPtr<T> params = make_object<T>(*attrs_ptr);
-
-  if (params->out_layout != "") {
-    // when users specify the out_layout of pooling, follow user's preference
-    ICHECK_EQ(params->layout, params->out_layout)
-        << "Pooling input/output layouts mismatch: " << params->layout << " 
vs. "
-        << params->out_layout;
-  } else if (new_in_layouts.defined()) {
-    // the pooling is using an inferred layout (i.e., new_in_layouts[0]) given 
by relay caller
-    ICHECK_EQ(new_in_layouts.size(), 1);
-    params->layout = new_in_layouts[0].name();
-  }
-
-  return InferCorrectLayoutOutput({params->layout}, {params->layout}, 
Attrs(params));
-}
-
-IndexExpr calculate_pool_dimension(IndexExpr in_dimension, IndexExpr 
pad_amount,
-                                   IndexExpr pool_size, IndexExpr dilation, 
IndexExpr stride_size,
-                                   bool ceil_mode) {
-  IndexExpr numerator = in_dimension + pad_amount - ((pool_size - 1) * 
dilation + 1);
-  IndexExpr denominator = stride_size;
-
-  // Emulate the behavior of running ceil on numerator / denominator rather 
than floor
-  if (ceil_mode) {
-    numerator += denominator - 1;
-  }
-
-  return numerator / denominator + 1;
-}
-
 template <typename AttrType>
 bool Pool2DRel(const Array<Type>& types, int num_inputs, const Attrs& attrs,
                const TypeReporter& reporter) {
diff --git a/src/relay/op/nn/pooling.h b/src/relay/op/nn/pooling.h
index 32ae464101..123cfcd075 100644
--- a/src/relay/op/nn/pooling.h
+++ b/src/relay/op/nn/pooling.h
@@ -18,8 +18,8 @@
  */
 
 /*!
- * \file src/relay/op/nn/convolution.h
- * \brief Properties def of convlution operator for sharing.
+ * \file src/relay/op/nn/pooling.h
+ * \brief utilities for creating pool ops
  */
 #ifndef TVM_RELAY_OP_NN_POOLING_H_
 #define TVM_RELAY_OP_NN_POOLING_H_
diff --git a/src/relay/op/nn/pooling_common.h b/src/relay/op/nn/pooling_common.h
new file mode 100644
index 0000000000..1193d36ebe
--- /dev/null
+++ b/src/relay/op/nn/pooling_common.h
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+/*!
+ * \file src/relay/op/nn/pooling_common.h
+ * \brief Common functions for pooling operator definition.
+ */
+#ifndef TVM_RELAY_OP_NN_POOLING_COMMON_H_
+#define TVM_RELAY_OP_NN_POOLING_COMMON_H_
+
+#include <tvm/auto_scheduler/compute_dag.h>
+#include <tvm/runtime/logging.h>
+#include <tvm/tir/analysis.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "../op_common.h"
+
+namespace tvm {
+namespace relay {
+
+inline IndexExpr calculate_pool_dimension(IndexExpr in_dimension, IndexExpr 
pad_amount,
+                                          IndexExpr pool_size, IndexExpr 
dilation,
+                                          IndexExpr stride_size, bool 
ceil_mode) {
+  IndexExpr numerator = in_dimension + pad_amount - ((pool_size - 1) * 
dilation + 1);
+  IndexExpr denominator = stride_size;
+
+  // Emulate the behavior of running ceil on numerator / denominator rather 
than floor
+  if (ceil_mode) {
+    numerator += denominator - 1;
+  }
+
+  return numerator / denominator + 1;
+}
+
+template <typename T>
+InferCorrectLayoutOutput PoolInferCorrectLayout(const Attrs& attrs,
+                                                const Array<Layout>& 
new_in_layouts,
+                                                const Array<Layout>& 
old_in_layouts,
+                                                const Array<tvm::relay::Type>& 
old_in_types) {
+  const auto* attrs_ptr = attrs.as<T>();
+  ICHECK(attrs_ptr);
+  ObjectPtr<T> params = make_object<T>(*attrs_ptr);
+
+  if (params->out_layout != "") {
+    // when users specify the out_layout of pooling, follow user's preference
+    ICHECK_EQ(params->layout, params->out_layout)
+        << "Pooling input/output layouts mismatch: " << params->layout << " 
vs. "
+        << params->out_layout;
+  } else if (new_in_layouts.defined()) {
+    // the pooling is using an inferred layout (i.e., new_in_layouts[0]) given 
by relay caller
+    // ICHECK_EQ(new_in_layouts.size(), 1);
+    params->layout = new_in_layouts[0].name();
+  }
+
+  return InferCorrectLayoutOutput({params->layout}, {params->layout}, 
Attrs(params));
+}
+}  // namespace relay
+}  // namespace tvm
+#endif  // TVM_RELAY_OP_NN_POOLING_COMMON_H_
diff --git a/src/relay/qnn/op/avg_pool2d.cc b/src/relay/qnn/op/avg_pool2d.cc
new file mode 100644
index 0000000000..b2dc08b856
--- /dev/null
+++ b/src/relay/qnn/op/avg_pool2d.cc
@@ -0,0 +1,223 @@
+/*
+ * 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.
+ */
+
+/*!
+ * \file src/relay/qnn/op/avg_pool2d.cc
+ * \brief Quantized avg_pool2d operator
+ */
+
+#include <tvm/relay/analysis.h>
+#include <tvm/relay/base.h>
+#include <tvm/relay/op.h>
+#include <tvm/relay/op_attr_types.h>
+#include <tvm/relay/qnn/attrs.h>
+#include <tvm/tir/expr.h>
+
+#include "../../op/nn/nn.h"
+#include "../../op/nn/pooling.h"
+#include "../../op/nn/pooling_common.h"
+#include "../../op/tensor/transform.h"
+#include "../../transforms/infer_layout_utils.h"
+#include "../../transforms/pattern_utils.h"
+#include "../utils.h"
+#include "op_common.h"
+
+namespace tvm {
+namespace relay {
+namespace qnn {
+
+// relay.op.qnn.avg_pool2d
+bool QnnAvgPool2DRel(const Array<Type>& types, int num_inputs, const Attrs& 
attrs,
+                     const TypeReporter& reporter) {
+  // Expected Types: data, input_zero_point, input_scale, output_zero_point, 
output_scale
+  // out_type
+
+  ICHECK_EQ(types.size(), 6);
+
+  const auto* data = types[0].as<TensorTypeNode>();
+  if (data == nullptr) return false;
+  ICHECK(data->dtype == DataType::Int(8) || data->dtype == DataType::UInt(8))
+      << "Expected quantized avg_pool2d type(int8, uint8) for input but was " 
<< data->dtype;
+
+  const auto* param = attrs.as<AvgPool2DAttrs>();
+  ICHECK(param != nullptr) << "AvgPool2DAttrs cannot be nullptr.";
+
+  // Check the types of scale and zero points.
+  for (size_t i = 1; i < 5; ++i) {
+    if (types[i].as<IncompleteTypeNode>()) {
+      return false;
+    }
+  }
+
+  ICHECK(IsScalarType(types[1], DataType::Float(32)));  // input_scale
+  ICHECK(IsScalarType(types[2], DataType::Int(32)));    // input_zero_point
+  ICHECK(IsScalarType(types[3], DataType::Float(32)));  // output_scale
+  ICHECK(IsScalarType(types[4], DataType::Int(32)));    // output_zero_point
+
+  // Find the output shape and data type
+  const auto dshape = data->shape;
+  ICHECK_GE(dshape.size(), 2U)
+      << "Pool2D only support input >= 2-D: input must have height and width";
+
+  // Check input and output layout
+  Layout layout(param->layout);
+  // The Layout is always NHWC
+  ICHECK(layout.Contains(LayoutAxis::Get('H')) && 
layout.Contains(LayoutAxis::Get('W')) &&
+         !layout.Contains(LayoutAxis::Get('h')) && 
!layout.Contains(LayoutAxis::Get('w')))
+      << "Invalid input layout " << layout
+      << ". qnn_avg_pool2d inut layout must have H and W, which cannot be 
split";
+
+  // Find the output shape and data type
+  const auto hidx = layout.IndexOf(LayoutAxis::Get('H'));
+  const auto widx = layout.IndexOf(LayoutAxis::Get('W'));
+
+  IndexExpr pad_h, pad_w;
+  if (param->padding.size() == 1) {
+    pad_h = param->padding[0] * 2;
+    pad_w = param->padding[0] * 2;
+  } else if (param->padding.size() == 2) {
+    // (top, left)
+    pad_h = param->padding[0] * 2;
+    pad_w = param->padding[1] * 2;
+  } else if (param->padding.size() == 4) {
+    // (top, left, bottom, right)
+    pad_h = param->padding[0] + param->padding[2];
+    pad_w = param->padding[1] + param->padding[3];
+  } else {
+    return false;
+  }
+
+  std::vector<IndexExpr> oshape(dshape.begin(), dshape.end());
+  if (dshape[hidx].as<tir::AnyNode>()) {
+    oshape[hidx] = dshape[hidx];
+  } else {
+    oshape[hidx] =
+        calculate_pool_dimension(dshape[hidx], pad_h, param->pool_size[0], 
param->dilation[0],
+                                 param->strides[0], param->ceil_mode);
+  }
+  if (dshape[widx].as<tir::AnyNode>()) {
+    oshape[widx] = dshape[widx];
+  } else {
+    oshape[widx] =
+        calculate_pool_dimension(dshape[widx], pad_w, param->pool_size[1], 
param->dilation[1],
+                                 param->strides[1], param->ceil_mode);
+  }
+
+  // assign output type
+  reporter->Assign(types[5], TensorType(oshape, data->dtype));
+  return true;
+}
+
+InferCorrectLayoutOutput QnnAvgPoolInferCorrectLayout(const Attrs& attrs,
+                                                      const Array<Layout>& 
new_in_layouts,
+                                                      const Array<Layout>& 
old_in_layouts,
+                                                      const 
Array<tvm::relay::Type>& old_in_types) {
+  // Use Relay AvgPool2D Infer correct layout.
+  auto avgpool_new_layouts =
+      PoolInferCorrectLayout<AvgPool2DAttrs>(attrs, new_in_layouts, 
old_in_layouts, old_in_types);
+
+  // Scales and zero points are scalars, use the "undef" layout for them.
+  Array<Layout> input_layouts = {avgpool_new_layouts->input_layouts[0], 
Layout::Undef(),
+                                 Layout::Undef(), Layout::Undef(), 
Layout::Undef()};
+  Array<Layout> output_layouts = avgpool_new_layouts->output_layouts;
+  return InferCorrectLayoutOutput(input_layouts, output_layouts, attrs);
+}
+
+/*
+ * \brief Forward rewrite the qnn avg_pool2d op.
+ * \param attrs The QNN avg_pool2d attrs.
+ * \param new_args The new mutated args to the call node.
+ * \param arg_types The types of input and output.
+ * \return The sequence of Relay ops for qnn avg_pool2d op.
+ * \note Lowering of the qnn.avg_pool2d operator
+
+ *  Quantized avg_pool2d will take one quantized input tensor and returns 
another
+ *  quantized tensor. Since the input qnn params can be different from the 
output
+ *  qnn params, first, we requantize the input tensors with output qnn params 
and
+ *  cast the results into Int32. Then we call relay.nn.avg_pool2d on that 
requantized
+ *  inputs. Finally, the results are cast into the quantized output data type.
+
+ * Note: The RequantizeOrUpcast function only perform requantization if the 
input
+ * and output qnn params are different, otherwise it only does casting to 
Int32.
+ */
+
+Expr QnnAvgPoolCanonicalize(const Attrs& attrs, const Array<Expr>& new_args,
+                            const Array<tvm::relay::Type>& arg_types) {
+  ICHECK_EQ(new_args.size(), 5);
+  Expr input_data = new_args[0];
+  Expr input_scale = new_args[1];
+  Expr input_zero_point = new_args[2];
+  Expr output_scale = new_args[3];
+  Expr output_zero_point = new_args[4];
+  const auto in_shape = get_shape(arg_types[0]);
+  const auto* avgpool_attrs = attrs.as<AvgPool2DAttrs>();
+  auto requantized_input = RequantizeOrUpcast(input_data, input_scale, 
input_zero_point,
+                                              output_scale, output_zero_point, 
in_shape);
+  Expr nn_avg = AvgPool2D(requantized_input, avgpool_attrs->pool_size, 
avgpool_attrs->strides,
+                          avgpool_attrs->dilation, avgpool_attrs->padding, 
avgpool_attrs->layout,
+                          avgpool_attrs->out_layout, avgpool_attrs->ceil_mode,
+                          avgpool_attrs->count_include_pad);
+
+  const auto* data = arg_types[5].as<TensorTypeNode>();
+  const int32_t min_val = GetQmin(data->dtype);
+  const int32_t max_val = GetQmax(data->dtype);
+  return Cast(Clip(nn_avg, min_val, max_val), data->dtype);
+}
+
+// Positional relay function to create quantized avg_pool2d operator used by 
frontend FFI.
+Expr MakeQuantizedAvgPool2D(Expr data, Expr input_scale, Expr 
input_zero_point, Expr output_scale,
+                            Expr output_zero_point, Array<IndexExpr> pool_size,
+                            Array<IndexExpr> strides, Array<IndexExpr> padding,
+                            Array<IndexExpr> dilation, bool ceil_mode, bool 
count_include_pad,
+                            String layout, String output_layout) {
+  auto attrs = make_object<AvgPool2DAttrs>();
+  attrs->pool_size = std::move(pool_size);
+  attrs->strides = std::move(strides);
+  attrs->padding = std::move(padding);
+  attrs->dilation = std::move(dilation);
+  attrs->layout = std::move(layout);
+  attrs->out_layout = std::move(output_layout);
+  attrs->ceil_mode = ceil_mode;
+  attrs->count_include_pad = count_include_pad;
+  static const Op& op = Op::Get("qnn.avg_pool2d");
+  return Call(op, {data, input_scale, input_zero_point, output_scale, 
output_zero_point},
+              Attrs(attrs), {});
+}
+
+RELAY_REGISTER_OP("qnn.avg_pool2d")
+    .describe("Customized? qnn_avg_pool2d for quantized tensors.")
+    .set_attrs_type<AvgPool2DAttrs>()
+    .set_num_inputs(5)
+    .add_argument("data", "Quantized Tensor", "The input data.")
+    .add_argument("input_scale", "Tensor", "The quantization scale of the 
input tensor.")
+    .add_argument("input_zero_point", "Tensor", "The quantization zero_point 
of the input tensor.")
+    .add_argument("output_scale", "Tensor", "The quantization scale of the 
output tensor.")
+    .add_argument("output_zero_point", "Tensor",
+                  "The quantization zero_point of the output tensor.")
+    .set_support_level(11)
+    .add_type_rel("QnnAvgPool2D", QnnAvgPool2DRel)
+    .set_attr<TOpPattern>("TOpPattern", kOutEWiseFusable)
+    .set_attr<FInferCorrectLayout>("FInferCorrectLayout", 
QnnAvgPoolInferCorrectLayout)
+    .set_attr<FTVMLegalize>("FTVMQnnCanonicalize", QnnAvgPoolCanonicalize);
+
+TVM_REGISTER_GLOBAL("relay.qnn.op._make.avg_pool2d").set_body_typed(MakeQuantizedAvgPool2D);
+
+}  // namespace qnn
+}  // namespace relay
+}  // namespace tvm
diff --git a/tests/python/contrib/test_hexagon/test_qnn_op_integration.py 
b/tests/python/contrib/test_hexagon/test_qnn_op_integration.py
new file mode 100644
index 0000000000..8cff4ed626
--- /dev/null
+++ b/tests/python/contrib/test_hexagon/test_qnn_op_integration.py
@@ -0,0 +1,576 @@
+# 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.
+# pylint: disable=invalid-name
+
+"""Tests for QNN operations on Hexagon"""
+
+import numpy as np
+
+import tvm.testing
+import tvm.topi.testing
+from tvm import relay
+from tvm.contrib.hexagon.session import Session
+from tvm.contrib.hexagon.pytest_plugin import HEXAGON_AOT_LLVM_TARGET
+from tvm.relay.backend import Executor
+from tvm.relay.testing import run_opt_pass, run_infer_type
+
+from .infrastructure import quantize_np
+
+
[email protected]_hexagon
+def test_disable_qnn_legalize_pass():
+    """No QNN pass test."""
+    x = relay.var("x", shape=(4, 8), dtype="float32")
+    op0 = relay.qnn.op.quantize(x, relay.const(2.0), relay.const(10), 
out_dtype="uint8")
+    op1 = relay.qnn.op.dequantize(op0, relay.const(0.5), relay.const(5))
+    relay_mod = tvm.IRModule.from_expr(op1)
+
+    target_hexagon = tvm.target.hexagon("v68")
+    # Default compilation flow
+    with tvm.transform.PassContext(opt_level=3):
+        opt_with_legalize, _ = relay.optimize(
+            relay_mod, tvm.target.Target(target_hexagon, host=target_hexagon)
+        )
+
+    # Disable QNN legalization and canonicalization passes
+    with tvm.transform.PassContext(opt_level=3, 
disabled_pass=["qnn.Legalize"]):
+        opt_without_legalize, _ = relay.optimize(
+            relay_mod, tvm.target.Target(target_hexagon, host=target_hexagon)
+        )
+
+    # Check that QNN ops are absent with default compilation flow.
+    text_with_legalize = opt_with_legalize.astext(show_meta_data=False)
+    assert "qnn.quantize" not in text_with_legalize and "qnn.dequantize" not 
in text_with_legalize
+
+    # Check that QNN ops are present without "qnn.Legalize" passes.
+    text_without_legalize = opt_without_legalize.astext(show_meta_data=False)
+    assert "qnn.quantize" in text_without_legalize and "qnn.dequantize" in 
text_without_legalize
+
+
+def build_hexagon_module(relay_mod):
+    with tvm.transform.PassContext(opt_level=3, 
disabled_pass=["QnnCanonicalize"]):
+        exe_mod = tvm.relay.build(
+            relay_mod,
+            tvm.target.Target(HEXAGON_AOT_LLVM_TARGET, 
host=HEXAGON_AOT_LLVM_TARGET),
+            executor=Executor("aot"),
+        )
+
+    return exe_mod
+
+
+def build_ref_module(relay_mod):
+    target_llvm = tvm.target.Target("llvm")
+    with tvm.transform.PassContext(opt_level=3):
+        exe_mod = tvm.relay.build(
+            relay_mod, tvm.target.Target(target_llvm, host=target_llvm), 
executor=Executor("aot")
+        )
+    return exe_mod
+
+
+def execute(mod_executor, inputs: dict):
+    for input_name, input_data in inputs.items():
+        mod_executor.set_input(input_name, input_data)
+    mod_executor.run()
+    return [mod_executor.get_output(i).numpy() for i in 
range(mod_executor.get_num_outputs())]
+
+
+def execute_on_hexagon(hexagon_session, exe_mod, inputs: dict):
+    return execute(hexagon_session.get_executor_from_factory(exe_mod), inputs)
+
+
+def execute_on_cpu(exe_mod, inputs: dict):
+    return 
execute(tvm.runtime.executor.AotModule(exe_mod["default"](tvm.cpu(0))), inputs)
+
+
+def assert_allclose(actuals, desireds, rtol=1e-07, atol=0.01):
+    return [tvm.testing.assert_allclose(a, d, rtol, atol) for a, d in 
zip(actuals, desireds)]
+
+
+def run_and_compare(hexagon_session, relay_mod, inputs, rtol=None, atol=None):
+    """Compile and execute given relay module on CPU and Hexagon, and compare
+    results"""
+    hexagon_mod = build_hexagon_module(relay_mod)
+    cpu_mod = build_ref_module(relay_mod)
+
+    hexagon_outs = execute_on_hexagon(hexagon_session, hexagon_mod, inputs)
+    cpu_outs = execute_on_cpu(cpu_mod, inputs)
+
+    # Do not pass rtol/atol if not present to use default values from 
assert_allclose
+    tolerances = dict()
+    if rtol is not None:
+        tolerances["rtol"] = rtol
+    if atol is not None:
+        tolerances["atol"] = atol
+
+    assert_allclose(hexagon_outs, cpu_outs, **tolerances)
+
+
+# First test basic QNN ops: quantize, dequantize, requantize
+#
+class TestQnnQuantize:
+    """QNN Quantize test class."""
+
+    input_shape = tvm.testing.parameter([1, 8, 8, 32], [1, 10, 10, 32], [1, 
12, 12, 128])
+    odtype = tvm.testing.parameter("int8", "uint8")
+
+    @tvm.testing.requires_hexagon
+    def test_qnn_quantize(self, hexagon_session: Session, odtype, input_shape):
+        """Test qnn.quantize"""
+
+        def gen_relay_expr_qnn(output_scale, output_zero_point):
+            data = relay.var("data", shape=input_shape, dtype="float32")
+            qnn_quantize = relay.qnn.op.quantize(
+                data,
+                output_scale=relay.const(output_scale),
+                output_zero_point=relay.const(output_zero_point),
+                axis=-1,
+                out_dtype=odtype,
+            )
+            return qnn_quantize
+
+        inputs = {"data": np.random.random(input_shape)}
+        # Use quantize_np to obtain reasonable quantization parameters.
+        ref_out, scale, zero_point = quantize_np(inputs["data"], odtype)
+
+        relay_mod = tvm.IRModule.from_expr(gen_relay_expr_qnn(scale, 
zero_point))
+
+        hexagon_mod = build_hexagon_module(relay_mod)
+        hexagon_outs = execute_on_hexagon(hexagon_session, hexagon_mod, inputs)
+        assert_allclose(hexagon_outs, [ref_out], atol=1)
+
+
+class TestQnnDequantize:
+    """QNN Dequantize test class."""
+
+    input_shape = tvm.testing.parameter(
+        [1, 12, 32, 128], [1, 10, 10, 32], [1, 6, 6, 2048], [1, 1000]
+    )
+    idtype = tvm.testing.parameter("int8", "uint8")
+
+    @tvm.testing.requires_hexagon
+    def test_qnn_dequantize(self, hexagon_session: Session, idtype, 
input_shape):
+        """Test qnn.dequantize"""
+
+        def gen_relay_expr_qnn(dtype, input_scale, input_zero_point):
+            data = relay.var("data", shape=input_shape, dtype=dtype)
+            qnn_dequantize = relay.qnn.op.dequantize(
+                data,
+                input_scale=relay.const(input_scale),
+                input_zero_point=relay.const(input_zero_point),
+            )
+            return qnn_dequantize
+
+        # Generate float data, then quantize it to produce input.
+        ref_out = np.random.random(input_shape)
+        data, scale, zero_point = quantize_np(ref_out, idtype)
+        inputs = {"data": data}
+
+        relay_mod = tvm.IRModule.from_expr(gen_relay_expr_qnn(idtype, scale, 
zero_point))
+
+        hexagon_mod = build_hexagon_module(relay_mod)
+        hexagon_outs = execute_on_hexagon(hexagon_session, hexagon_mod, inputs)
+        # We do
+        #   original -[quantize]-> input -[dequantize]-> output
+        # then compare "original" with "output". Use rtol=1 because of the 
quantized
+        # format in the middle.
+        assert_allclose(hexagon_outs, [ref_out], rtol=1, atol=1e-2)  # rtol = 1
+
+
+class TestQnnRequantize:
+    """QNN requantize test class"""
+
+    @tvm.testing.requires_hexagon
+    def test_qnn_requantize(self, hexagon_session: Session):
+        """Test qnn.requantize"""
+        data_shape = [256]
+        data = relay.var("data", shape=data_shape, dtype="int32")
+
+        op = relay.qnn.op.requantize(
+            data,
+            input_scale=relay.const(0.156),
+            input_zero_point=relay.const(2),
+            output_scale=relay.const(0.212),
+            output_zero_point=relay.const(1),
+            out_dtype="int8",
+        )
+        relay_mod = tvm.IRModule.from_expr(op)
+
+        inputs = {"data": np.arange(-256, 256, 2, dtype="int32")}
+
+        run_and_compare(hexagon_session, relay_mod, inputs, rtol=0, atol=0)  # 
equal
+
+
+class TestQnnAvgPool2d:
+    """QNN AvgPool2d test class."""
+
+    _multitest_params = [
+        ([1, 12, 12, 32], "NHWC", [3, 3], [1, 1], [2, 3], [1, 2, 3, 4], False, 
False),
+        ([1, 18, 18, 32], "NCHW", [3, 3], [2, 2], [2, 1], [1, 2, 3, 4], False, 
True),
+    ]
+
+    (
+        input_shape,
+        layout,
+        kernel,
+        stride,
+        dilation,
+        padding,
+        ceil_mode,
+        count_include_pad,
+    ) = tvm.testing.parameters(*_multitest_params)
+
+    idtype, odtype = tvm.testing.parameters(("uint8", "uint8"))
+
+    @tvm.testing.requires_hexagon
+    def test_qnn_avg_pool2d(
+        self,
+        hexagon_session: Session,
+        idtype,
+        odtype,
+        input_shape,
+        kernel,
+        stride,
+        dilation,
+        padding,
+        ceil_mode,
+        count_include_pad,
+        layout,
+    ):
+        """Test qnn.avg_pool2d"""
+
+        def gen_relay_expr_qnn(
+            dtype, input_scale, input_zero_point, output_scale, 
output_zero_point
+        ):
+            data = relay.var("data", shape=input_shape, dtype=dtype)
+            qnn_avg_pool = relay.qnn.op.avg_pool2d(
+                data,
+                input_scale=relay.const(input_scale),
+                input_zero_point=relay.const(input_zero_point),
+                output_scale=relay.const(output_scale),
+                output_zero_point=relay.const(output_zero_point),
+                pool_size=kernel,
+                strides=stride,
+                dilation=dilation,
+                padding=padding,
+                ceil_mode=ceil_mode,
+                count_include_pad=count_include_pad,
+                layout=layout,
+            )
+
+            return qnn_avg_pool
+
+        # Generate inputs and reference data first.
+        fp_input = np.random.random(input_shape)
+        fp_output = tvm.topi.testing.poolnd_python(
+            fp_input,
+            kernel,
+            stride,
+            dilation,
+            padding_before=padding[:2],
+            padding_after=padding[2:],
+            pool_type="avg",
+            count_include_pad=count_include_pad,
+            ceil_mode=ceil_mode,
+            layout=layout,
+        )
+        input_data, input_scale, input_zero_point = quantize_np(fp_input, 
idtype)
+        ref_out, output_scale, output_zero_point = quantize_np(fp_output, 
odtype)
+        inputs = {"data": input_data}
+
+        relay_mod = tvm.IRModule.from_expr(
+            gen_relay_expr_qnn(
+                idtype, input_scale, input_zero_point, output_scale, 
output_zero_point
+            )
+        )
+
+        hexagon_mod = build_hexagon_module(relay_mod)
+        hexagon_outs = execute_on_hexagon(hexagon_session, hexagon_mod, inputs)
+        assert_allclose(hexagon_outs, [ref_out], rtol=0, atol=2)
+
+
+class TestQnnBinaryOp:
+    """QNN binary op test class"""
+
+    operation = tvm.testing.parameter(relay.qnn.op.add, relay.qnn.op.subtract, 
relay.qnn.op.mul)
+    dtype = tvm.testing.parameter("uint8", "int8")
+    input_shape = tvm.testing.parameter([256], [4, 256])
+
+    @tvm.testing.requires_hexagon
+    def test_qnn_binary_op(self, hexagon_session: Session, operation, dtype, 
input_shape):
+        """Test binary qnn ops"""
+        lhs_shape = [4, 256]
+        rhs_shape = input_shape
+        lhs = relay.var("lhs", shape=lhs_shape, dtype=dtype)
+        rhs = relay.var("rhs", shape=rhs_shape, dtype=dtype)
+        lhs_zp = 1
+        rhs_zp = 3
+
+        op = operation(
+            lhs,
+            rhs,
+            lhs_scale=relay.const(0.041, "float32"),
+            lhs_zero_point=relay.const(lhs_zp, "int32"),
+            rhs_scale=relay.const(0.017, "float32"),
+            rhs_zero_point=relay.const(rhs_zp, "int32"),
+            output_scale=relay.const(0.039, "float32"),
+            output_zero_point=relay.const(2, "int32"),
+        )
+        relay_mod = tvm.IRModule.from_expr(op)
+
+        inputs = {
+            "lhs": np.random.randint(np.iinfo(dtype).min + lhs_zp, 
np.iinfo(dtype).max, lhs_shape),
+            "rhs": np.random.randint(np.iinfo(dtype).min + rhs_zp, 
np.iinfo(dtype).max, rhs_shape),
+        }
+
+        run_and_compare(hexagon_session, relay_mod, inputs, atol=1)  # diff by 
1 is ok
+
+    @tvm.testing.requires_hexagon
+    def test_qnn_binary_op_broadcasting(self, hexagon_session: Session, 
operation):
+        """Test binary qnn ops (with argument broadcast)"""
+        lhs_shape = [4, 256]
+        lhs = relay.var("lhs", shape=lhs_shape, dtype="uint8")
+        rhs = relay.const(11, dtype="uint8")
+
+        op = operation(
+            lhs,
+            rhs,
+            lhs_scale=relay.const(0.049, "float32"),
+            lhs_zero_point=relay.const(1, "int32"),
+            rhs_scale=relay.const(0.067, "float32"),
+            rhs_zero_point=relay.const(3, "int32"),
+            output_scale=relay.const(0.041, "float32"),
+            output_zero_point=relay.const(2, "int32"),
+        )
+        relay_mod = tvm.IRModule.from_expr(op)
+
+        inputs = {"lhs": np.random.randint(1, 255, size=lhs_shape)}
+
+        run_and_compare(hexagon_session, relay_mod, inputs, atol=1)  # diff by 
1 is ok
+
+
+class TestQnnConcatenate:
+    """QNN concatenate test class"""
+
+    @tvm.testing.requires_hexagon
+    def test_qnn_concatenate(self, hexagon_session: Session):
+        """Test qnn.concatenate"""
+        x_shape = [1, 64]
+        y_shape = [2, 64]
+        z_shape = [3, 64]
+        input_x = relay.var("x", shape=x_shape, dtype="uint8")
+        input_y = relay.var("y", shape=y_shape, dtype="uint8")
+        input_z = relay.var("z", shape=z_shape, dtype="uint8")
+
+        op = relay.qnn.op.concatenate(
+            (input_x, input_y, input_z),
+            input_scales=(relay.const(0.3), relay.const(0.7), 
relay.const(1.3)),
+            input_zero_points=(relay.const(0), relay.const(1), relay.const(2)),
+            output_scale=relay.const(0.8),
+            output_zero_point=relay.const(5),
+            axis=0,
+        )
+        relay_mod = tvm.IRModule.from_expr(op)
+
+        inputs = {
+            "x": np.arange(0, 64, 1, dtype="uint8").reshape(x_shape),
+            "y": np.arange(0, 128, 1, dtype="uint8").reshape(y_shape),
+            "z": np.arange(0, 192, 1, dtype="uint8").reshape(z_shape),
+        }
+
+        run_and_compare(hexagon_session, relay_mod, inputs, atol=1)  # diff by 
1 is ok
+
+
+class TestQnnConv2D:
+    """QNN conv2d op test class."""
+
+    @tvm.testing.requires_hexagon
+    def test_qnn_quantize_conv2d_requantize(self, hexagon_session: Session):
+        """Tast qnn.conv2d"""
+        data_shape = [1, 8, 32, 32]
+        weight_shape = [16, 8, 3, 3]
+        data = relay.var("data", shape=data_shape, dtype="float32")
+        weight = relay.var("weight", shape=weight_shape, dtype="float32")
+        op0 = relay.qnn.op.quantize(data, relay.const(0.078), relay.const(0), 
out_dtype="uint8")
+        op1 = relay.qnn.op.quantize(weight, relay.const(0.07), relay.const(0), 
out_dtype="int8")
+        op2 = relay.qnn.op.conv2d(
+            op0,
+            op1,
+            input_zero_point=relay.const(0),
+            kernel_zero_point=relay.const(0),
+            input_scale=relay.const(0.078),
+            kernel_scale=relay.const(0.07),
+            padding=[0, 0, 0, 0],
+            channels=16,
+            kernel_size=[3, 3],
+        )
+        op5 = relay.qnn.op.requantize(
+            op2,
+            input_scale=relay.const(0.05),
+            input_zero_point=relay.const(0),
+            output_scale=relay.const(0.21),
+            output_zero_point=relay.const(61),
+            out_dtype="int8",
+        )
+        relay_mod = tvm.IRModule.from_expr(op5)
+
+        inputs = {
+            "data": np.random.rand(*data_shape),
+            "weight": np.random.rand(*weight_shape) - 0.5,
+        }
+
+        run_and_compare(hexagon_session, relay_mod, inputs, rtol=0, atol=0)  # 
equal
+
+
+class TestQnnDense:
+    """QNN dense op test class."""
+
+    @tvm.testing.requires_hexagon
+    def test_alter_layout_qnn_dense(self):
+        """Test weights layout transformation of qnn.dense with int8 weights"""
+        data = relay.var("data", shape=(128, 16), dtype="uint8")
+        weight = relay.var("weight", shape=(64, 16), dtype="int8")
+        zero = relay.const(0)
+        iscale = relay.const(0.15)
+        wscale = relay.const(0.37)
+
+        def before():
+            return relay.qnn.op.dense(data, weight, zero, zero, iscale, 
wscale, units=None)
+
+        def expected():
+            op0 = relay.layout_transform(weight, src_layout="NC", 
dst_layout="NC32n4c")
+            return relay.qnn.op.contrib_dense_pack(data, op0, zero, zero, 
iscale, wscale, "NC32n4c")
+
+        target = tvm.target.hexagon("v68")
+        with tvm.target.Target(target):
+            a = run_opt_pass(before(), tvm.relay.transform.AlterOpLayout())
+            b = run_infer_type(expected())
+            tvm.ir.assert_structural_equal(a, b)
+
+    # Dense + bias_add + requantize
+    #
+    dtype = tvm.testing.parameter("uint8", "int8")
+    n_dim = tvm.testing.parameter(64, 60)
+
+    @tvm.testing.requires_hexagon
+    def test_qnn_dense_biasadd_requantize(self, hexagon_session: Session, 
dtype, n_dim):
+        """Check lowering of qnn.dense + bias_add + qnn.requantize
+        dtype: type of weights
+        n_dim: N dimension of weights, need to check cases when it is multiple 
of 32 and not.
+        """
+        data_shape = [128, 32]
+        weight_shape = [n_dim, 32]
+        bias_shape = [n_dim]
+        data = relay.var("data", shape=data_shape, dtype="uint8")
+        weight = relay.var("weight", shape=weight_shape, dtype=dtype)
+        bias = relay.var("bias", shape=bias_shape, dtype="int32")
+
+        op0 = relay.qnn.op.dense(
+            data,
+            weight,
+            input_zero_point=relay.const(2),
+            kernel_zero_point=relay.const(0),
+            input_scale=relay.const(0.08),
+            kernel_scale=relay.const(0.07),
+            units=None,
+        )
+        op1 = relay.nn.bias_add(op0, bias)
+        op2 = relay.qnn.op.requantize(
+            op1,
+            input_scale=relay.const(1.3),
+            input_zero_point=relay.const(4),
+            output_scale=relay.const(3.7),
+            output_zero_point=relay.const(1),
+            out_dtype="uint8",
+        )
+        relay_mod = tvm.IRModule.from_expr(op2)
+
+        np.random.seed(0)
+
+        inputs = {
+            "data": np.random.randint(2, 8, size=data_shape, dtype="uint8"),
+            "weight": np.random.randint(0, 8, size=weight_shape, dtype=dtype),
+            "bias": np.random.randint(-10, 10, size=bias_shape, dtype="int32"),
+        }
+
+        run_and_compare(hexagon_session, relay_mod, inputs, atol=1)  # diff by 
1 is ok
+
+    # Dense + requantize
+    #
+    @tvm.testing.requires_hexagon
+    def test_qnn_dense_requantize(self, hexagon_session: Session):
+        """Check lowering of qnn.dense + qnn.requantize
+        Checkint the case: data type = "uint8", weight type = "int8", input zp 
= 0 and kernel zp = 0
+        """
+        data_shape = [128, 32]
+        weight_shape = [64, 32]
+        data = relay.var("data", shape=data_shape, dtype="uint8")
+        weight = relay.var("weight", shape=weight_shape, dtype="int8")
+
+        op0 = relay.qnn.op.dense(
+            data,
+            weight,
+            input_zero_point=relay.const(0),
+            kernel_zero_point=relay.const(0),
+            input_scale=relay.const(0.06),
+            kernel_scale=relay.const(0.19),
+            units=64,
+        )
+        op1 = relay.qnn.op.requantize(
+            op0,
+            input_scale=relay.const(0.1),
+            input_zero_point=relay.const(0),
+            output_scale=relay.const(0.24),
+            output_zero_point=relay.const(64),
+            out_dtype="uint8",
+        )
+        relay_mod = tvm.IRModule.from_expr(op1)
+
+        np.random.seed(0)
+
+        inputs = {
+            "data": np.random.randint(0, 8, size=data_shape, dtype="uint8"),
+            "weight": np.random.randint(-4, 4, size=weight_shape, 
dtype="int8"),
+        }
+
+        run_and_compare(hexagon_session, relay_mod, inputs, atol=1)  # diff by 
1 is ok
+
+
+class TestQnnTanh:
+    """QNN tanh test class"""
+
+    @tvm.testing.requires_hexagon
+    def test_qnn_tanh(self, hexagon_session: Session):
+        """Test qnn.tanh"""
+        data_shape = [256]
+        data = relay.var("data", shape=data_shape, dtype="uint8")
+
+        op = relay.qnn.op.tanh(
+            data,
+            scale=relay.const(0.518),
+            zero_point=relay.const(137),
+            output_scale=relay.const(0.207),
+            output_zero_point=relay.const(128),
+        )
+        relay_mod = tvm.IRModule.from_expr(op)
+
+        inputs = {"data": np.arange(0, 256, 1, dtype="uint8")}
+
+        run_and_compare(hexagon_session, relay_mod, inputs, rtol=0, atol=0)  # 
equal
+
+
+if __name__ == "__main__":
+    tvm.testing.main()
diff --git a/tests/python/contrib/test_hexagon/test_wo_qnn_canonicalization.py 
b/tests/python/contrib/test_hexagon/test_wo_qnn_canonicalization.py
deleted file mode 100644
index f4342f5814..0000000000
--- a/tests/python/contrib/test_hexagon/test_wo_qnn_canonicalization.py
+++ /dev/null
@@ -1,475 +0,0 @@
-# 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.
-"""No QNN canonicalization tests."""
-
-import numpy as np
-
-import tvm.testing
-from tvm import relay
-from tvm.contrib.hexagon.session import Session
-from tvm.contrib.hexagon.pytest_plugin import HEXAGON_AOT_LLVM_TARGET
-from tvm.relay.backend import Executor
-from tvm.relay.testing import run_opt_pass, run_infer_type
-
-
[email protected]_hexagon
-def test_no_qnn_pass():
-    """No QNN pass test."""
-    x = relay.var("x", shape=(4, 8), dtype="float32")
-    op0 = relay.qnn.op.quantize(x, relay.const(2.0), relay.const(10), 
out_dtype="uint8")
-    op1 = relay.qnn.op.dequantize(op0, relay.const(0.5), relay.const(5))
-    mod = tvm.IRModule.from_expr(op1)
-
-    target_hexagon = tvm.target.hexagon("v68")
-    # Default compilation flow
-    with tvm.transform.PassContext(opt_level=3):
-        opt_mod_1, _ = relay.optimize(mod, tvm.target.Target(target_hexagon, 
host=target_hexagon))
-
-    # Disable QNN legalization and canonicalization passes
-    with tvm.transform.PassContext(opt_level=3, 
disabled_pass=["qnn.Legalize"]):
-        opt_mod_2, _ = relay.optimize(mod, tvm.target.Target(target_hexagon, 
host=target_hexagon))
-
-    # Check that QNN ops are absent with default compilation flow.
-    assert "qnn.quantize" not in opt_mod_1.astext(show_meta_data=False)
-    assert "qnn.dequantize" not in opt_mod_1.astext(show_meta_data=False)
-
-    # Check that QNN ops are present without "qnn.Legalize" passes.
-    assert "qnn.quantize" in opt_mod_2.astext(show_meta_data=False)
-    assert "qnn.dequantize" in opt_mod_2.astext(show_meta_data=False)
-
-
-def test_alter_layout_qnn_dense():
-    """Test weights layout transformation of qnn.dense with int8 weights"""
-    data = relay.var("data", shape=(128, 16), dtype="uint8")
-    weight = relay.var("weight", shape=(64, 16), dtype="int8")
-    zero = relay.const(0)
-    iscale = relay.const(0.15)
-    wscale = relay.const(0.37)
-
-    def before():
-        return relay.qnn.op.dense(data, weight, zero, zero, iscale, wscale, 
units=None)
-
-    def expected():
-        op0 = relay.layout_transform(weight, src_layout="NC", 
dst_layout="NC32n4c")
-        return relay.qnn.op.contrib_dense_pack(data, op0, zero, zero, iscale, 
wscale, "NC32n4c")
-
-    target = tvm.target.hexagon("v68")
-    with tvm.target.Target(target):
-        a = run_opt_pass(before(), tvm.relay.transform.AlterOpLayout())
-        b = run_infer_type(expected())
-        tvm.ir.assert_structural_equal(a, b)
-
-
-def execute(mod_executor, inputs: dict):
-    for input_name, input_data in inputs.items():
-        mod_executor.set_input(input_name, input_data)
-    mod_executor.run()
-    return mod_executor.get_output(0).numpy()
-
-
-def build_hexagon_module(mod):
-    with tvm.transform.PassContext(opt_level=3, 
disabled_pass=["QnnCanonicalize"]):
-        hexagon_lowered = tvm.relay.build(
-            mod,
-            tvm.target.Target(HEXAGON_AOT_LLVM_TARGET, 
host=HEXAGON_AOT_LLVM_TARGET),
-            executor=Executor("aot"),
-        )
-
-    return hexagon_lowered
-
-
-def build_ref_module(mod):
-    target_llvm = tvm.target.Target("llvm")
-    with tvm.transform.PassContext(opt_level=3):
-        llvm_lowered = tvm.relay.build(
-            mod,
-            tvm.target.Target(target_llvm, host=target_llvm),
-            executor=Executor("aot"),
-        )
-    return llvm_lowered
-
-
[email protected]_hexagon
-def test_qnn_conv2d_rq(hexagon_session: Session):
-    """QNN conv2d test."""
-    data_shape = [1, 8, 32, 32]
-    weight_shape = [16, 8, 3, 3]
-    data = relay.var("data", shape=data_shape, dtype="float32")
-    weight = relay.var("weight", shape=weight_shape, dtype="float32")
-    op0 = relay.qnn.op.quantize(data, relay.const(0.078), relay.const(0), 
out_dtype="uint8")
-    op1 = relay.qnn.op.quantize(weight, relay.const(0.07), relay.const(0), 
out_dtype="int8")
-    op2 = relay.qnn.op.conv2d(
-        op0,
-        op1,
-        input_zero_point=relay.const(0),
-        kernel_zero_point=relay.const(0),
-        input_scale=relay.const(0.078),
-        kernel_scale=relay.const(0.07),
-        padding=[0, 0, 0, 0],
-        channels=16,
-        kernel_size=[3, 3],
-    )
-    op5 = relay.qnn.op.requantize(
-        op2,
-        input_scale=relay.const(0.05),
-        input_zero_point=relay.const(0),
-        output_scale=relay.const(0.21),
-        output_zero_point=relay.const(61),
-        out_dtype="int8",
-    )
-    relay_mod = tvm.IRModule.from_expr(op5)
-
-    # Compile for Hexagon
-    hexagon_lowered = build_hexagon_module(relay_mod)
-
-    # Reference compilation
-    llvm_lowered = build_ref_module(relay_mod)
-
-    data_np = np.random.rand(*data_shape)
-    weight_np = np.random.rand(*weight_shape) - 0.5
-    inputs = {"data": data_np, "weight": weight_np}
-
-    hx_m = hexagon_session.get_executor_from_factory(hexagon_lowered)
-    hexagon_output = execute(hx_m, inputs)
-
-    dev = tvm.cpu(0)
-    llvm_m = tvm.runtime.executor.AotModule(llvm_lowered["default"](dev))
-    llvm_out = execute(llvm_m, inputs)
-
-    np.testing.assert_equal(hexagon_output, llvm_out)
-
-
-class TestQnnDense:
-    """QNN dense op test class."""
-
-    dtype = tvm.testing.parameter("uint8", "int8")
-    n_dim = tvm.testing.parameter(64, 60)
-
-    @tvm.testing.requires_hexagon
-    def test_qnn_dense_add_requantize(self, hexagon_session: Session, dtype, 
n_dim):
-        """Check lowering of qnn.dense + bias_add + qnn.requantize
-        dtype: type of weights
-        n_dim: N dimension of weights, need to check cases when it is multiple 
of 32 and not.
-        """
-        data_shape = [128, 32]
-        weight_shape = [n_dim, 32]
-        bias_shape = [n_dim]
-        data = relay.var("data", shape=data_shape, dtype="uint8")
-        weight = relay.var("weight", shape=weight_shape, dtype=dtype)
-        bias = relay.var("bias", shape=bias_shape, dtype="int32")
-
-        op0 = relay.qnn.op.dense(
-            data,
-            weight,
-            input_zero_point=relay.const(2),
-            kernel_zero_point=relay.const(0),
-            input_scale=relay.const(0.08),
-            kernel_scale=relay.const(0.07),
-            units=None,
-        )
-        op1 = relay.nn.bias_add(op0, bias)
-        op2 = relay.qnn.op.requantize(
-            op1,
-            input_scale=relay.const(1.3),
-            input_zero_point=relay.const(4),
-            output_scale=relay.const(3.7),
-            output_zero_point=relay.const(1),
-            out_dtype="uint8",
-        )
-        relay_mod = tvm.IRModule.from_expr(op2)
-
-        # Compile for Hexagon
-        hexagon_lowered = build_hexagon_module(relay_mod)
-
-        # Reference compilation
-        llvm_lowered = build_ref_module(relay_mod)
-
-        np.random.seed(0)
-
-        data_np = np.random.randint(2, 8, size=data_shape, dtype="uint8")
-        weight_np = np.random.randint(0, 8, size=weight_shape, dtype=dtype)
-        bias_np = np.random.randint(-10, 10, size=bias_shape, dtype="int32")
-        inputs = {"data": data_np, "weight": weight_np, "bias": bias_np}
-
-        hx_m = hexagon_session.get_executor_from_factory(hexagon_lowered)
-        hexagon_output = execute(hx_m, inputs)
-
-        llvm_m = 
tvm.runtime.executor.AotModule(llvm_lowered["default"](tvm.cpu(0)))
-        llvm_out = execute(llvm_m, inputs)
-
-        # Diff by 1 is Ok.
-        tvm.testing.assert_allclose(hexagon_output, llvm_out, atol=1)
-
-    @tvm.testing.requires_hexagon
-    def test_qnn_dense_requantize(self, hexagon_session: Session):
-        """Check lowering of qnn.dense + qnn.requantize
-        Checkint the case: data type = "uint8", weight type = "int8", input zp 
= 0 and kernel zp = 0
-        """
-        data_shape = [128, 32]
-        weight_shape = [64, 32]
-        data = relay.var("data", shape=data_shape, dtype="uint8")
-        weight = relay.var("weight", shape=weight_shape, dtype="int8")
-
-        op0 = relay.qnn.op.dense(
-            data,
-            weight,
-            input_zero_point=relay.const(0),
-            kernel_zero_point=relay.const(0),
-            input_scale=relay.const(0.06),
-            kernel_scale=relay.const(0.19),
-            units=64,
-        )
-        op1 = relay.qnn.op.requantize(
-            op0,
-            input_scale=relay.const(0.1),
-            input_zero_point=relay.const(0),
-            output_scale=relay.const(0.24),
-            output_zero_point=relay.const(64),
-            out_dtype="uint8",
-        )
-        relay_mod = tvm.IRModule.from_expr(op1)
-
-        # Compile for Hexagon
-        hexagon_lowered = build_hexagon_module(relay_mod)
-
-        # Reference compilation
-        llvm_lowered = build_ref_module(relay_mod)
-
-        np.random.seed(0)
-
-        data_np = np.random.randint(0, 8, size=data_shape, dtype="uint8")
-        weight_np = np.random.randint(-4, 4, size=weight_shape, dtype="int8")
-        inputs = {"data": data_np, "weight": weight_np}
-
-        hx_m = hexagon_session.get_executor_from_factory(hexagon_lowered)
-        hexagon_output = execute(hx_m, inputs)
-
-        llvm_m = 
tvm.runtime.executor.AotModule(llvm_lowered["default"](tvm.cpu(0)))
-        llvm_out = execute(llvm_m, inputs)
-
-        # Diff by 1 is Ok.
-        tvm.testing.assert_allclose(hexagon_output, llvm_out, atol=1)
-
-
-class TestQnnBinaryOp:
-    """QNN binary op test class"""
-
-    operation = tvm.testing.parameter(
-        relay.qnn.op.add,
-        relay.qnn.op.subtract,
-        relay.qnn.op.mul,
-    )
-    dtype = tvm.testing.parameter("uint8", "int8")
-    input_shape = tvm.testing.parameter([256], [4, 256])
-
-    @tvm.testing.requires_hexagon
-    def test_qnn_binary_op_broadcasting(
-        self, hexagon_session: Session, operation, dtype, input_shape
-    ):
-        """qnn binary op test without QNN canonicalization."""
-        lhs_shape = [4, 256]
-        rhs_shape = input_shape
-        lhs = relay.var("lhs", shape=lhs_shape, dtype=dtype)
-        rhs = relay.var("rhs", shape=rhs_shape, dtype=dtype)
-        zp_const1 = 1
-        zp_const2 = 3
-
-        op = operation(
-            lhs,
-            rhs,
-            lhs_scale=relay.const(0.041, "float32"),
-            lhs_zero_point=relay.const(zp_const1, "int32"),
-            rhs_scale=relay.const(0.017, "float32"),
-            rhs_zero_point=relay.const(zp_const2, "int32"),
-            output_scale=relay.const(0.039, "float32"),
-            output_zero_point=relay.const(2, "int32"),
-        )
-        mod = tvm.IRModule.from_expr(op)
-
-        # Compile for Hexagon
-        hexagon_lowered = build_hexagon_module(mod)
-
-        # Reference compilation
-        llvm_lowered = build_ref_module(mod)
-
-        lhs_np = np.random.randint(np.iinfo(dtype).min + zp_const1, 
np.iinfo(dtype).max, lhs_shape)
-        rhs_np = np.random.randint(np.iinfo(dtype).min + zp_const2, 
np.iinfo(dtype).max, rhs_shape)
-        inputs = {"lhs": lhs_np, "rhs": rhs_np}
-
-        hx_m = hexagon_session.get_executor_from_factory(hexagon_lowered)
-        hexagon_output = execute(hx_m, inputs)
-
-        dev = tvm.cpu(0)
-        llvm_m = tvm.runtime.executor.AotModule(llvm_lowered["default"](dev))
-        llvm_output = execute(llvm_m, inputs)
-
-        # Diff by 1 is Ok.
-        tvm.testing.assert_allclose(hexagon_output, llvm_output, atol=1)
-
-    @tvm.testing.requires_hexagon
-    def test_qnn_binary_op_scalar(self, hexagon_session: Session, operation):
-        """qnn binary op test without QNN canonicalization."""
-        lhs_shape = [4, 256]
-        lhs = relay.var("lhs", shape=lhs_shape, dtype="uint8")
-        rhs = relay.const(11, dtype="uint8")
-
-        op = operation(
-            lhs,
-            rhs,
-            lhs_scale=relay.const(0.049, "float32"),
-            lhs_zero_point=relay.const(1, "int32"),
-            rhs_scale=relay.const(0.067, "float32"),
-            rhs_zero_point=relay.const(3, "int32"),
-            output_scale=relay.const(0.041, "float32"),
-            output_zero_point=relay.const(2, "int32"),
-        )
-        mod = tvm.IRModule.from_expr(op)
-
-        # Compile for Hexagon
-        hexagon_lowered = build_hexagon_module(mod)
-
-        # Reference compilation
-        llvm_lowered = build_ref_module(mod)
-
-        lhs_np = np.random.randint(1, 255, size=lhs_shape)
-        inputs = {"lhs": lhs_np}
-
-        hx_m = hexagon_session.get_executor_from_factory(hexagon_lowered)
-        hexagon_output = execute(hx_m, inputs)
-
-        dev = tvm.cpu(0)
-        llvm_m = tvm.runtime.executor.AotModule(llvm_lowered["default"](dev))
-        llvm_output = execute(llvm_m, inputs)
-
-        # Diff by 1 is Ok.
-        tvm.testing.assert_allclose(hexagon_output, llvm_output, atol=1)
-
-
-class TestQnnOp:
-    """QNN op test class"""
-
-    @tvm.testing.requires_hexagon
-    def test_qnn_requantize(self, hexagon_session: Session):
-        """qnn.requantize test without QNN canonicalization."""
-        data_shape = [256]
-        data = relay.var("data", shape=data_shape, dtype="int32")
-
-        op = relay.qnn.op.requantize(
-            data,
-            input_scale=relay.const(0.156),
-            input_zero_point=relay.const(2),
-            output_scale=relay.const(0.212),
-            output_zero_point=relay.const(1),
-            out_dtype="int8",
-        )
-        mod = tvm.IRModule.from_expr(op)
-
-        # Compile for Hexagon
-        hexagon_lowered = build_hexagon_module(mod)
-
-        # Reference compilation
-        llvm_lowered = build_ref_module(mod)
-
-        data_np = np.arange(-256, 256, 2, dtype="int32")
-        inputs = {"data": data_np}
-
-        hx_m = hexagon_session.get_executor_from_factory(hexagon_lowered)
-        hexagon_output = execute(hx_m, inputs)
-
-        dev = tvm.cpu(0)
-        llvm_m = tvm.runtime.executor.AotModule(llvm_lowered["default"](dev))
-        llvm_output = execute(llvm_m, inputs)
-
-        np.testing.assert_equal(hexagon_output, llvm_output)
-
-    @tvm.testing.requires_hexagon
-    def test_qnn_concatenate(self, hexagon_session: Session):
-        """qnn.concatenate op test without QNN canonicalization."""
-        x_shape = [1, 64]
-        y_shape = [2, 64]
-        z_shape = [3, 64]
-        input_x = relay.var("x", shape=x_shape, dtype="uint8")
-        input_y = relay.var("y", shape=y_shape, dtype="uint8")
-        input_z = relay.var("z", shape=z_shape, dtype="uint8")
-
-        op = relay.qnn.op.concatenate(
-            (input_x, input_y, input_z),
-            input_scales=(relay.const(0.3), relay.const(0.7), 
relay.const(1.3)),
-            input_zero_points=(relay.const(0), relay.const(1), relay.const(2)),
-            output_scale=relay.const(0.8),
-            output_zero_point=relay.const(5),
-            axis=0,
-        )
-        mod = tvm.IRModule.from_expr(op)
-
-        # Compile for Hexagon
-        hexagon_lowered = build_hexagon_module(mod)
-
-        # Reference compilation
-        llvm_lowered = build_ref_module(mod)
-
-        x_np = np.arange(0, 64, 1, dtype="uint8").reshape(x_shape)
-        y_np = np.arange(0, 128, 1, dtype="uint8").reshape(y_shape)
-        z_np = np.arange(0, 192, 1, dtype="uint8").reshape(z_shape)
-        inputs = {"x": x_np, "y": y_np, "z": z_np}
-
-        hx_m = hexagon_session.get_executor_from_factory(hexagon_lowered)
-        hexagon_output = execute(hx_m, inputs)
-
-        dev = tvm.cpu(0)
-        llvm_m = tvm.runtime.executor.AotModule(llvm_lowered["default"](dev))
-        llvm_output = execute(llvm_m, inputs)
-
-        # Diff by 1 is Ok.
-        tvm.testing.assert_allclose(hexagon_output, llvm_output, atol=1)
-
-    @tvm.testing.requires_hexagon
-    def test_qnn_tanh(self, hexagon_session: Session):
-        """qnn.tanh op test without QNN canonicalization."""
-        data_shape = [256]
-        data = relay.var("data", shape=data_shape, dtype="uint8")
-
-        op = relay.qnn.op.tanh(
-            data,
-            scale=relay.const(0.518),
-            zero_point=relay.const(137),
-            output_scale=relay.const(0.207),
-            output_zero_point=relay.const(128),
-        )
-        mod = tvm.IRModule.from_expr(op)
-
-        # Compile for Hexagon
-        hexagon_lowered = build_hexagon_module(mod)
-
-        # Reference compilation
-        llvm_lowered = build_ref_module(mod)
-
-        data_np = np.arange(0, 256, 1, dtype="uint8")
-        inputs = {"data": data_np}
-
-        hx_m = hexagon_session.get_executor_from_factory(hexagon_lowered)
-        hexagon_output = execute(hx_m, inputs)
-
-        dev = tvm.cpu(0)
-        llvm_m = tvm.runtime.executor.AotModule(llvm_lowered["default"](dev))
-        llvm_output = execute(llvm_m, inputs)
-
-        np.testing.assert_equal(hexagon_output, llvm_output)
-
-
-if __name__ == "__main__":
-    tvm.testing.main()
diff --git 
a/tests/python/contrib/test_hexagon/topi/slice_op/test_avg_pool2d_slice.py 
b/tests/python/contrib/test_hexagon/topi/slice_op/test_avg_pool2d_slice.py
index 0eedfdbf8d..712d5b303e 100644
--- a/tests/python/contrib/test_hexagon/topi/slice_op/test_avg_pool2d_slice.py
+++ b/tests/python/contrib/test_hexagon/topi/slice_op/test_avg_pool2d_slice.py
@@ -20,74 +20,70 @@ from typing import *
 
 from tvm import te
 import tvm.testing
+from tvm.topi.testing import poolnd_python
 from tvm.contrib.hexagon.session import Session
 import tvm.topi.hexagon.slice_ops as sl
 import tvm.topi.hexagon.qnn as qn
 from tvm.contrib.hexagon import allocate_hexagon_array
-from ...infrastructure import (
-    transform_numpy,
-    quantize_np,
-    get_hexagon_target,
-)
-from ...pytest_util import (
-    get_multitest_ids,
-    create_populated_numpy_ndarray,
-    TensorContentRandom,
-)
+import pytest
+from ...infrastructure import transform_numpy, quantize_np, get_hexagon_target
+from ...pytest_util import get_multitest_ids, create_populated_numpy_ndarray, 
TensorContentRandom
 
-input_layout = tvm.testing.parameter(
-    "nhwc-8h2w32c2w-2d",
-)
 
-dtype = tvm.testing.parameter("float16", "uint8")
+dtype = tvm.testing.parameter("uint8", "float16")
 
 
 @tvm.testing.fixture
-def output_layout(output_shape, dtype):
-    o_b, o_h, o_w, o_c = output_shape
-    if dtype == "float16":
-        if o_h == 1 and o_w == 1:
-            return "n11c-1024c-2d"
+def output_layout(output_shape, op_layout, dtype):
+    if op_layout == "NHWC":
+        o_b, o_h, o_w, o_c = output_shape
+        if dtype == "float16":
+            if o_h == 1 and o_w == 1:
+                return "n11c-1024c-2d"
+            else:
+                return "nhwc-8h2w32c2w-2d"
+        elif dtype == "int8" or "uint8":
+            if o_h == 1 and o_w == 1:
+                return "n11c-2048c-2d"
+            else:
+                return "nhwc-8h8w32c-2d"
         else:
-            assert o_h % 8 == 0 and o_w % 4 == 0, "Invalid output shape"
-            return "nhwc-8h2w32c2w-2d"
-    elif dtype == "int8" or "uint8":
-        if o_h == 1 and o_w == 1:
-            return "n11c-2048c-2d"
+            raise RuntimeError(f"Unsupported data type '{dtype}'")
+
+    elif op_layout == "NCHW":
+        o_b, o_c, o_h, o_w = output_shape
+        if dtype == "float16":
+            if o_h == 1 and o_w == 1:
+                return "nc11-1024c-2d"
+            else:
+                return "nchw-8h2w32c2w-2d"
+        elif dtype == "int8" or "uint8":
+            if o_h == 1 and o_w == 1:
+                return "nc11-2048c-2d"
+            else:
+                return "nchw-8h8w32c-2d"
         else:
-            assert o_h % 8 == 0 and o_w % 8 == 0, "Invalid output shape"
-            return "nhwc-8h8w32c-2d"
+            raise RuntimeError(f"Unsupported data type '{dtype}'")
     else:
-        raise RuntimeError(f"Unsupported data type '{dtype}'")
+        raise RuntimeError(f"Unsupported layout for qnn.avg_pool2d 
'{op_layout}'")
 
 
 @tvm.testing.fixture
-def input_np(input_shape, dtype: str, input_tensor_populator):
-    if dtype == "uint8":
-        dtype = "float32"  # Use "float32" input which will be quantized later
-    return create_populated_numpy_ndarray(input_shape, dtype, 
input_tensor_populator)
-
-
[email protected]
-def transformed_expected_output_np(expected_output_np, output_layout, dtype):
+def input_layout(op_layout, dtype):
+    in_layout = op_layout.lower()
     if dtype == "float16":
-        return transform_numpy(expected_output_np, "nhwc", output_layout)
-    elif dtype in ("uint8", "int8"):
-        quant_arr, scale, zero_point = quantize_np(expected_output_np, dtype)
-        return [transform_numpy(quant_arr, "nhwc", output_layout), scale, 
zero_point]
+        return in_layout + "-8h2w32c2w-2d"
+    elif dtype == "int8" or "uint8":
+        return in_layout + "-8h8w32c-2d"
     else:
         raise RuntimeError(f"Unsupported data type '{dtype}'")
 
 
 @tvm.testing.fixture
-def transformed_input_np_padded(input_np_padded, input_layout, dtype):
-    if dtype == "float16":
-        return transform_numpy(input_np_padded, "nhwc", input_layout)
-    elif dtype in ("uint8", "int8"):
-        quant_arr, scale, zero_point = quantize_np(input_np_padded, dtype)
-        return [transform_numpy(quant_arr, "nhwc", input_layout), scale, 
zero_point]
-    else:
-        raise RuntimeError(f"Unsupported data type '{dtype}'")
+def input_np(input_shape, dtype: str, input_tensor_populator):
+    if dtype == "uint8":
+        dtype = "float32"  # Use "float32" input which will be quantized later
+    return create_populated_numpy_ndarray(input_shape, dtype, 
input_tensor_populator)
 
 
 class TestAvgPool2dSlice:
@@ -99,110 +95,143 @@ class TestAvgPool2dSlice:
         "pad",  # padding
         "ceil",  # ceil_mode
         "cnt_padded",  # count_include_pad
+        "op_layout",  # input output 4D layout
         None,  # input_tensor_populator
     ]
-
     _multitest_params = [
         (
-            [1, 8, 8, 32],
+            [1, 7, 11, 32],
+            [3, 3],
+            [3, 2],
+            [2, 3],
+            [1, 2, 3, 4],
+            False,
+            False,
+            "NHWC",
+            TensorContentRandom(),
+        ),
+        (
+            [1, 1, 1, 2048],
+            [4, 4],
+            [2, 2],
+            [2, 3],
+            [0, 2, 1, 4],
+            False,
+            False,
+            "NHWC",
+            TensorContentRandom(),
+        ),
+        # Test default stride,dilation, and padding with different layouts
+        (
+            [1, 10, 10, 32],
             [3, 3],
             [1, 1],
             [1, 1],
             [0, 0, 0, 0],
             False,
             True,
+            "NHWC",
             TensorContentRandom(),
         ),
         (
-            [1, 16, 16, 32],
+            [1, 12, 12, 32],
             [3, 3],
             [1, 1],
             [1, 1],
             [0, 0, 0, 0],
             False,
             True,
+            "NHWC",
             TensorContentRandom(),
         ),
         (
-            [1, 8, 8, 32],
-            [8, 8],
+            [1, 32, 14, 14],
+            [3, 3],
             [1, 1],
             [1, 1],
             [0, 0, 0, 0],
             False,
             True,
+            "NCHW",
             TensorContentRandom(),
         ),
-        # Test non-one stride and dilation
         (
-            [1, 8, 8, 32],
-            [3, 3],
-            [2, 3],
+            [1, 32, 15, 15],
+            [8, 8],
+            [1, 1],
             [1, 1],
             [0, 0, 0, 0],
             False,
             True,
+            "NCHW",
             TensorContentRandom(),
         ),
+        # Test non-one stride and dilation with different layouts
         (
-            [1, 8, 8, 32],
+            [1, 18, 24, 32],
             [3, 3],
-            [2, 2],
+            [2, 3],
             [2, 2],
             [0, 0, 0, 0],
             False,
             True,
+            "NHWC",
             TensorContentRandom(),
         ),
         (
-            [1, 8, 8, 32],
-            [3, 3],
+            [1, 32, 18, 18],
+            [5, 5],
             [2, 2],
             [2, 3],
             [0, 0, 0, 0],
             False,
             True,
+            "NCHW",
             TensorContentRandom(),
         ),
-        # Test non-zero padding
+        # Test non-zero padding with count include and exclude pad and 
different layouts
         (
-            [1, 8, 8, 32],
+            [1, 6, 6, 32],
             [3, 3],
             [1, 1],
             [1, 1],
             [1, 1, 1, 1],
             False,
-            True,
+            False,
+            "NHWC",
             TensorContentRandom(),
         ),
         (
             [1, 8, 8, 32],
             [3, 3],
-            [1, 1],
-            [1, 1],
-            [1, 2, 3, 4],
+            [1, 2],
+            [2, 3],
+            [2, 2, 3, 3],
             False,
-            True,
+            False,
+            "NHWC",
             TensorContentRandom(),
         ),
         (
-            [1, 8, 8, 32],
+            [1, 32, 6, 6],
             [3, 3],
             [1, 1],
             [1, 1],
             [1, 2, 3, 4],
             False,
-            True,
+            False,
+            "NCHW",
             TensorContentRandom(),
         ),
         (
-            [1, 8, 8, 32],
+            [1, 32, 15, 22],
             [3, 3],
             [3, 2],
             [2, 3],
             [1, 2, 3, 4],
             False,
-            True,
+            False,
+            "NCHW",
             TensorContentRandom(),
         ),
         # Test n11c-1024c-2d layout which will require input and output to 
have different layout
@@ -214,6 +243,7 @@ class TestAvgPool2dSlice:
             [0, 0, 0, 0],
             False,
             True,
+            "NHWC",
             TensorContentRandom(),
         ),
         (
@@ -221,9 +251,21 @@ class TestAvgPool2dSlice:
             [6, 6],
             [1, 1],
             [1, 1],
-            [0, 0, 0, 0],
+            [2, 2, 2, 2],
             False,
-            True,
+            False,
+            "NHWC",
+            TensorContentRandom(),
+        ),
+        (
+            [1, 1, 1, 2048],
+            [4, 4],
+            [2, 2],
+            [2, 3],
+            [0, 2, 1, 4],
+            False,
+            False,
+            "NHWC",
             TensorContentRandom(),
         ),
         (
@@ -234,23 +276,24 @@ class TestAvgPool2dSlice:
             [0, 0, 0, 0],
             False,
             True,
+            "NHWC",
             TensorContentRandom(),
         ),
         (
-            [1, 1, 1, 2048],
+            [1, 2048, 1, 1],
             [4, 4],
             [2, 2],
             [2, 3],
             [0, 0, 0, 0],
             False,
             True,
+            "NCHW",
             TensorContentRandom(),
         ),
     ]
 
     _param_ids = get_multitest_ids(_multitest_params, _param_descs)
 
-    # NOTE: input_layout is always assumed to be "nhwc-8h2w32c2w-2d"
     (
         output_shape,
         kernel,
@@ -259,23 +302,17 @@ class TestAvgPool2dSlice:
         padding,
         ceil_mode,
         count_include_pad,
+        op_layout,
         input_tensor_populator,
     ) = tvm.testing.parameters(*_multitest_params, ids=_param_ids)
 
     @tvm.testing.fixture
     def expected_output_np(
-        self,
-        input_np,
-        kernel,
-        stride,
-        dilation,
-        padding,
-        ceil_mode,
-        count_include_pad,
+        self, input_np, kernel, stride, dilation, padding, ceil_mode, 
count_include_pad, op_layout
     ):
         pad_before = padding[:2]
         pad_after = padding[2:]
-        ref_np = tvm.topi.testing.poolnd_python(
+        ref_np = poolnd_python(
             input_np,
             kernel,
             stride,
@@ -285,150 +322,129 @@ class TestAvgPool2dSlice:
             "avg",  # pool_type
             count_include_pad,
             False,  # ceil_mode,
-            layout="NHWC",
+            layout=op_layout,
         )
         return ref_np
 
     @tvm.testing.fixture
-    def input_shape(self, output_shape, kernel, padding, stride, dilation, 
output_layout):
+    def input_shape(
+        self, output_shape, kernel, padding, stride, dilation, op_layout, 
output_layout
+    ):
         # Input shape without any padding; 'ceil' is being ignored from 
calculation:
-        o_b, o_h, o_w, o_c = output_shape
+        if op_layout == "NHWC":
+            o_b, o_h, o_w, o_c = output_shape
+        else:
+            o_b, o_c, o_h, o_w = output_shape
         d_h, d_w = dilation
         s_h, s_w = stride
         k_h, k_w = kernel
         pad_before_h, pad_before_w = padding[:2]
         pad_after_h, pad_after_w = padding[2:]
 
-        if output_layout == "n11c-1024c-2d":
-            assert (
-                pad_before_w == 0 and pad_after_w == 0 and pad_before_h == 0 
and pad_after_h == 0
-            ), "Padding must be zero for n11c-1024c-2d layout"
+        if (
+            output_layout == "n11c-2048c-2d"
+            or output_layout == "nc11-2048c-2d"
+            or output_layout == "n11c-1024c-2d"
+            or output_layout == "nc11-1024c-2d"
+        ):
             assert o_h == 1 and o_w == 1, "Output height and width must be 1"
 
         in_h = (o_h - 1) * s_h + d_h * (k_h - 1) + 1 - pad_before_h - 
pad_after_h
         in_w = (o_w - 1) * s_w + d_w * (k_w - 1) + 1 - pad_before_w - 
pad_after_w
 
-        return [o_b, in_h, in_w, o_c]
-
-    @tvm.testing.fixture
-    def input_shape_padded(self, input_shape, padding, output_layout, dtype):
-        # Input shape is adjusted to account for 'padding'. Also, due to the 
physical
-        # layout of the buffer, height and width are adjusted so that they are 
a
-        # multiple of the buffer size dictated by the layout.
-        # NOTE: For float16, the input layout is always assumed to be 
nhwc-8h2w32c2w-2d and
-        # for int8/uint8, it's nhwc-8h8w32c-2d.
-        # For both nhwc-8h2w32c2w-2d and nhwc-8h8w32c-2d, the height should be 
a multiple
-        # of 8. However, the width should be a multiple of 4 for the first 
case and 8 for
-        # the second case.
-
-        height_mult = 8
-        if dtype == "float16":
-            width_mult = 4  # input layout : nhwc-8h2w32c2w-2d
-        elif dtype in ("uint8", "int8"):
-            width_mult = 8  # input layout : nhwc-8h8w32c-2d
+        if op_layout == "NHWC":
+            return [o_b, in_h, in_w, o_c]
         else:
-            raise RuntimeError(f"Unsupport dtype '{dtype}'")
-
-        pad_before_h, pad_before_w = padding[:2]
-        pad_after_h, pad_after_w = padding[2:]
-        padded_input_height = (
-            (input_shape[1] + pad_before_h + pad_after_h + height_mult - 1) // 
height_mult
-        ) * height_mult
-        padded_input_width = (
-            (input_shape[2] + pad_before_w + pad_after_w + width_mult - 1) // 
width_mult
-        ) * width_mult
-        return [input_shape[0], padded_input_height, padded_input_width, 
input_shape[3]]
-
-    @tvm.testing.fixture
-    def input_np_padded(self, input_np, input_shape, input_shape_padded, 
padding):
-        pad_before_h, pad_before_w = padding[:2]
-        pad_after_h = input_shape_padded[1] - input_shape[1] - pad_before_h
-        pad_after_w = input_shape_padded[2] - input_shape[2] - pad_before_w
-        input_padded = np.pad(
-            input_np,
-            ((0, 0), (pad_before_h, pad_after_h), (pad_before_w, pad_after_w), 
(0, 0)),
-            "constant",
-        )
-        return input_padded
+            return [o_b, o_c, in_h, in_w]
 
     @tvm.testing.fixture
     def schedule_args(
         self,
-        stride,
         kernel,
-        dtype,
+        stride,
+        padding,
         dilation,
-        input_layout,
+        count_include_pad,
         output_layout,
         output_shape,
-        input_shape_padded,
-        transformed_input_np_padded,
-        transformed_expected_output_np,
+        input_np,
+        input_shape,
+        input_layout,
+        expected_output_np,
+        dtype,
+        op_layout,
     ):
-        """
-        Construct schedule args based on dtype
-        """
-        A = te.placeholder(input_shape_padded, name="A", dtype=dtype)
-
+        """Construct schedule args based on dtype"""
+        A = te.placeholder(input_shape, name="A", dtype=dtype)
         if dtype == "float16":
-            M = sl.avg_pool2d_compute(A, kernel, stride, dilation, 
output_shape)
+            if op_layout == "NHWC":
+                M = sl.avg_pool2d_NHWC(
+                    A, kernel, stride, padding, dilation, count_include_pad, 
output_shape
+                )
+            elif op_layout == "NCHW":
+                M = sl.avg_pool2d_NCHW(
+                    A, kernel, stride, padding, dilation, count_include_pad, 
output_shape
+                )
+            else:
+                raise RuntimeError(f"Unsupported layout for 
slice_op.avg_pool2d '{op_layout}'")
             tir_schedule = sl.avg_pool2d_schedule(M, A, output_layout, 
input_layout)
         elif dtype in ("uint8", "int8"):
-            in_data, in_scale, in_zero_point = transformed_input_np_padded
-            _, out_scale, out_zero_point = transformed_expected_output_np
-            M = qn.qnn_avg_pool2d_compute(
-                A,
-                kernel,
-                stride,
-                dilation,
-                output_shape,
-                dtype,
-                in_zero_point,
-                in_scale,
-                out_zero_point,
-                out_scale,
-            )
+            _, in_scale, in_zero_point = quantize_np(input_np, dtype)
+            _, out_scale, out_zero_point = quantize_np(expected_output_np, 
dtype)
+            if op_layout == "NHWC":
+                M = qn.qnn_avg_pool2d_NHWC(
+                    A,
+                    kernel,
+                    stride,
+                    padding,
+                    dilation,
+                    count_include_pad,
+                    output_shape,
+                    dtype,
+                    in_scale,
+                    in_zero_point,
+                    out_scale,
+                    out_zero_point,
+                )
+            elif op_layout == "NCHW":
+                M = qn.qnn_avg_pool2d_NCHW(
+                    A,
+                    kernel,
+                    stride,
+                    padding,
+                    dilation,
+                    count_include_pad,
+                    output_shape,
+                    dtype,
+                    in_scale,
+                    in_zero_point,
+                    out_scale,
+                    out_zero_point,
+                )
+            else:
+                raise RuntimeError(f"Unsupported layout for qnn.avg_pool2d 
'{op_layout}'")
+
             tir_schedule = qn.qnn_avg_pool2d_schedule(M, A, output_layout, 
input_layout)
 
         return [tir_schedule.mod, [A, M]]
 
     @tvm.testing.requires_hexagon
     def test_avg_pool2d_slice(
-        self,
-        dtype,
-        output_layout,
-        output_shape,
-        transformed_input_np_padded,
-        transformed_expected_output_np,
-        schedule_args,
-        hexagon_session: Session,
+        self, dtype, input_np, expected_output_np, schedule_args, 
hexagon_session: Session
     ):
-        in_data = transformed_input_np_padded
-
+        print("schedule_args : ", schedule_args)
         with tvm.transform.PassContext(opt_level=3):
-            func = tvm.build(
-                *schedule_args,
-                get_hexagon_target("v69"),
-                name="avg_pool2d",
-            )
-
-        input_axis_separator = [4]
-        if output_layout in (
-            "nhwc-8h2w32c2w-2d",
-            "nhwc-8h8w32c-2d",
-            "n11c-1024c-2d",
-            "n11c-2048c-2d",
-        ):
-            output_axis_separator = [4]
-        else:
-            raise RuntimeError(f"Unexpected layout '{output_layout}'")
+            func = tvm.build(*schedule_args, get_hexagon_target("v69"), 
name="avg_pool2d")
+
+        input_axis_separator = []
+        output_axis_separator = []
 
         if dtype == "float16":
-            in_data_np = transformed_input_np_padded
-            out_data_np = transformed_expected_output_np
+            in_data_np = input_np
+            out_data_np = expected_output_np
         elif dtype in ("uint8", "int8"):
-            in_data_np, _, _ = transformed_input_np_padded
-            out_data_np, _, _ = transformed_expected_output_np
+            in_data_np, _, _ = quantize_np(input_np, dtype)
+            out_data_np, _, _ = quantize_np(expected_output_np, dtype)
         else:
             raise RuntimeError(f"Unsupport dtype '{dtype}'")
 
@@ -436,34 +452,26 @@ class TestAvgPool2dSlice:
             hexagon_session.device,
             data=in_data_np,
             axis_separators=input_axis_separator,
-            mem_scope="global.vtcm",
+            mem_scope="global.ddr",
         )
         output_arr = allocate_hexagon_array(
             hexagon_session.device,
             out_data_np.shape,
             dtype,
             axis_separators=output_axis_separator,
-            mem_scope="global.vtcm",
+            mem_scope="global.ddr",
         )
 
         mod = hexagon_session.load_module(func)
 
         mod(input_arr, output_arr)
-        b, h, w, c = output_shape
-        if output_layout == "nhwc-8h2w32c2w-2d":
-            output_np = output_arr.numpy().reshape([b, h // 8, w // 4, c // 
32, 8, 2, 32, 2])
-        elif output_layout == "nhwc-8h8w32c-2d":
-            output_np = output_arr.numpy().reshape([b, h // 8, w // 8, c // 
32, 8, 8, 32])
-        elif output_layout == "n11c-2048c-2d":
-            output_np = output_arr.numpy().reshape([b, 1, 1, c // 2048, 2048])
-        elif output_layout == "n11c-1024c-2d":
-            output_np = output_arr.numpy().reshape([b, 1, 1, c // 1024, 1024])
-        else:
-            raise RuntimeError(f"Unexpected layout '{output_layout}'")
+
+        output_np = output_arr.numpy()
         if dtype == "float16":
             np.testing.assert_allclose(output_np, out_data_np, rtol=1e-3, 
atol=1e-3)
         else:
-            np.testing.assert_allclose(output_np, out_data_np, rtol=1, atol=1)
+            output_np = output_arr.numpy()
+            np.testing.assert_allclose(output_np, out_data_np, rtol=0, atol=2)
 
 
 if __name__ == "__main__":

Reply via email to