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 unary operation microkernel test generator')
22parser.add_argument("-s", "--spec", metavar="FILE", required=True,
23                    help="Specification (YAML) file")
24parser.add_argument("-o", "--output", metavar="FILE", required=True,
25                    help='Output (C++ source) file')
26parser.set_defaults(defines=list())
27
28
29def split_ukernel_name(name):
30  match = re.match(r"^xnn_(f16|f32)_(relu|sigmoid|vabs|velu|vlrelu|vneg|vsqr|vrndne|vrndz|vrndd|vrndu|vsqrt)_(fact_)?ukernel__(.+)_x(\d+)$", name)
31  if match is None:
32    raise ValueError("Unexpected microkernel name: " + name)
33  op_type = {
34    "relu": "ReLU",
35    "vabs": "Abs",
36    "velu": "ELU",
37    "vlrelu": "LeakyReLU",
38    "vneg": "Negate",
39    "sigmoid": "Sigmoid",
40    "vrndne": "RoundToNearestEven",
41    "vrndz": "RoundTowardsZero",
42    "vrndu": "RoundUp",
43    "vrndd": "RoundDown",
44    "vsqr": "Square",
45    "vsqrt": "SquareRoot",
46  }[match.group(2)]
47  batch_tile = int(match.group(5))
48
49  arch, isa = xnncommon.parse_target_name(target_name=match.group(4))
50  return op_type, batch_tile, arch, isa
51
52
53BINOP_TEST_TEMPLATE = """\
54TEST(${TEST_NAME}, batch_eq_${BATCH_TILE}) {
55  $if ISA_CHECK:
56    ${ISA_CHECK};
57  VUnOpMicrokernelTester()
58    .batch_size(${BATCH_TILE})
59    .Test(${", ".join(TEST_ARGS)});
60}
61
62$if BATCH_TILE > 1:
63  TEST(${TEST_NAME}, batch_div_${BATCH_TILE}) {
64    $if ISA_CHECK:
65      ${ISA_CHECK};
66    for (size_t batch_size = ${BATCH_TILE*2}; batch_size < ${BATCH_TILE*10}; batch_size += ${BATCH_TILE}) {
67      VUnOpMicrokernelTester()
68        .batch_size(batch_size)
69        .Test(${", ".join(TEST_ARGS)});
70    }
71  }
72
73  TEST(${TEST_NAME}, batch_lt_${BATCH_TILE}) {
74    $if ISA_CHECK:
75      ${ISA_CHECK};
76    for (size_t batch_size = 1; batch_size < ${BATCH_TILE}; batch_size++) {
77      VUnOpMicrokernelTester()
78        .batch_size(batch_size)
79        .Test(${", ".join(TEST_ARGS)});
80    }
81  }
82
83TEST(${TEST_NAME}, batch_gt_${BATCH_TILE}) {
84  $if ISA_CHECK:
85    ${ISA_CHECK};
86  for (size_t batch_size = ${BATCH_TILE+1}; batch_size < ${10 if BATCH_TILE == 1 else BATCH_TILE*2}; batch_size++) {
87    VUnOpMicrokernelTester()
88      .batch_size(batch_size)
89      .Test(${", ".join(TEST_ARGS)});
90  }
91}
92
93TEST(${TEST_NAME}, inplace) {
94  $if ISA_CHECK:
95    ${ISA_CHECK};
96  for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) {
97    VUnOpMicrokernelTester()
98      .batch_size(batch_size)
99      .inplace(true)
100      .Test(${", ".join(TEST_ARGS)});
101  }
102}
103
104$if OP_TYPE == "LeakyReLU":
105  TEST(${TEST_NAME}, slope) {
106    $if ISA_CHECK:
107      ${ISA_CHECK};
108    for (float slope : std::vector<float>({-0.7f, 0.3f, 1.3f})) {
109      for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) {
110        VUnOpMicrokernelTester()
111          .batch_size(batch_size)
112          .slope(slope)
113          .Test(${", ".join(TEST_ARGS)});
114      }
115    }
116  }
117
118$if OP_TYPE == "ELU":
119  TEST(${TEST_NAME}, prescale) {
120    $if ISA_CHECK:
121      ${ISA_CHECK};
122    for (float prescale : std::vector<float>({0.1f, 10.0f})) {
123      for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) {
124        VUnOpMicrokernelTester()
125          .batch_size(batch_size)
126          .prescale(prescale)
127          .Test(${", ".join(TEST_ARGS)});
128      }
129    }
130  }
131
132  TEST(${TEST_NAME}, alpha) {
133    $if ISA_CHECK:
134      ${ISA_CHECK};
135    for (float alpha : std::vector<float>({0.3f, 3.0f})) {
136      for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) {
137        VUnOpMicrokernelTester()
138          .batch_size(batch_size)
139          .alpha(alpha)
140          .Test(${", ".join(TEST_ARGS)});
141      }
142    }
143  }
144
145  TEST(${TEST_NAME}, beta) {
146    $if ISA_CHECK:
147      ${ISA_CHECK};
148    for (float beta : std::vector<float>({0.3f, 3.0f})) {
149      for (size_t batch_size = 1; batch_size <= ${BATCH_TILE*5}; batch_size += ${max(1, BATCH_TILE-1)}) {
150        VUnOpMicrokernelTester()
151          .batch_size(batch_size)
152          .beta(beta)
153          .Test(${", ".join(TEST_ARGS)});
154      }
155    }
156  }
157"""
158
159
160def generate_test_cases(ukernel, op_type, batch_tile, isa):
161  """Generates all tests cases for a Vector Unary Operation micro-kernel.
162
163  Args:
164    ukernel: C name of the micro-kernel function.
165    op_type: Operation type.
166    batch_tile: Number of batch elements processed per one iteration of the
167                inner loop of the micro-kernel.
168    isa: instruction set required to run the micro-kernel. Generated unit test
169         will skip execution if the host processor doesn't support this ISA.
170
171  Returns:
172    Code for the test case.
173  """
174  _, test_name = ukernel.split("_", 1)
175  _, datatype, _ = ukernel.split("_", 2)
176  test_args = [
177    "xnn_f32_vunary_ukernel_function(%s)" % ukernel,
178    "VUnOpMicrokernelTester::OpType::%s" % op_type,
179  ]
180  if not isa:
181    test_args.append("VUnOpMicrokernelTester::Variant::Scalar")
182  return xngen.preprocess(BINOP_TEST_TEMPLATE, {
183      "TEST_NAME": test_name.upper().replace("UKERNEL_", ""),
184      "TEST_ARGS": test_args,
185      "DATATYPE": datatype,
186      "BATCH_TILE": batch_tile,
187      "OP_TYPE": op_type,
188      "ISA_CHECK": xnncommon.generate_isa_check_macro(isa),
189    })
190
191
192def main(args):
193  options = parser.parse_args(args)
194
195  with codecs.open(options.spec, "r", encoding="utf-8") as spec_file:
196    spec_yaml = yaml.safe_load(spec_file)
197    if not isinstance(spec_yaml, list):
198      raise ValueError("expected a list of micro-kernels in the spec")
199
200    tests = """\
201// Copyright 2019 Google LLC
202//
203// This source code is licensed under the BSD-style license found in the
204// LICENSE file in the root directory of this source tree.
205//
206// Auto-generated file. Do not edit!
207//   Specification: {specification}
208//   Generator: {generator}
209
210
211#include <gtest/gtest.h>
212
213#include <xnnpack/common.h>
214#include <xnnpack/isa-checks.h>
215
216#include <xnnpack/vunary.h>
217#include "vunary-microkernel-tester.h"
218""".format(specification=options.spec, generator=sys.argv[0])
219
220    for ukernel_spec in spec_yaml:
221      name = ukernel_spec["name"]
222      op_type, batch_tile, arch, isa = split_ukernel_name(name)
223
224      # specification can override architecture
225      arch = ukernel_spec.get("arch", arch)
226
227      test_case = generate_test_cases(name, op_type, batch_tile, isa)
228      tests += "\n\n" + xnncommon.postprocess_test_case(test_case, arch, isa)
229
230    with codecs.open(options.output, "w", encoding="utf-8") as output_file:
231      output_file.write(tests)
232
233
234if __name__ == "__main__":
235  main(sys.argv[1:])
236