1#!/usr/bin/env python 2# Copyright 2019 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 math 10import os 11import re 12import sys 13import yaml 14 15sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) 16import xngen 17import xnncommon 18 19 20parser = argparse.ArgumentParser( 21 description='Vector binary operation microkernel test generator') 22parser.add_argument("-t", "--tester", metavar="TESTER", required=True, 23 choices=["VAddMicrokernelTester", "VAddCMicrokernelTester", 24 "VMulMicrokernelTester", "VMulCMicrokernelTester", 25 "VBinaryMicrokernelTester", "VBinaryCMicrokernelTester"], 26 help="Tester class to be used in the generated test") 27parser.add_argument("-s", "--spec", metavar="FILE", required=True, 28 help="Specification (YAML) file") 29parser.add_argument("-o", "--output", metavar="FILE", required=True, 30 help='Output (C++ source) file') 31parser.set_defaults(defines=list()) 32 33 34def split_ukernel_name(name): 35 match = re.fullmatch(r"xnn_(qu8|qs8|f16|f32)_v(add|div|max|min|mul|sqrdiff|sub|addc|divc|rdivc|maxc|minc|mulc|sqrdiffc|subc|rsubc)(_(minmax|relu)(_(fp32|rndnu))?)?_ukernel__(.+)_x(\d+)", name) 36 if match is None: 37 raise ValueError("Unexpected microkernel name: " + name) 38 op_type = { 39 "add": "Add", 40 "div": "Div", 41 "max": "Max", 42 "min": "Min", 43 "mul": "Mul", 44 "sqrdiff": "SqrDiff", 45 "sub": "Sub", 46 "addc": "AddC", 47 "divc": "DivC", 48 "rdivc": "RDivC", 49 "maxc": "MaxC", 50 "minc": "MinC", 51 "mulc": "MulC", 52 "sqrdiffc": "SqrDiffC", 53 "subc": "SubC", 54 "rsubc": "RSubC", 55 }[match.group(2)] 56 batch_tile = int(match.group(8)) 57 58 activation_type = match.group(4) 59 if activation_type is None: 60 activation_type = "LINEAR" 61 else: 62 activation_type = activation_type.upper() 63 64 requantization_type = match.group(6) 65 if not requantization_type: 66 requantization_type = None 67 else: 68 requantization_type = requantization_type.upper() 69 70 arch, isa = xnncommon.parse_target_name(target_name=match.group(7)) 71 return op_type, activation_type, requantization_type, batch_tile, arch, isa 72 73 74BINOP_TEST_TEMPLATE = """\ 75TEST(${TEST_NAME}, batch_eq_${BATCH_TILE}) { 76 $if ISA_CHECK: 77 ${ISA_CHECK}; 78 ${TESTER}() 79 .batch_size(${BATCH_TILE}) 80 .Test(${", ".join(TEST_ARGS)}); 81} 82 83$if BATCH_TILE > 1: 84 TEST(${TEST_NAME}, batch_div_${BATCH_TILE}) { 85 $if ISA_CHECK: 86 ${ISA_CHECK}; 87 for (size_t batch_size = ${BATCH_TILE*2}; batch_size < ${BATCH_TILE*10}; batch_size += ${BATCH_TILE}) { 88 ${TESTER}() 89 .batch_size(batch_size) 90 .Test(${", ".join(TEST_ARGS)}); 91 } 92 } 93 94 TEST(${TEST_NAME}, batch_lt_${BATCH_TILE}) { 95 $if ISA_CHECK: 96 ${ISA_CHECK}; 97 for (size_t batch_size = 1; batch_size < ${BATCH_TILE}; batch_size++) { 98 ${TESTER}() 99 .batch_size(batch_size) 100 .Test(${", ".join(TEST_ARGS)}); 101 } 102 } 103 104TEST(${TEST_NAME}, batch_gt_${BATCH_TILE}) { 105 $if ISA_CHECK: 106 ${ISA_CHECK}; 107 for (size_t batch_size = ${BATCH_TILE+1}; batch_size < ${10 if BATCH_TILE == 1 else BATCH_TILE*2}; batch_size++) { 108 ${TESTER}() 109 .batch_size(batch_size) 110 .Test(${", ".join(TEST_ARGS)}); 111 } 112} 113 114$if TESTER in ["VAddCMicrokernelTester", "VMulCMicrokernelTester", "VBinaryCMicrokernelTester"]: 115 TEST(${TEST_NAME}, inplace) { 116 $if ISA_CHECK: 117 ${ISA_CHECK}; 118 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 119 ${TESTER}() 120 .batch_size(batch_size) 121 .inplace(true) 122 .Test(${", ".join(TEST_ARGS)}); 123 } 124 } 125$else: 126 TEST(${TEST_NAME}, inplace_a) { 127 $if ISA_CHECK: 128 ${ISA_CHECK}; 129 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 130 ${TESTER}() 131 .batch_size(batch_size) 132 .inplace_a(true) 133 .Test(${", ".join(TEST_ARGS)}); 134 } 135 } 136 137 TEST(${TEST_NAME}, inplace_b) { 138 $if ISA_CHECK: 139 ${ISA_CHECK}; 140 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 141 ${TESTER}() 142 .batch_size(batch_size) 143 .inplace_b(true) 144 .Test(${", ".join(TEST_ARGS)}); 145 } 146 } 147 148 TEST(${TEST_NAME}, inplace_a_and_b) { 149 $if ISA_CHECK: 150 ${ISA_CHECK}; 151 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 152 ${TESTER}() 153 .batch_size(batch_size) 154 .inplace_a(true) 155 .inplace_b(true) 156 .Test(${", ".join(TEST_ARGS)}); 157 } 158 } 159 160$if DATATYPE.startswith("Q"): 161 TEST(${TEST_NAME}, a_zero_point) { 162 $if ISA_CHECK: 163 ${ISA_CHECK}; 164 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 165 for (int32_t a_zero_point = -128; a_zero_point <= 127; a_zero_point += 51) { 166 ${TESTER}() 167 .batch_size(batch_size) 168 .a_zero_point(a_zero_point) 169 .Test(${", ".join(TEST_ARGS)}); 170 } 171 } 172 } 173 174 TEST(${TEST_NAME}, b_zero_point) { 175 $if ISA_CHECK: 176 ${ISA_CHECK}; 177 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 178 for (int32_t b_zero_point = -128; b_zero_point <= 127; b_zero_point += 51) { 179 ${TESTER}() 180 .batch_size(batch_size) 181 .b_zero_point(b_zero_point) 182 .Test(${", ".join(TEST_ARGS)}); 183 } 184 } 185 } 186 187 TEST(${TEST_NAME}, y_zero_point) { 188 $if ISA_CHECK: 189 ${ISA_CHECK}; 190 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 191 for (int32_t y_zero_point = -128; y_zero_point <= 127; y_zero_point += 51) { 192 ${TESTER}() 193 .batch_size(batch_size) 194 .y_zero_point(y_zero_point) 195 .Test(${", ".join(TEST_ARGS)}); 196 } 197 } 198 } 199 200 TEST(${TEST_NAME}, a_scale) { 201 $if ISA_CHECK: 202 ${ISA_CHECK}; 203 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 204 for (float a_scale = 0.1f; a_scale <= 10.0f; a_scale *= 3.14f) { 205 ${TESTER}() 206 .batch_size(batch_size) 207 .a_scale(a_scale) 208 .Test(${", ".join(TEST_ARGS)}); 209 } 210 } 211 } 212 213 TEST(${TEST_NAME}, b_scale) { 214 $if ISA_CHECK: 215 ${ISA_CHECK}; 216 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 217 for (float b_scale = 0.1f; b_scale <= 10.0f; b_scale *= 3.14f) { 218 ${TESTER}() 219 .batch_size(batch_size) 220 .b_scale(b_scale) 221 .Test(${", ".join(TEST_ARGS)}); 222 } 223 } 224 } 225 226 TEST(${TEST_NAME}, y_scale) { 227 $if ISA_CHECK: 228 ${ISA_CHECK}; 229 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 230 for (float y_scale = 0.1f; y_scale <= 10.0f; y_scale *= 3.14f) { 231 ${TESTER}() 232 .batch_size(batch_size) 233 .y_scale(y_scale) 234 .Test(${", ".join(TEST_ARGS)}); 235 } 236 } 237 } 238 239$if ACTIVATION_TYPE == "MINMAX": 240 TEST(${TEST_NAME}, qmin) { 241 $if ISA_CHECK: 242 ${ISA_CHECK}; 243 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 244 ${TESTER}() 245 .batch_size(batch_size) 246 .qmin(128) 247 .Test(${", ".join(TEST_ARGS)}); 248 } 249 } 250 251 TEST(${TEST_NAME}, qmax) { 252 $if ISA_CHECK: 253 ${ISA_CHECK}; 254 for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) { 255 ${TESTER}() 256 .batch_size(batch_size) 257 .qmax(128) 258 .Test(${", ".join(TEST_ARGS)}); 259 } 260 } 261""" 262 263 264def generate_test_cases(ukernel, op_type, init_fn, activation_type, 265 requantization_type, tester, batch_tile, isa): 266 """Generates all tests cases for a Vector Binary Operation micro-kernel. 267 268 Args: 269 ukernel: C name of the micro-kernel function. 270 op_type: Operation type (ADD/MUL/SUB/etc). 271 init_fn: C name of the function to initialize microkernel parameters. 272 activation_type: Activation type (LINEAR/MINMAX/RELU). 273 requantization_type: Requantization type (FP32/RNDNU). 274 tester: C++ name of the tester class. 275 batch_tile: Number of batch elements processed per one iteration of the 276 inner loop of the micro-kernel. 277 isa: instruction set required to run the micro-kernel. Generated unit test 278 will skip execution if the host processor doesn't support this ISA. 279 280 Returns: 281 Code for the test case. 282 """ 283 _, test_name = ukernel.split("_", 1) 284 _, datatype, _ = ukernel.split("_", 2) 285 test_args = [ukernel] 286 if tester in ["VBinaryMicrokernelTester", "VBinaryCMicrokernelTester"]: 287 test_args.append("%s::OpType::%s" % (tester, op_type)) 288 if init_fn: 289 test_args.append(init_fn) 290 if requantization_type: 291 test_args.append("xnn_%s_requantize_%s" % \ 292 (datatype.lower(), requantization_type.lower())) 293 return xngen.preprocess(BINOP_TEST_TEMPLATE, { 294 "TEST_NAME": test_name.upper().replace("UKERNEL_", ""), 295 "TEST_ARGS": test_args, 296 "TESTER": tester, 297 "DATATYPE": datatype.upper(), 298 "BATCH_TILE": batch_tile, 299 "OP_TYPE": op_type, 300 "ACTIVATION_TYPE": activation_type, 301 "ISA_CHECK": xnncommon.generate_isa_check_macro(isa), 302 }) 303 304 305def main(args): 306 options = parser.parse_args(args) 307 308 with codecs.open(options.spec, "r", encoding="utf-8") as spec_file: 309 spec_yaml = yaml.safe_load(spec_file) 310 if not isinstance(spec_yaml, list): 311 raise ValueError("expected a list of micro-kernels in the spec") 312 313 spec_name = os.path.splitext(os.path.split(options.spec)[1])[0] 314 microkernel_header = { 315 "VAddMicrokernelTester": "xnnpack/vadd.h", 316 "VAddCMicrokernelTester": "xnnpack/vadd.h", 317 "VMulMicrokernelTester": "xnnpack/vmul.h", 318 "VMulCMicrokernelTester": "xnnpack/vmul.h", 319 "VBinaryMicrokernelTester": "xnnpack/vbinary.h", 320 "VBinaryCMicrokernelTester": "xnnpack/vbinary.h", 321 }[options.tester] 322 tester_header = { 323 "VAddMicrokernelTester": "vadd-microkernel-tester.h", 324 "VAddCMicrokernelTester": "vaddc-microkernel-tester.h", 325 "VMulMicrokernelTester": "vmul-microkernel-tester.h", 326 "VMulCMicrokernelTester": "vmulc-microkernel-tester.h", 327 "VBinaryMicrokernelTester": "vbinary-microkernel-tester.h", 328 "VBinaryCMicrokernelTester": "vbinaryc-microkernel-tester.h", 329 }[options.tester] 330 tests = """\ 331// Copyright 2019 Google LLC 332// 333// This source code is licensed under the BSD-style license found in the 334// LICENSE file in the root directory of this source tree. 335// 336// Auto-generated file. Do not edit! 337// Specification: {specification} 338// Generator: {generator} 339 340 341#include <gtest/gtest.h> 342 343#include <xnnpack/common.h> 344#include <xnnpack/isa-checks.h> 345 346#include <xnnpack/microparams-init.h> 347#include <{microkernel_header}> 348#include "{tester_header}" 349""".format(specification=options.spec, generator=sys.argv[0], 350 microkernel_header=microkernel_header, tester_header=tester_header) 351 352 for ukernel_spec in spec_yaml: 353 name = ukernel_spec["name"] 354 init_fn = ukernel_spec.get("init") 355 op_type, activation_type, requantization_type, batch_tile, arch, isa = \ 356 split_ukernel_name(name) 357 358 # specification can override architecture 359 arch = ukernel_spec.get("arch", arch) 360 361 test_case = generate_test_cases(name, op_type, init_fn, activation_type, 362 requantization_type, options.tester, 363 batch_tile, isa) 364 tests += "\n\n" + xnncommon.postprocess_test_case(test_case, arch, isa) 365 366 txt_changed = True 367 if os.path.exists(options.output): 368 with codecs.open(options.output, "r", encoding="utf-8") as output_file: 369 txt_changed = output_file.read() != tests 370 371 if txt_changed: 372 with codecs.open(options.output, "w", encoding="utf-8") as output_file: 373 output_file.write(tests) 374 375 376if __name__ == "__main__": 377 main(sys.argv[1:]) 378