blob: 06726ba1589a60f4f43e9d86070be52086146ba6 [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 Johnson6179c212022-01-13 13:46:35 +000082 "-v", "--verbose", dest="verbose", action="store_true", help="Verbose operation"
83 )
84 args = parser.parse_args(argv)
Jeremy Johnson88588622022-07-12 16:42:29 +010085
Jeremy Johnson6179c212022-01-13 13:46:35 +000086 return args
87
88
89def find_ref_model_artifacts(path: Path):
90 """Check the location of the flatc compiler and schema artifacts."""
91 flatc = path / LOCATION_REF_MODEL_FLATC
92 schema = path / LOCATION_REF_MODEL_SCHEMA
93 if not flatc.is_file():
94 raise Exception(
95 f"flatc not found in {flatc}\nHave you built the flatbuffers compiler?"
96 )
97 if not schema.is_file():
98 raise Exception(
99 f"TOSA schema not found at {schema}\nHave you checked out the submodules?"
100 )
101 return flatc, schema
102
103
104def find_framework_artifacts(framework: str, schema_path: Path, desc_file: Path):
105 """Check that any required schema has been supplied for conversion."""
106 if framework == "tflite":
107 if not schema_path:
108 raise Exception("the following arguments are required: --framework-schema")
109 elif not schema_path.is_file():
110 raise Exception(f"framework schema not found at {schema_path}")
111 model = desc_file.parent.parent / "model.tflite"
112 if not model.is_file():
113 raise Exception(f"Model file not found at {model}")
114 return schema_path, model
115 return None, None
116
117
118def get_framework_name(name_array: list, framework: str):
119 """Get the framework conversion directory name."""
120 name = ""
121 for part in name_array:
122 if part == "_FW_":
123 part = framework
124 name = f"{name}{part}"
125 return name
126
127
128def convert_flatbuffer_file(flatc: Path, schema: Path, model_file: Path, output: Path):
129 """Convert the flatbuffer binary into JSON."""
130 try:
131 fbbin_to_json(flatc, schema, model_file, output)
132 except Exception as e:
133 logger.error(f"Failed to convert flatbuffer binary:\n{e}")
134 return None
135
136 if model_file.name == "model.tflite":
137 file_name = "model-tflite.json"
138 os.rename(output / "model.json", output / file_name)
139 else:
140 file_name = model_file.stem + ".json"
141 return output / file_name
142
143
144def convert_numpy_file(n_file: Path, output: Path, outname: Optional[str] = None):
145 """Convert a numpy file into a JSON file."""
146 j_file = output / (outname if outname else (n_file.stem + ".json"))
147 npy_to_json(n_file, j_file)
148 return j_file
149
150
151def update_desc_json(
Jeremy Johnson88588622022-07-12 16:42:29 +0100152 test_dir: Path,
153 test_desc,
154 output_dir: Optional[Path] = None,
155 create_result=True,
156 profiles=None,
Jeremy Johnson6179c212022-01-13 13:46:35 +0000157):
158 """Update the desc.json format for conformance and optionally create result."""
159 ofm_files = []
160 cfm_files = []
161 if not output_dir:
162 output_dir = test_dir
163 for index, ofm in enumerate(test_desc["ofm_file"]):
164 ofm_path = test_dir / ofm
165 if not test_desc["expected_failure"]:
166 cfm = NAME_CONFORMANCE_RESULT_PREFIX + test_desc["ofm_name"][index]
167 if create_result:
168 if ofm_path.is_file():
169 # Use the desc.json name
170 ofm_refmodel = ofm_path
171 else:
172 # Adjust for renaming due to tosa_verif_run_tests
173 ofm_refmodel = ofm_path.with_suffix(NAME_REFMODEL_RUN_RESULT_SUFFIX)
174 # Create conformance result
175 if ofm_refmodel.is_file():
176 convert_numpy_file(ofm_refmodel, output_dir, outname=cfm + ".json")
177 else:
178 logger.error(f"Missing result file {ofm_path}")
179 return None
180 cfm_files.append(cfm + ".npy")
181 # Remove path and "ref-"/"ref_model_" from output filenames
182 ofm_files.append(strip_ref_output_name(ofm_path.name))
183
184 # Rewrite output file names as they can be relative, but keep them npys
185 test_desc["ofm_file"] = ofm_files
186 if not test_desc["expected_failure"]:
187 # Output expected result file for conformance if expected pass
188 test_desc["expected_result_file"] = cfm_files
Jeremy Johnson88588622022-07-12 16:42:29 +0100189
190 # Add supported profiles
191 if profiles is None:
192 # Assume base profile
193 profiles = [PROFILES_LIST[0]]
194 test_desc["profile"] = profiles
195
Jeremy Johnson6179c212022-01-13 13:46:35 +0000196 return test_desc
197
198
199def strip_ref_output_name(name):
200 """Remove mentions of reference from output files."""
201 if name.startswith("ref-"):
202 name = name[4:]
203 if name.startswith("ref_model_"):
204 name = name[10:]
205 return name
206
207
208def main(argv=None):
209 """Convert the given directory to a conformance test."""
210 args = parse_args(argv)
211 # Verbosity
212 if args.verbose:
213 logger.setLevel(logging.DEBUG)
214
215 # Check we can get the files we need
216 try:
217 flatc, schema = find_ref_model_artifacts(args.ref_model_dir)
218 except Exception as err:
219 logger.error(err)
220 return 2
221
222 # Work out where the desc.json file is
223 desc_filename = args.test_dir / NAME_DESC_FILENAME
224 framework_conversion = False
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100225 test_type_desc = "unknown"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000226 if desc_filename.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100227 logger.debug("Found TOSA operator unit test")
228 test_type_desc = "TOSA operator"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000229 else:
230 desc_filename = (
231 args.test_dir
232 / get_framework_name(NAME_FLATBUFFER_DIR, args.framework)
233 / NAME_DESC_FILENAME
234 )
235 if desc_filename.is_file():
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100236 logger.debug(f"Found framework unit test for {args.framework}")
237 test_type_desc = f"{args.framework}"
Jeremy Johnson6179c212022-01-13 13:46:35 +0000238 framework_conversion = True
239 else:
240 logger.error(f"Could not find {NAME_DESC_FILENAME} in {args.test_dir}")
241 return 2
242 logger.debug(f"desc.json file: {desc_filename}")
243
244 # Check for required files for framework conversion
245 if framework_conversion:
246 try:
247 framework_schema, framework_filename = find_framework_artifacts(
248 args.framework, args.framework_schema, desc_filename
249 )
250 except Exception as err:
251 logger.error(err)
252 return 2
253 else:
254 framework_schema, framework_filename = None, None
255
256 # Open the meta desc.json file
257 with open(desc_filename, mode="r") as fd:
258 test_desc = json.load(fd)
259
260 if "tosa_file" not in test_desc:
261 logger.error(f"Unsupported desc.json file found {desc_filename}")
262 return 2
263
264 # Dictionary fix
265 if "ifm_name" not in test_desc:
266 logger.warn("Old format desc.json file found - attempting to fix up")
267 test_desc["ifm_name"] = test_desc["ifm_placeholder"]
268 del test_desc["ifm_placeholder"]
269
270 # Make the output directory if needed
271 try:
272 args.output_dir.mkdir(parents=True, exist_ok=True)
273 except FileExistsError:
274 logger.error(f"{args.output_dir} is not a directory")
275 return 2
276
277 # Convert the TOSA flatbuffer binary
278 tosa_filename = desc_filename.parent / test_desc["tosa_file"]
279 tosa_filename = convert_flatbuffer_file(
280 flatc, schema, tosa_filename, args.output_dir
281 )
282 if not tosa_filename:
283 # Failed to convert the file, json2fbbin will have printed an error
284 return 1
285 else:
286 # Replace binary with JSON name
287 test_desc["tosa_file"] = tosa_filename.name
288
289 if framework_conversion and framework_filename:
290 # Convert the framework flatbuffer binary
291 framework_filename = convert_flatbuffer_file(
292 flatc, framework_schema, framework_filename, args.output_dir
293 )
294 if not framework_filename:
295 # Failed to convert the file, json2fbbin will have printed an error
296 return 1
297
298 # Convert input files to JSON
299 ifm_files = []
300 for file in test_desc["ifm_file"]:
301 if file is None:
302 ifm_files.append(None)
303 else:
304 path = desc_filename.parent / file
305 convert_numpy_file(path, args.output_dir)
306 ifm_files.append(path.name)
307 # Rewrite input file names to make sure the paths are correct,
308 # but keep them numpys as the test runner will convert them back
309 # before giving them to the SUT
310 test_desc["ifm_file"] = ifm_files
311
312 # Update desc.json and convert result files to JSON
313 test_desc = update_desc_json(
Jeremy Johnson88588622022-07-12 16:42:29 +0100314 desc_filename.parent,
315 test_desc,
316 output_dir=args.output_dir,
317 create_result=True,
318 profiles=args.profile,
Jeremy Johnson6179c212022-01-13 13:46:35 +0000319 )
320 if not test_desc:
321 # Error from conversion/update
322 return 1
323
324 # Output new desc.json
325 new_desc_filename = args.output_dir / NAME_DESC_FILENAME
326 with open(new_desc_filename, "w") as fd:
327 json.dump(test_desc, fd, indent=2)
328
Jeremy Johnson0ecfa372022-06-30 14:27:56 +0100329 logger.info(f"Converted {test_type_desc} test to {args.output_dir}")
Jeremy Johnson6179c212022-01-13 13:46:35 +0000330 return 0
331
332
333if __name__ == "__main__":
334 exit(main())