xref: /aosp_15_r20/external/XNNPACK/tools/generate-dwconv2d-chw-test.py (revision 4bdc94577ba0e567308109d787f7fec7b531ce36)
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