blob: 4718fa7e06713d6f78fd0ea0139fe2500988688e [file]
# 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
"""Hexagon testing infrastructure"""
import numpy
import tvm
from tvm import te
def ceildiv(o, d):
assert o >= 0
assert d >= 0
return tvm.tir.floordiv(o + d - 1, d)
# defines inner block shape: 8h8w32c
def get_block_shape():
return 8, 8, 32
# defines inner filter block shape: 8i32o41
def get_filter_block_shape():
return 8, 32, 4
# input: locgical shape in nhwc layout
# output: physical packed shape in nhw8h8w32c layout
def get_packed_shape(logical_shape_nhwc):
assert len(logical_shape_nhwc) == 4
physical_shape_nhwc8h8w32c = [logical_shape_nhwc[0]]
block_shape = get_block_shape()
off_h, off_w, off_c = block_shape
physical_shape_nhwc8h8w32c.append(ceildiv(logical_shape_nhwc[1], off_h))
physical_shape_nhwc8h8w32c.append(ceildiv(logical_shape_nhwc[2], off_w))
physical_shape_nhwc8h8w32c.append(ceildiv(logical_shape_nhwc[3], off_c))
physical_shape_nhwc8h8w32c.extend(block_shape)
return physical_shape_nhwc8h8w32c
# input: physical packed shape in nhw8h8w32c layout
# output: logical shape in nhwc layout
def get_logical_shape(physical_shape_nhwc8h8w32c):
assert len(physical_shape_nhwc8h8w32c) == 7
logical_shape_nhwc = [physical_shape_nhwc8h8w32c[0]]
logical_shape_nhwc.append(physical_shape_nhwc8h8w32c[1] * physical_shape_nhwc8h8w32c[4])
logical_shape_nhwc.append(physical_shape_nhwc8h8w32c[2] * physical_shape_nhwc8h8w32c[5])
logical_shape_nhwc.append(physical_shape_nhwc8h8w32c[3] * physical_shape_nhwc8h8w32c[6])
return logical_shape_nhwc
def get_packed_filter_shape(logical_shape_oihw):
"""return packed filter shape
Parameters
----------
logical_shape_oihw :
logical shape in oihw layout
Returns
-------
physical_shape_oihw8i32o4i :
physical packed shape in oihw8i3204i layout
"""
assert len(logical_shape_oihw) == 4
filter_block_shape = get_filter_block_shape()
filter_Cio, filter_Ki, filter_Cii = filter_block_shape
filter_Ci = filter_Cio * filter_Cii
physical_shape_oihw8i32o4i = []
physical_shape_oihw8i32o4i.append(int(ceildiv(logical_shape_oihw[0], filter_Ki)))
physical_shape_oihw8i32o4i.append(int(ceildiv(logical_shape_oihw[1], filter_Ci)))
physical_shape_oihw8i32o4i.append(logical_shape_oihw[2])
physical_shape_oihw8i32o4i.append(logical_shape_oihw[3])
physical_shape_oihw8i32o4i.extend(filter_block_shape)
return physical_shape_oihw8i32o4i
def build_and_run(inputs, func, target: str, target_host: str, *args, **kwargs):
"""build and run the function func"""
schedule, placeholders, binds = func(*args, **kwargs)
func = tvm.compile(
schedule, placeholders, target=tvm.target.Target(target, host=target_host), binds=binds
)
dev = tvm.device(target)
tensors = []
for tensor in inputs:
tensors.append(tvm.runtime.tensor(tensor, dev))
tensors.append(
tvm.runtime.tensor(
numpy.zeros([i.value for i in placeholders[-1].shape], dtype=placeholders[-1].dtype),
dev,
)
)
func(*tensors)
return tensors[-1].numpy()
def run_module(mod, inputs):
"""invokes run function of specified module with inputs provided"""
mod.set_input(**inputs)
mod.run()
output = mod.get_output(0).numpy()
return output
def get_conv2d_nhwc_shape(shape_nhwc, kernel_size, strides, padding, dilation, out_channels):
assert len(shape_nhwc) == 4
kernel = []
kernel.append((kernel_size[0] - 1) * dilation[0] + 1)
kernel.append((kernel_size[1] - 1) * dilation[1] + 1)
return (
shape_nhwc[0],
(shape_nhwc[1] - kernel[0] + padding[0] + padding[1]) // strides[0] + 1,
(shape_nhwc[2] - kernel[1] + padding[2] + padding[3]) // strides[1] + 1,
out_channels,
)
def conv2d_verify(output, ref_output, dtype):
"""transpose and reshape output and compare with ref_output"""
# nhwc8h8w32c -> nhwc
logical_output_shape = get_logical_shape(output.shape)
output = output.transpose(0, 1, 4, 2, 5, 3, 6).reshape(logical_output_shape)
# slice output to match ref_output shape
# e.g. 8x8 spatial 3x3 filter = 6x6 ref output
# but still 8x8 output given the blocked layout
output = output[
0 : ref_output.shape[0] : 1,
0 : ref_output.shape[1] : 1,
0 : ref_output.shape[2] : 1,
0 : ref_output.shape[3] : 1,
]
if "int" in dtype:
tol = {"atol": 0, "rtol": 0}
elif dtype == "float32":
tol = {"rtol": 1e-4, "atol": 2e-4}
tvm.testing.assert_allclose(output, ref_output, **tol)
def conv2d_compute(X, filt, pad, stride, dilation):
"""Define conv2d compute"""
block_shape = get_block_shape()
block_H, block_W, block_C = block_shape
filter_c_io, _, filter_c_ii = get_filter_block_shape()
filter_c_i = filter_c_io * filter_c_ii
shape_filter = filt.shape
kernel_size = tuple(shape_filter[2:4])
out_channels = shape_filter[0] * shape_filter[5]
logical_input_shape = get_logical_shape(X.shape)
logical_output_shape = get_conv2d_nhwc_shape(
logical_input_shape,
kernel_size,
stride,
pad,
dilation,
out_channels,
)
output_shape = get_packed_shape(logical_output_shape)
rh = te.reduce_axis((0, kernel_size[0]), name="rh")
rw = te.reduce_axis((0, kernel_size[1]), name="rw")
rc = te.reduce_axis((0, logical_input_shape[3]), name="rc")
def compute(n, ho, wo, ko, hi, wi, ki):
h = ho * block_H + hi
h_contig = h * stride[0] + rh
h_block_id = h_contig // block_H
h_block_offset = h_contig % block_H
w = wo * block_W + wi
w_contig = w * stride[1] + rw
w_block_id = w_contig // block_W
w_block_offset = w_contig % block_W
c_block_id = rc // block_C
c_block_offset = rc % block_C
rco = rc // filter_c_i
rcio = (rc % filter_c_i) // filter_c_ii
rcii = rc % filter_c_ii
return te.sum(
X[
n,
h_block_id,
w_block_id,
c_block_id,
h_block_offset,
w_block_offset,
c_block_offset,
]
* filt[ko, rco, rh, rw, rcio, ki, rcii],
axis=[rh, rw, rc],
)
return output_shape, compute
def transform_numpy(arr_np, current_layout: str, new_layout: str):
"""Reshape and transpose numpy array according to the specified layout"""
if current_layout == "nhwc":
if new_layout == "nhwc":
return arr_np
if new_layout in ["nhwc-8h2w32c2w-2d", "nhwc-8h2w32c2w-1d"]:
n, h, w, c = arr_np.shape
return arr_np.reshape([n, h // 8, 8, w // 4, 2, 2, c // 32, 32]).transpose(
0, 1, 3, 6, 2, 4, 7, 5
)
if new_layout in ["nhwc-4h2w32c2w-2d"]:
n, h, w, c = arr_np.shape
return arr_np.reshape([n, h // 4, 4, w // 4, 2, 2, c // 32, 32]).transpose(
0, 1, 3, 6, 2, 4, 7, 5
)
if new_layout in ["n11c-1024c-2d", "n11c-1024c-1d"]:
n, h, w, c = arr_np.shape
assert h == 1 and w == 1, "The size of h and w must be 1"
return arr_np.reshape([n, 1, 1, c // 1024, 1024])
if new_layout == "nc-1024-2d":
n, c = arr_np.shape
return arr_np.reshape([n, c // 1024, 1024])
if new_layout == "nhwc-1024c-2d":
N, H, W, C = arr_np.shape
return arr_np.reshape([N, H, W, C // 1024, 1024])
if new_layout == "nc-2048-2d":
N, C = arr_np.shape
return arr_np.reshape([N, C // 2048, 2048])
if new_layout == "nhwc-2048c-2d":
N, H, W, C = arr_np.shape
return arr_np.reshape([N, H, W, C // 2048, 2048])
if new_layout == "nhwc-8h8w32c-2d":
n, h, w, c = arr_np.shape
return arr_np.reshape([n, h // 8, 8, w // 8, 8, c // 32, 32]).transpose(
0, 1, 3, 5, 2, 4, 6
)
if new_layout == "n11c-2048c-2d":
n, h, w, c = arr_np.shape
assert h == 1 and w == 1, "The size of h and w must be 1"
return arr_np.reshape([n, h, w, c // 2048, 2048])
raise RuntimeError(f"Unexpected new_layout '{new_layout}'")
if current_layout == "nc":
n, c = arr_np.shape
if new_layout in ["nc-2048c-1d"]:
return arr_np.reshape([n, c // 2048, 2048])
if new_layout in ["nc-2048c-2d"]:
return arr_np.reshape([n, c // 2048, 2048])
if new_layout in ["nc-1024c-2d"]:
return arr_np.reshape([n, c // 1024, 1024])
if new_layout in ["nc-1024c-1d"]:
return arr_np.reshape([n, c // 1024, 1024])
if new_layout in ["nc-512c-2d"]:
return arr_np.reshape([n, c // 512, 512])
if new_layout in ["nc-2048c-2d"]:
return arr_np.reshape([n, c // 2048, 2048])
raise RuntimeError(f"Unexpected new_layout '{new_layout}'")
if current_layout == "nhw":
if new_layout in ["nhw-32h16w-2d"]:
n, h, w = arr_np.shape
return arr_np.reshape([n, h // 32, 32, w // 16, 16]).transpose(0, 1, 3, 2, 4)
raise RuntimeError(f"Unexpected new_layout '{new_layout}'")
if current_layout == "ncw":
if new_layout == "ncw":
return arr_np
if new_layout in ["ncw-32c64w-2d"]:
n, c, w = arr_np.shape
return arr_np.reshape([n, c // 32, 32, w // 64, 64]).transpose(0, 1, 3, 2, 4)
raise RuntimeError(f"Unexpected new_layout '{new_layout}'")
if current_layout == "nchw":
if new_layout in ["nchw-32c8h8w-2d", "nchw-32c8h8w-1d"]:
n, c, h, w = arr_np.shape
return arr_np.reshape([n, c // 32, 32, h // 8, 8, w // 8, 8]).transpose(
0, 1, 3, 5, 2, 4, 6
)
if new_layout in ["nchw-32c8h4w-2d", "nchw-32c8h4w-1d"]:
n, c, h, w = arr_np.shape
return arr_np.reshape([n, c // 32, 32, h // 8, 8, w // 4, 4]).transpose(
0, 1, 3, 5, 2, 4, 6
)
raise RuntimeError(f"Unexpected new_layout '{new_layout}'")
raise RuntimeError(f"Unexpected current_layout '{current_layout}'")
def quantize_np(arr_np: numpy.ndarray, dtype: str):
"""
Returns quantized array along with scale and zero-point
Parameters
----------
arr_np: numpy.ndarray
Input numpy array to be quantized
dtype: str
dtype of the quantized array: "uint8", "int8", etc
Returns
-------
quant_np: numpy.ndarray
Quantized numpy array
scale: float
Scale
zero_point: int
Value corresponding to float 0
"""
if dtype == "uint8":
qmax = 255
qmin = 0
elif dtype == "int8":
qmax = 127
qmin = -128
else:
raise RuntimeError(f"Unsupported quantized data type '{dtype}'")
fmin = numpy.amin(arr_np)
fmax = numpy.amax(arr_np)
# Include floating-point zero in the range
if fmax < 0:
fmax = 0.0
elif fmin > 0:
fmin = 0.0
scale = (fmax - fmin) / (qmax - qmin)
zero_point = numpy.rint((fmax * qmin - fmin * qmax) / (fmax - fmin)).astype("int32")
quant_np = numpy.clip(((arr_np / scale).round() + zero_point), qmin, qmax).astype(dtype)
return quant_np, scale, zero_point
def get_hexagon_target(cpu_ver: str, **kwargs) -> tvm.target.Target:
"""Creates a Hexagon target"""
target = tvm.target.hexagon(cpu_ver, **kwargs)
return tvm.target.Target(target, host=target)