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 bisect 9import codecs 10import math 11import os 12import sys 13import yaml 14 15sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) 16from primes import next_prime 17import xngen 18import xnncommon 19 20 21parser = argparse.ArgumentParser(description='XNNPACK generator') 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 28def split_ukernel_name(name): 29 common_name, target_name = name.split("__", 1) 30 common_parts = common_name.split("_") 31 param_spec = common_parts[-1] 32 assert param_spec.startswith("up") 33 34 if len(param_spec[2:].split("p")) > 1: 35 tile_part, kernel_part = param_spec[2:].split("p", 1) 36 primary_tile = int(tile_part) 37 cr, kr = map(int, kernel_part.split("x")) 38 else: 39 primary_tile = 0; 40 cr, kr = map(int, param_spec[2:].split("x")) 41 arch, isa = xnncommon.parse_target_name(target_name) 42 43 requantization = common_parts[-3] 44 if requantization not in ["fp32", "rndnu"]: 45 requantization = None 46 47 return primary_tile, cr, kr, requantization, arch, isa 48 49 50DWCONV_TEST_CODE = """\ 51TEST(${TEST_NAME}, c_eq_${CBLOCK}) { 52 $if ISA_CHECK: 53 ${ISA_CHECK}; 54 DWConvMicrokernelTester() 55 .cr(${CR}) 56 .kr(${KR}) 57 .channels(${CBLOCK}) 58 .Test(${", ".join(TEST_ARGS)}); 59} 60 61$if IS_PIPELINED: 62 TEST(${TEST_NAME}, c_eq_${CBLOCK * 2}) { 63 $if ISA_CHECK: 64 ${ISA_CHECK}; 65 DWConvMicrokernelTester() 66 .cr(${CR}) 67 .kr(${KR}) 68 .channels(${CBLOCK * 2}) 69 .Test(${", ".join(TEST_ARGS)}); 70 } 71 72$if CBLOCK > 1: 73 TEST(${TEST_NAME}, c_div_${CBLOCK}) { 74 $if ISA_CHECK: 75 ${ISA_CHECK}; 76 for (uint32_t channels = ${ADJCBLOCK + CBLOCK}; channels < ${CR * 16}; channels += ${CR * 3}) { 77 DWConvMicrokernelTester() 78 .cr(${CR}) 79 .kr(${KR}) 80 .channels(channels) 81 .Test(${", ".join(TEST_ARGS)}); 82 } 83 } 84 85 $if ACTIVATION == "MINMAX": 86 TEST(${TEST_NAME}, c_div_${CBLOCK}_with_qmin) { 87 $if ISA_CHECK: 88 ${ISA_CHECK}; 89 for (uint32_t channels = ${ADJCBLOCK + CBLOCK}; channels < ${CR * 16}; channels += ${CR * 3}) { 90 DWConvMicrokernelTester() 91 .cr(${CR}) 92 .kr(${KR}) 93 .channels(channels) 94 .qmin(128) 95 .Test(${", ".join(TEST_ARGS)}); 96 } 97 } 98 99 TEST(${TEST_NAME}, c_div_${CBLOCK}_with_qmax) { 100 $if ISA_CHECK: 101 ${ISA_CHECK}; 102 for (uint32_t channels = ${ADJCBLOCK + CBLOCK}; channels < ${CR * 16}; channels += ${CR * 3}) { 103 DWConvMicrokernelTester() 104 .cr(${CR}) 105 .kr(${KR}) 106 .channels(channels) 107 .qmax(128) 108 .Test(${", ".join(TEST_ARGS)}); 109 } 110 } 111 112 TEST(${TEST_NAME}, c_lt_${ADJCBLOCK}) { 113 $if ISA_CHECK: 114 ${ISA_CHECK}; 115 for (uint32_t channels = 1; channels < ${ADJCBLOCK}; channels++) { 116 DWConvMicrokernelTester() 117 .cr(${CR}) 118 .kr(${KR}) 119 .channels(channels) 120 .Test(${", ".join(TEST_ARGS)}); 121 } 122 } 123 124TEST(${TEST_NAME}, c_gt_${ADJCBLOCK}) { 125 $if ISA_CHECK: 126 ${ISA_CHECK}; 127 for (uint32_t channels = ${ADJCBLOCK + 1}; channels < ${10 if CBLOCK == 1 else ADJCBLOCK + CBLOCK}; channels++) { 128 DWConvMicrokernelTester() 129 .cr(${CR}) 130 .kr(${KR}) 131 .channels(channels) 132 .Test(${", ".join(TEST_ARGS)}); 133 } 134} 135 136$if ACTIVATION == "MINMAX": 137 TEST(${TEST_NAME}, c_gt_${ADJCBLOCK}_with_qmin) { 138 $if ISA_CHECK: 139 ${ISA_CHECK}; 140 for (uint32_t channels = ${ADJCBLOCK + 1}; channels < ${10 if CBLOCK == 1 else ADJCBLOCK + CBLOCK}; channels++) { 141 DWConvMicrokernelTester() 142 .cr(${CR}) 143 .kr(${KR}) 144 .channels(channels) 145 .qmin(128) 146 .Test(${", ".join(TEST_ARGS)}); 147 } 148 } 149 150 TEST(${TEST_NAME}, c_gt_${ADJCBLOCK}_with_qmax) { 151 $if ISA_CHECK: 152 ${ISA_CHECK}; 153 for (uint32_t channels = ${ADJCBLOCK + 1}; channels < ${10 if CBLOCK == 1 else ADJCBLOCK + CBLOCK}; channels++) { 154 DWConvMicrokernelTester() 155 .cr(${CR}) 156 .kr(${KR}) 157 .channels(channels) 158 .qmax(128) 159 .Test(${", ".join(TEST_ARGS)}); 160 } 161 } 162 163TEST(${TEST_NAME}, multipixel) { 164 $if ISA_CHECK: 165 ${ISA_CHECK}; 166 for (size_t channels = 1; channels <= ${CBLOCK * 5}; channels += ${max(1, CBLOCK - 1)}) { 167 DWConvMicrokernelTester() 168 .cr(${CR}) 169 .kr(${KR}) 170 .channels(channels) 171 .width(3) 172 .Test(${", ".join(TEST_ARGS)}); 173 } 174} 175 176TEST(${TEST_NAME}, multipixel_with_step) { 177 $if ISA_CHECK: 178 ${ISA_CHECK}; 179 for (size_t channels = 1; channels <= ${CBLOCK * 5}; channels += ${max(1, CBLOCK - 1)}) { 180 for (size_t step = 2; step <= ${KR}; step++) { 181 DWConvMicrokernelTester() 182 .cr(${CR}) 183 .kr(${KR}) 184 .channels(channels) 185 .width(3) 186 .step(step) 187 .Test(${", ".join(TEST_ARGS)}); 188 } 189 } 190} 191 192TEST(${TEST_NAME}, multipixel_with_output_stride) { 193 $if ISA_CHECK: 194 ${ISA_CHECK}; 195 for (size_t channels = 1; channels <= ${CBLOCK * 5}; channels += ${max(1, CBLOCK - 1)}) { 196 DWConvMicrokernelTester() 197 .cr(${CR}) 198 .kr(${KR}) 199 .channels(${CR}) 200 .width(5) 201 .output_stride(${next_prime(CR * 5 + 1)}) 202 .Test(${", ".join(TEST_ARGS)}); 203 } 204} 205 206$if ACTIVATION == "MINMAX": 207 TEST(${TEST_NAME}, multipixel_with_qmin) { 208 $if ISA_CHECK: 209 ${ISA_CHECK}; 210 for (size_t channels = 1; channels <= ${CBLOCK * 5}; channels += ${max(1, CBLOCK - 1)}) { 211 DWConvMicrokernelTester() 212 .cr(${CR}) 213 .kr(${KR}) 214 .channels(channels) 215 .width(3) 216 .qmin(128) 217 .Test(${", ".join(TEST_ARGS)}); 218 } 219 } 220 221 TEST(${TEST_NAME}, multipixel_with_qmax) { 222 $if ISA_CHECK: 223 ${ISA_CHECK}; 224 for (size_t channels = 1; channels <= ${CBLOCK * 5}; channels += ${max(1, CBLOCK - 1)}) { 225 DWConvMicrokernelTester() 226 .cr(${CR}) 227 .kr(${KR}) 228 .channels(channels) 229 .width(3) 230 .qmax(128) 231 .Test(${", ".join(TEST_ARGS)}); 232 } 233 } 234 235$if DATATYPE == "qu8": 236 TEST(${TEST_NAME}, input_zero_point_only) { 237 $if ISA_CHECK: 238 ${ISA_CHECK}; 239 for (size_t channels = 1; channels <= ${CBLOCK * 5}; channels += ${max(1, CBLOCK - 1)}) { 240 DWConvMicrokernelTester() 241 .cr(${CR}) 242 .kr(${KR}) 243 .channels(channels) 244 .width(3) 245 .input_zero_point(255) 246 .kernel_zero_point(0) 247 .Test(${", ".join(TEST_ARGS)}); 248 } 249 } 250 251 TEST(${TEST_NAME}, kernel_zero_point_only) { 252 $if ISA_CHECK: 253 ${ISA_CHECK}; 254 for (size_t channels = 1; channels <= ${CBLOCK * 5}; channels += ${max(1, CBLOCK - 1)}) { 255 DWConvMicrokernelTester() 256 .cr(${CR}) 257 .kr(${KR}) 258 .channels(channels) 259 .width(3) 260 .input_zero_point(0) 261 .kernel_zero_point(255) 262 .Test(${", ".join(TEST_ARGS)}); 263 } 264 } 265 266TEST(${TEST_NAME}, input_offset) { 267 $if ISA_CHECK: 268 ${ISA_CHECK}; 269 for (uint32_t channels = ${ADJCBLOCK + CBLOCK}; channels < ${CR * 16}; channels += ${CR * 3}) { 270 DWConvMicrokernelTester() 271 .cr(${CR}) 272 .kr(${KR}) 273 .channels(channels) 274 .input_offset(${next_prime(CR + 1) * 16}) 275 .Test(${", ".join(TEST_ARGS)}); 276 } 277} 278 279TEST(${TEST_NAME}, zero) { 280 $if ISA_CHECK: 281 ${ISA_CHECK}; 282 for (uint32_t mz = 0; mz < ${KR}; mz++) { 283 for (uint32_t channels = ${ADJCBLOCK + CBLOCK}; channels < ${CR * 16}; channels += ${CR * 3}) { 284 DWConvMicrokernelTester() 285 .cr(${CR}) 286 .kr(${KR}) 287 .channels(channels) 288 .input_offset(${next_prime(CR + 1) * 16}) 289 .zero_index(mz) 290 .Test(${", ".join(TEST_ARGS)}); 291 } 292 } 293} 294""" 295 296def generate_test_cases(ukernel, primary_tile, cr, kr, c_block, 297 init_fn, requantization, is_pipelined, isa): 298 """Generates all tests cases for a DWCONV micro-kernel. 299 300 Args: 301 ukernel: C name of the micro-kernel function. 302 cr: CR parameter of the DWCONV micro-kernel. 303 kr: KR parameter of the DWCONV micro-kernel. 304 k_block: Number of C values processed per one iteration of the main loop of 305 the micro-kernel. 306 init_fn: C name of the function to initialize microkernel parameters. 307 requantization: name of the requantization scheme used by the microkernel. 308 is_pipelined: Indicates if the micro-kernel is implemented with software 309 pipelining. Additional test cases are generated for software 310 pipelined micro-kernels to separately test prologue + epiloque 311 of the pipelined loop and iteration of the pipelined loop. 312 isa: instruction set required to run the micro-kernel. Generated unit test 313 will skip execution if the host processor doesn't support this ISA. 314 315 Returns: 316 Code for the test case. 317 """ 318 _, test_name = ukernel.split("_", 1) 319 _, datatype, ukernel_type, activation, _ = ukernel.split("_", 4) 320 if activation == "ukernel": 321 activation = "linear" 322 test_args = [ukernel] 323 if init_fn: 324 test_args.append(init_fn) 325 if requantization: 326 requantization_datatype = {"qc8": "qs8"}.get(datatype, datatype) 327 test_args.append("xnn_%s_requantize_%s" % 328 (requantization_datatype, requantization)) 329 return xngen.preprocess(DWCONV_TEST_CODE, { 330 "TEST_NAME": test_name.upper().replace("UKERNEL_", ""), 331 "TEST_ARGS": test_args, 332 "UKERNEL_TYPE": ukernel_type.upper(), 333 "DATATYPE": datatype, 334 "ACTIVATION": activation.upper(), 335 "PRIMARY_TILE": primary_tile, 336 "CR": cr, 337 "KR": kr, 338 "CBLOCK": c_block, 339 "ADJCBLOCK": 2 * c_block if is_pipelined else c_block, 340 "IS_PIPELINED": is_pipelined, 341 "ISA_CHECK": xnncommon.generate_isa_check_macro(isa), 342 "next_prime": next_prime, 343 "sqrt": math.sqrt, 344 }) 345 346 347def main(args): 348 options = parser.parse_args(args) 349 350 with codecs.open(options.spec, "r", encoding="utf-8") as spec_file: 351 spec_yaml = yaml.safe_load(spec_file) 352 if not isinstance(spec_yaml, list): 353 raise ValueError("expected a list of micro-kernels in the spec") 354 355 tests = """\ 356// Copyright (c) Facebook, Inc. and its affiliates. 357// All rights reserved. 358// 359// Copyright 2019 Google LLC 360// 361// This source code is licensed under the BSD-style license found in the 362// LICENSE file in the root directory of this source tree. 363// 364// Auto-generated file. Do not edit! 365// Specification: {specification} 366// Generator: {generator} 367 368 369#include <gtest/gtest.h> 370 371#include <xnnpack/common.h> 372#include <xnnpack/isa-checks.h> 373 374#include <xnnpack/dwconv.h> 375#include "dwconv-microkernel-tester.h" 376""".format(specification=options.spec, generator=sys.argv[0]) 377 378 for ukernel_spec in spec_yaml: 379 name = ukernel_spec["name"] 380 init_fn = ukernel_spec.get("init") 381 pipelined = bool(ukernel_spec.get("pipelined", False)) 382 assembly = bool(ukernel_spec.get("assembly", False)) 383 primary_tile, cr, kr, requantization, arch, isa = split_ukernel_name(name) 384 385 # specification can override architecture 386 arch = ukernel_spec.get("arch", arch) 387 388 test_case = generate_test_cases( 389 name, primary_tile, cr, kr, cr, init_fn, requantization, pipelined, isa) 390 tests += "\n\n" + xnncommon.postprocess_test_case(test_case, arch, isa, assembly) 391 392 txt_changed = True 393 if os.path.exists(options.output): 394 with codecs.open(options.output, "r", encoding="utf-8") as output_file: 395 txt_changed = output_file.read() != tests 396 397 if txt_changed: 398 with codecs.open(options.output, "w", encoding="utf-8") as output_file: 399 output_file.write(tests) 400 401 402if __name__ == "__main__": 403 main(sys.argv[1:]) 404