1#!/usr/bin/env python 2# Copyright 2020 Google LLC 3# 4# This source code is licensed under the BSD-style license found in the 5# LICENSE file in the root directory of this source tree. 6 7import argparse 8import codecs 9import os 10import re 11import sys 12import yaml 13 14sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) 15from primes import next_prime 16import xngen 17import xnncommon 18 19 20parser = argparse.ArgumentParser( 21 description='Test generator for DWCONV2D CHW micro-kernels') 22parser.add_argument("-s", "--spec", metavar="FILE", required=True, 23 help="Spec (YAML) file") 24parser.add_argument("-o", "--output", metavar="FILE", required=True, 25 help='Output (C++ source) file') 26parser.set_defaults(defines=list()) 27 28 29TEST_TEMPLATE = """\ 30$if SUBSAMPLING == 1: 31 TEST(${TEST_NAME}, output_width_eq_${WIDTH_TILE}) { 32 $if ISA_CHECK: 33 ${ISA_CHECK}; 34 DWConv2DMicrokernelTester() 35 .input_width(${(WIDTH_TILE - 1) * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}) 36 .input_height(${HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING - 1}) 37 .kernel_height(${KERNEL_HEIGHT}) 38 .kernel_width(${KERNEL_WIDTH}) 39 .subsampling(${SUBSAMPLING}) 40 .padding_left(${PADDING}) 41 .padding_right(${PADDING}) 42 .padding_top(${PADDING}) 43 .padding_bottom(${PADDING}) 44 .Test(${", ".join(TEST_ARGS)}); 45 } 46$else: 47 TEST(${TEST_NAME}, output_width_eq_${WIDTH_TILE}) { 48 $if ISA_CHECK: 49 ${ISA_CHECK}; 50 for (size_t input_width = ${(WIDTH_TILE - 1) * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}; input_width < ${WIDTH_TILE * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}; input_width++) { 51 DWConv2DMicrokernelTester() 52 .input_width(input_width) 53 .input_height(${HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING - 1}) 54 .kernel_height(${KERNEL_HEIGHT}) 55 .kernel_width(${KERNEL_WIDTH}) 56 .subsampling(${SUBSAMPLING}) 57 .padding_left(${PADDING}) 58 .padding_right(${PADDING}) 59 .padding_top(${PADDING}) 60 .padding_bottom(${PADDING}) 61 .Test(${", ".join(TEST_ARGS)}); 62 } 63 } 64 65$if WIDTH_TILE > 1: 66 TEST(${TEST_NAME}, output_width_div_${WIDTH_TILE}) { 67 $if ISA_CHECK: 68 ${ISA_CHECK}; 69 for (size_t input_width = ${2 * WIDTH_TILE * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING - 1}; input_width < ${8 * WIDTH_TILE * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING - 1}; input_width += ${WIDTH_TILE * SUBSAMPLING}) { 70 DWConv2DMicrokernelTester() 71 .input_width(input_width) 72 .input_height(${HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING - 1}) 73 .kernel_height(${KERNEL_HEIGHT}) 74 .kernel_width(${KERNEL_WIDTH}) 75 .subsampling(${SUBSAMPLING}) 76 .padding_left(${PADDING}) 77 .padding_right(${PADDING}) 78 .padding_top(${PADDING}) 79 .padding_bottom(${PADDING}) 80 .Test(${", ".join(TEST_ARGS)}); 81 } 82 } 83 84 TEST(${TEST_NAME}, output_width_lt_${WIDTH_TILE}) { 85 $if ISA_CHECK: 86 ${ISA_CHECK}; 87 for (size_t input_width = ${max(1, KERNEL_WIDTH - 2 * PADDING)}; input_width < ${(WIDTH_TILE - 1) * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}; input_width++) { 88 DWConv2DMicrokernelTester() 89 .input_width(${WIDTH_TILE * SUBSAMPLING}) 90 .input_height(${HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING - 1}) 91 .kernel_height(${KERNEL_HEIGHT}) 92 .kernel_width(${KERNEL_WIDTH}) 93 .subsampling(${SUBSAMPLING}) 94 .padding_left(${PADDING}) 95 .padding_right(${PADDING}) 96 .padding_top(${PADDING}) 97 .padding_bottom(${PADDING}) 98 .Test(${", ".join(TEST_ARGS)}); 99 } 100 } 101 102TEST(${TEST_NAME}, output_width_gt_${WIDTH_TILE}) { 103 $if ISA_CHECK: 104 ${ISA_CHECK}; 105 for (size_t input_width = ${WIDTH_TILE * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}; input_width < ${(5 if WIDTH_TILE == 1 else 2) * WIDTH_TILE * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}; input_width++) { 106 DWConv2DMicrokernelTester() 107 .input_width(input_width) 108 .input_height(${HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING - 1}) 109 .kernel_height(${KERNEL_HEIGHT}) 110 .kernel_width(${KERNEL_WIDTH}) 111 .subsampling(${SUBSAMPLING}) 112 .padding_left(${PADDING}) 113 .padding_right(${PADDING}) 114 .padding_top(${PADDING}) 115 .padding_bottom(${PADDING}) 116 .Test(${", ".join(TEST_ARGS)}); 117 } 118} 119 120$if SUBSAMPLING > 1: 121 TEST(${TEST_NAME}, output_height_eq_${HEIGHT_TILE}) { 122 $if ISA_CHECK: 123 ${ISA_CHECK}; 124 for (size_t input_height = ${(HEIGHT_TILE - 1) * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING}; input_height < ${HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING}; input_height++) { 125 for (size_t input_width = 1; input_width < ${5 * WIDTH_TILE * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}; input_width += ${max(1, WIDTH_TILE * SUBSAMPLING - 1)}) { 126 DWConv2DMicrokernelTester() 127 .input_width(input_width) 128 .input_height(input_height) 129 .kernel_height(${KERNEL_HEIGHT}) 130 .kernel_width(${KERNEL_WIDTH}) 131 .subsampling(${SUBSAMPLING}) 132 .padding_left(${PADDING}) 133 .padding_right(${PADDING}) 134 .padding_top(${PADDING}) 135 .padding_bottom(${PADDING}) 136 .Test(${", ".join(TEST_ARGS)}); 137 } 138 } 139 } 140 141$if HEIGHT_TILE > 1: 142 TEST(${TEST_NAME}, output_height_div_${HEIGHT_TILE}) { 143 $if ISA_CHECK: 144 ${ISA_CHECK}; 145 for (size_t input_height = ${2 * HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING - 1}; input_height < ${8 * HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING - 1}; input_height += ${HEIGHT_TILE * SUBSAMPLING}) { 146 for (size_t input_width = 1; input_width < ${5 * WIDTH_TILE * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}; input_width += ${max(1, WIDTH_TILE * SUBSAMPLING - 1)}) { 147 DWConv2DMicrokernelTester() 148 .input_width(input_width) 149 .input_height(input_height) 150 .kernel_height(${KERNEL_HEIGHT}) 151 .kernel_width(${KERNEL_WIDTH}) 152 .subsampling(${SUBSAMPLING}) 153 .padding_left(${PADDING}) 154 .padding_right(${PADDING}) 155 .padding_top(${PADDING}) 156 .padding_bottom(${PADDING}) 157 .Test(${", ".join(TEST_ARGS)}); 158 } 159 } 160 } 161 162 TEST(${TEST_NAME}, output_height_lt_${HEIGHT_TILE}) { 163 $if ISA_CHECK: 164 ${ISA_CHECK}; 165 for (size_t input_height = ${max(1, KERNEL_HEIGHT - 2 * PADDING)}; input_height < ${(HEIGHT_TILE - 1) * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING}; input_height++) { 166 for (size_t input_width = 1; input_width < ${5 * WIDTH_TILE * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}; input_width += ${max(1, WIDTH_TILE * SUBSAMPLING - 1)}) { 167 DWConv2DMicrokernelTester() 168 .input_width(input_width) 169 .input_height(input_height) 170 .kernel_height(${KERNEL_HEIGHT}) 171 .kernel_width(${KERNEL_WIDTH}) 172 .subsampling(${SUBSAMPLING}) 173 .padding_left(${PADDING}) 174 .padding_right(${PADDING}) 175 .padding_top(${PADDING}) 176 .padding_bottom(${PADDING}) 177 .Test(${", ".join(TEST_ARGS)}); 178 } 179 } 180 } 181 182TEST(${TEST_NAME}, output_height_gt_${HEIGHT_TILE}) { 183 $if ISA_CHECK: 184 ${ISA_CHECK}; 185 for (size_t input_height = ${HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING}; input_height < ${(5 if WIDTH_TILE == 1 else 2) * HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING}; input_height++) { 186 for (size_t input_width = 1; input_width < ${5 * WIDTH_TILE * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}; input_width += ${max(1, WIDTH_TILE * SUBSAMPLING - 1)}) { 187 DWConv2DMicrokernelTester() 188 .input_width(input_width) 189 .input_height(input_height) 190 .kernel_height(${KERNEL_HEIGHT}) 191 .kernel_width(${KERNEL_WIDTH}) 192 .subsampling(${SUBSAMPLING}) 193 .padding_left(${PADDING}) 194 .padding_right(${PADDING}) 195 .padding_top(${PADDING}) 196 .padding_bottom(${PADDING}) 197 .Test(${", ".join(TEST_ARGS)}); 198 } 199 } 200} 201 202$if SUBSAMPLING > 1: 203 TEST(${TEST_NAME}, padding_top_eq_${SUBSAMPLING - 1}) { 204 $if ISA_CHECK: 205 ${ISA_CHECK}; 206 for (size_t input_height = ${max(1, KERNEL_HEIGHT - 2 * PADDING + 1)}; input_height < ${3 * HEIGHT_TILE * SUBSAMPLING + KERNEL_HEIGHT - 2 * PADDING + 1}; input_height++) { 207 for (size_t input_width = 1; input_width < ${5 * WIDTH_TILE * SUBSAMPLING + KERNEL_WIDTH - 2 * PADDING}; input_width += ${max(1, WIDTH_TILE * SUBSAMPLING - 1)}) { 208 DWConv2DMicrokernelTester() 209 .input_width(input_width) 210 .input_height(input_height) 211 .kernel_height(${KERNEL_HEIGHT}) 212 .kernel_width(${KERNEL_WIDTH}) 213 .subsampling(${SUBSAMPLING}) 214 .padding_left(${PADDING}) 215 .padding_right(${PADDING}) 216 .padding_top(${PADDING - 1}) 217 .padding_bottom(${PADDING}) 218 .Test(${", ".join(TEST_ARGS)}); 219 } 220 } 221 } 222""" 223 224def split_ukernel_name(name): 225 match = re.fullmatch(r"xnn_(f16|f32)_dwconv2d_chw_ukernel_(\d+)x(\d+)(s2)?p(\d+)__(.+)_(\d+)x(\d+)(_acc\d+)?", name) 226 assert match is not None 227 kernel_height, kernel_width = int(match.group(2)), int(match.group(3)) 228 if match.group(4): 229 assert match.group(4).startswith("s") 230 stride = int(match.group(4)[1:]) 231 else: 232 stride = 1 233 padding = int(match.group(5)) 234 235 height_tile = int(match.group(7)) 236 width_tile = int(match.group(8)) 237 238 arch, isa = xnncommon.parse_target_name(target_name=match.group(6)) 239 return kernel_height, kernel_width, stride, padding, arch, isa, \ 240 height_tile, width_tile 241 242 243def generate_test_cases(ukernel, kernel_height, kernel_width, subsampling, \ 244 init_fn, padding, isa, height_tile, width_tile): 245 """Generates all tests cases for a DWCONV2D CHW micro-kernel. 246 247 Args: 248 ukernel: C name of the micro-kernel function. 249 kernel_height: convolution kernel height assumed by the micro-kernel. 250 kernel_width: convolution kernel width assumed by the micro-kernel. 251 subsampling: convolution subsampling (stride) assumed by the micro-kernel. 252 The same subsampling factor is assumed for both horizontal and 253 vertical directions. 254 init_fn: C name of the function to initialize microkernel parameters. 255 padding: convolution padding value assumed by the micro-kernel for right, 256 bottom, and left padding. If convolution stride is 1, the same 257 padding value is assumed for the top padding. If convolution stride 258 is different than 1, top padding is specified via micro-kernel 259 parameter, and can be either padding or (padding - 1). 260 isa: instruction set required to run the micro-kernel. Generated unit test 261 will skip execution if the host processor doesn't support this ISA. 262 height_tile: number of output rows processed in one iteration of the main 263 loop of the micro-kernel. 264 width_tile: number of output columns processed in one iteration of the main 265 loop of the micro-kernel. 266 267 Returns: 268 Code for the test case. 269 """ 270 _, test_name = ukernel.split("_", 1) 271 _, datatype, ukernel_type, _ = ukernel.split("_", 3) 272 test_args = [ukernel] 273 if init_fn: 274 test_args.append(init_fn) 275 if not isa: 276 test_args.append("DWConv2DMicrokernelTester::Variant::Scalar") 277 return xngen.preprocess(TEST_TEMPLATE, { 278 "TEST_NAME": test_name.upper().replace("UKERNEL_", ""), 279 "TEST_ARGS": test_args, 280 "UKERNEL_TYPE": ukernel_type.upper(), 281 "DATATYPE": datatype, 282 "KERNEL_HEIGHT": kernel_height, 283 "KERNEL_WIDTH": kernel_width, 284 "SUBSAMPLING": subsampling, 285 "PADDING": padding, 286 "HEIGHT_TILE": height_tile, 287 "WIDTH_TILE": width_tile, 288 "ISA_CHECK": xnncommon.generate_isa_check_macro(isa), 289 "next_prime": next_prime, 290 }) 291 292 293def main(args): 294 options = parser.parse_args(args) 295 296 with codecs.open(options.spec, "r", encoding="utf-8") as spec_file: 297 spec_yaml = yaml.safe_load(spec_file) 298 if not isinstance(spec_yaml, list): 299 raise ValueError("expected a list of micro-kernels in the spec") 300 301 tests = """\ 302// Copyright 2020 Google LLC 303// 304// This source code is licensed under the BSD-style license found in the 305// LICENSE file in the root directory of this source tree. 306// 307// Auto-generated file. Do not edit! 308// Specification: {specification} 309// Generator: {generator} 310 311 312#include <gtest/gtest.h> 313 314#include <xnnpack/common.h> 315#include <xnnpack/isa-checks.h> 316 317#include <xnnpack/dwconv.h> 318#include "dwconv2d-microkernel-tester.h" 319""".format(specification=options.spec, generator=sys.argv[0]) 320 321 for ukernel_spec in spec_yaml: 322 name = ukernel_spec["name"] 323 init_fn = ukernel_spec.get("init") 324 pipelined = bool(ukernel_spec.get("pipelined", False)) 325 assembly = bool(ukernel_spec.get("assembly", False)) 326 kernel_height, kernel_width, subsampling, padding, arch, isa, \ 327 height_tile, width_tile = split_ukernel_name(name) 328 329 # specification can override architecture 330 arch = ukernel_spec.get("arch", arch) 331 332 test_case = generate_test_cases(name, kernel_height, kernel_width, \ 333 subsampling, init_fn, padding, isa, \ 334 height_tile, width_tile) 335 tests += "\n\n" + xnncommon.postprocess_test_case(test_case, arch, isa, assembly) 336 337 txt_changed = True 338 if os.path.exists(options.output): 339 with codecs.open(options.output, "r", encoding="utf-8") as output_file: 340 txt_changed = output_file.read() != tests 341 342 if txt_changed: 343 with codecs.open(options.output, "w", encoding="utf-8") as output_file: 344 output_file.write(tests) 345 346 347if __name__ == "__main__": 348 main(sys.argv[1:]) 349