blob: 9cafed39f96399b490d2cb7e3d9c023f6ce96635 [file] [log] [blame]
Jeremy Johnson6179c212022-01-13 13:46:35 +00001#!/usr/bin/env python3
2# Copyright (c) 2021-2022, ARM Limited.
3# SPDX-License-Identifier: Apache-2.0
4"""This script converts generated tests into conformance tests.
5
6It can convert a framework unit test or a reference model unit test.
7It expects the tests have been already run on the reference model
8so it can capture the result as the expected result.
9"""
10import argparse
11import json
12import logging
13import os
14from pathlib import Path
15from typing import Optional
16
17from json2fbbin.json2fbbin import fbbin_to_json
18from json2numpy.json2numpy import npy_to_json
19
20logging.basicConfig(level=logging.INFO)
21logger = logging.getLogger("convert2conformance")
22
23LOCATION_REF_MODEL_SCHEMA = Path("thirdparty/serialization_lib/schema/tosa.fbs")
24LOCATION_REF_MODEL_FLATC = Path(
25 "build/thirdparty/serialization_lib/third_party/flatbuffers/flatc"
26)
27
28NAME_FLATBUFFER_DIR = ["flatbuffer-", "_FW_"]
29NAME_DESC_FILENAME = "desc.json"
30NAME_CONFORMANCE_RESULT_PREFIX = "Conformance-"
31NAME_REFMODEL_RUN_RESULT_SUFFIX = ".runner.tosa_refmodel_sut_run.npy"
32
Jeremy Johnson88588622022-07-12 16:42:29 +010033PROFILES_LIST = ["tosa-bi", "tosa-mi"]
34
Jeremy Johnson6179c212022-01-13 13:46:35 +000035
36def parse_args(argv):
37 """Parse the arguments."""
38 parser = argparse.ArgumentParser()
39 parser.add_argument(
40 "test_dir",
41 default=Path.cwd(),
42 type=Path,
43 nargs="?",
44 help="The test directory to convert (default is CWD)",
45 )
46 parser.add_argument(
47 "--ref-model-directory",
48 dest="ref_model_dir",
49 type=Path,
50 required=True,
51 help="Reference Model directory (must be pre-built)",
52 )
53 parser.add_argument(
54 "--output-directory",
55 dest="output_dir",
56 type=Path,
57 default=Path.cwd() / "conformance",
58 help="Output directory (default is conformance in CWD)",
59 )
60 parser.add_argument(
61 "--framework",
62 dest="framework",
63 choices=["tflite"],
64 default="tflite",
65 help="Framework to convert (default tflite)",
66 )
67 parser.add_argument(
68 "--framework-schema",
69 dest="framework_schema",
70 type=Path,
71 help="Framework schema needed to convert framework models",
72 )
73 parser.add_argument(
Jeremy Johnson88588622022-07-12 16:42:29 +010074 "--profile",
75 dest="profile",
76 choices=PROFILES_LIST,
77 action="append",
78 required=True,
79 help="Profiles this test is suitable for. May be repeated",
80 )
81 parser.add_argument(
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +000082 "--strict",
83 dest="strict",
84 action="store_true",
85 help="Output directory must not contain the same test directory",
86 )
87 parser.add_argument(
Jeremy Johnson6179c212022-01-13 13:46:35 +000088 "-v", "--verbose", dest="verbose", action="store_true", help="Verbose operation"
89 )
90 args = parser.parse_args(argv)
Jeremy Johnson88588622022-07-12 16:42:29 +010091
Jeremy Johnson6179c212022-01-13 13:46:35 +000092 return args
93
94
95def find_ref_model_artifacts(path: Path):
96 """Check the location of the flatc compiler and schema artifacts."""
97 flatc = path / LOCATION_REF_MODEL_FLATC
98 schema = path / LOCATION_REF_MODEL_SCHEMA
99 if not flatc.is_file():
100 raise Exception(
101 f"flatc not found in {flatc}\nHave you built the flatbuffers compiler?"
102 )
103 if not schema.is_file():
104 raise Exception(
105 f"TOSA schema not found at {schema}\nHave you checked out the submodules?"
106 )
107 return flatc, schema
108
109
110def find_framework_artifacts(framework: str, schema_path: Path, desc_file: Path):
111 """Check that any required schema has been supplied for conversion."""
112 if framework == "tflite":
113 if not schema_path:
114 raise Exception("the following arguments are required: --framework-schema")
115 elif not schema_path.is_file():
116 raise Exception(f"framework schema not found at {schema_path}")
117 model = desc_file.parent.parent / "model.tflite"
118 if not model.is_file():
119 raise Exception(f"Model file not found at {model}")
120 return schema_path, model
121 return None, None
122
123
124def get_framework_name(name_array: list, framework: str):
125 """Get the framework conversion directory name."""
126 name = ""
127 for part in name_array:
128 if part == "_FW_":
129 part = framework
130 name = f"{name}{part}"
131 return name
132
133
134def convert_flatbuffer_file(flatc: Path, schema: Path, model_file: Path, output: Path):
135 """Convert the flatbuffer binary into JSON."""
136 try:
137 fbbin_to_json(flatc, schema, model_file, output)
138 except Exception as e:
139 logger.error(f"Failed to convert flatbuffer binary:\n{e}")
140 return None
141
142 if model_file.name == "model.tflite":
143 file_name = "model-tflite.json"
144 os.rename(output / "model.json", output / file_name)
145 else:
146 file_name = model_file.stem + ".json"
147 return output / file_name
148
149
150def convert_numpy_file(n_file: Path, output: Path, outname: Optional[str] = None):
151 """Convert a numpy file into a JSON file."""
152 j_file = output / (outname if outname else (n_file.stem + ".json"))
153 npy_to_json(n_file, j_file)
154 return j_file
155
156
157def update_desc_json(
Jeremy Johnson88588622022-07-12 16:42:29 +0100158 test_dir: Path,
159 test_desc,
160 output_dir: Optional[Path] = None,
161 create_result=True,
162 profiles=None,
Jeremy Johnson6179c212022-01-13 13:46:35 +0000163):
164 """Update the desc.json format for conformance and optionally create result."""
165 ofm_files = []
166 cfm_files = []
167 if not output_dir:
168 output_dir = test_dir
169 for index, ofm in enumerate(test_desc["ofm_file"]):
170 ofm_path = test_dir / ofm
171 if not test_desc["expected_failure"]:
172 cfm = NAME_CONFORMANCE_RESULT_PREFIX + test_desc["ofm_name"][index]
173 if create_result:
174 if ofm_path.is_file():
175 # Use the desc.json name
176 ofm_refmodel = ofm_path
177 else:
178 # Adjust for renaming due to tosa_verif_run_tests
179 ofm_refmodel = ofm_path.with_suffix(NAME_REFMODEL_RUN_RESULT_SUFFIX)
180 # Create conformance result
181 if ofm_refmodel.is_file():
182 convert_numpy_file(ofm_refmodel, output_dir, outname=cfm + ".json")
183 else:
184 logger.error(f"Missing result file {ofm_path}")
185 return None
186 cfm_files.append(cfm + ".npy")
187 # Remove path and "ref-"/"ref_model_" from output filenames
188 ofm_files.append(strip_ref_output_name(ofm_path.name))
189
190 # Rewrite output file names as they can be relative, but keep them npys
191 test_desc["ofm_file"] = ofm_files
192 if not test_desc["expected_failure"]:
193 # Output expected result file for conformance if expected pass
194 test_desc["expected_result_file"] = cfm_files
Jeremy Johnson88588622022-07-12 16:42:29 +0100195
196 # Add supported profiles
197 if profiles is None:
198 # Assume base profile
199 profiles = [PROFILES_LIST[0]]
200 test_desc["profile"] = profiles
201
Jeremy Johnson6179c212022-01-13 13:46:35 +0000202 return test_desc
203
204
205def strip_ref_output_name(name):
206 """Remove mentions of reference from output files."""
207 if name.startswith("ref-"):
208 name = name[4:]
209 if name.startswith("ref_model_"):
210 name = name[10:]
211 return name
212
213
214def main(argv=None):
215 """Convert the given directory to a conformance test."""
216 args = parse_args(argv)
217 # Verbosity
218 if args.verbose:
219 logger.setLevel(logging.DEBUG)
220
221 # Check we can get the files we need
222 try:
223 flatc, schema = find_ref_model_artifacts(args.ref_model_dir)
224 except Exception as err:
225 logger.error(err)
226 return 2
227
228 # Work out where the desc.json file is
229 desc_filename = args.test_dir / NAME_DESC_FILENAME
230 framework_conversion = False
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100231 test_type_desc = "unknown"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000232 if desc_filename.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100233 logger.debug("Found TOSA operator unit test")
234 test_type_desc = "TOSA operator"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000235 else:
236 desc_filename = (
237 args.test_dir
238 / get_framework_name(NAME_FLATBUFFER_DIR, args.framework)
239 / NAME_DESC_FILENAME
240 )
241 if desc_filename.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100242 logger.debug(f"Found framework unit test for {args.framework}")
243 test_type_desc = f"{args.framework}"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000244 framework_conversion = True
245 else:
246 logger.error(f"Could not find {NAME_DESC_FILENAME} in {args.test_dir}")
247 return 2
248 logger.debug(f"desc.json file: {desc_filename}")
249
250 # Check for required files for framework conversion
251 if framework_conversion:
252 try:
253 framework_schema, framework_filename = find_framework_artifacts(
254 args.framework, args.framework_schema, desc_filename
255 )
256 except Exception as err:
257 logger.error(err)
258 return 2
259 else:
260 framework_schema, framework_filename = None, None
261
262 # Open the meta desc.json file
263 with open(desc_filename, mode="r") as fd:
264 test_desc = json.load(fd)
265
266 if "tosa_file" not in test_desc:
267 logger.error(f"Unsupported desc.json file found {desc_filename}")
268 return 2
269
270 # Dictionary fix
271 if "ifm_name" not in test_desc:
272 logger.warn("Old format desc.json file found - attempting to fix up")
273 test_desc["ifm_name"] = test_desc["ifm_placeholder"]
274 del test_desc["ifm_placeholder"]
275
276 # Make the output directory if needed
277 try:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000278 args.output_dir.mkdir(parents=True, exist_ok=(not args.strict))
Jeremy Johnson6179c212022-01-13 13:46:35 +0000279 except FileExistsError:
Jeremy Johnsondd8d9c22022-12-12 14:18:10 +0000280 if args.strict:
281 logger.error(f"{args.output_dir} already exists")
282 else:
283 logger.error(f"{args.output_dir} is not a directory")
Jeremy Johnson6179c212022-01-13 13:46:35 +0000284 return 2
285
286 # Convert the TOSA flatbuffer binary
287 tosa_filename = desc_filename.parent / test_desc["tosa_file"]
288 tosa_filename = convert_flatbuffer_file(
289 flatc, schema, tosa_filename, args.output_dir
290 )
291 if not tosa_filename:
292 # Failed to convert the file, json2fbbin will have printed an error
293 return 1
294 else:
295 # Replace binary with JSON name
296 test_desc["tosa_file"] = tosa_filename.name
297
298 if framework_conversion and framework_filename:
299 # Convert the framework flatbuffer binary
300 framework_filename = convert_flatbuffer_file(
301 flatc, framework_schema, framework_filename, args.output_dir
302 )
303 if not framework_filename:
304 # Failed to convert the file, json2fbbin will have printed an error
305 return 1
306
307 # Convert input files to JSON
308 ifm_files = []
309 for file in test_desc["ifm_file"]:
310 if file is None:
311 ifm_files.append(None)
312 else:
313 path = desc_filename.parent / file
314 convert_numpy_file(path, args.output_dir)
315 ifm_files.append(path.name)
316 # Rewrite input file names to make sure the paths are correct,
317 # but keep them numpys as the test runner will convert them back
318 # before giving them to the SUT
319 test_desc["ifm_file"] = ifm_files
320
321 # Update desc.json and convert result files to JSON
322 test_desc = update_desc_json(
Jeremy Johnson88588622022-07-12 16:42:29 +0100323 desc_filename.parent,
324 test_desc,
325 output_dir=args.output_dir,
326 create_result=True,
327 profiles=args.profile,
Jeremy Johnson6179c212022-01-13 13:46:35 +0000328 )
329 if not test_desc:
330 # Error from conversion/update
331 return 1
332
333 # Output new desc.json
334 new_desc_filename = args.output_dir / NAME_DESC_FILENAME
335 with open(new_desc_filename, "w") as fd:
336 json.dump(test_desc, fd, indent=2)
337
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100338 logger.info(f"Converted {test_type_desc} test to {args.output_dir}")
Jeremy Johnson6179c212022-01-13 13:46:35 +0000339 return 0
340
341
342if __name__ == "__main__":
343 exit(main())