blob: 71f263bbfe48b0959d92510c349611682805aaba [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
33
34def parse_args(argv):
35 """Parse the arguments."""
36 parser = argparse.ArgumentParser()
37 parser.add_argument(
38 "test_dir",
39 default=Path.cwd(),
40 type=Path,
41 nargs="?",
42 help="The test directory to convert (default is CWD)",
43 )
44 parser.add_argument(
45 "--ref-model-directory",
46 dest="ref_model_dir",
47 type=Path,
48 required=True,
49 help="Reference Model directory (must be pre-built)",
50 )
51 parser.add_argument(
52 "--output-directory",
53 dest="output_dir",
54 type=Path,
55 default=Path.cwd() / "conformance",
56 help="Output directory (default is conformance in CWD)",
57 )
58 parser.add_argument(
59 "--framework",
60 dest="framework",
61 choices=["tflite"],
62 default="tflite",
63 help="Framework to convert (default tflite)",
64 )
65 parser.add_argument(
66 "--framework-schema",
67 dest="framework_schema",
68 type=Path,
69 help="Framework schema needed to convert framework models",
70 )
71 parser.add_argument(
72 "-v", "--verbose", dest="verbose", action="store_true", help="Verbose operation"
73 )
74 args = parser.parse_args(argv)
75 return args
76
77
78def find_ref_model_artifacts(path: Path):
79 """Check the location of the flatc compiler and schema artifacts."""
80 flatc = path / LOCATION_REF_MODEL_FLATC
81 schema = path / LOCATION_REF_MODEL_SCHEMA
82 if not flatc.is_file():
83 raise Exception(
84 f"flatc not found in {flatc}\nHave you built the flatbuffers compiler?"
85 )
86 if not schema.is_file():
87 raise Exception(
88 f"TOSA schema not found at {schema}\nHave you checked out the submodules?"
89 )
90 return flatc, schema
91
92
93def find_framework_artifacts(framework: str, schema_path: Path, desc_file: Path):
94 """Check that any required schema has been supplied for conversion."""
95 if framework == "tflite":
96 if not schema_path:
97 raise Exception("the following arguments are required: --framework-schema")
98 elif not schema_path.is_file():
99 raise Exception(f"framework schema not found at {schema_path}")
100 model = desc_file.parent.parent / "model.tflite"
101 if not model.is_file():
102 raise Exception(f"Model file not found at {model}")
103 return schema_path, model
104 return None, None
105
106
107def get_framework_name(name_array: list, framework: str):
108 """Get the framework conversion directory name."""
109 name = ""
110 for part in name_array:
111 if part == "_FW_":
112 part = framework
113 name = f"{name}{part}"
114 return name
115
116
117def convert_flatbuffer_file(flatc: Path, schema: Path, model_file: Path, output: Path):
118 """Convert the flatbuffer binary into JSON."""
119 try:
120 fbbin_to_json(flatc, schema, model_file, output)
121 except Exception as e:
122 logger.error(f"Failed to convert flatbuffer binary:\n{e}")
123 return None
124
125 if model_file.name == "model.tflite":
126 file_name = "model-tflite.json"
127 os.rename(output / "model.json", output / file_name)
128 else:
129 file_name = model_file.stem + ".json"
130 return output / file_name
131
132
133def convert_numpy_file(n_file: Path, output: Path, outname: Optional[str] = None):
134 """Convert a numpy file into a JSON file."""
135 j_file = output / (outname if outname else (n_file.stem + ".json"))
136 npy_to_json(n_file, j_file)
137 return j_file
138
139
140def update_desc_json(
141 test_dir: Path, test_desc, output_dir: Optional[Path] = None, create_result=True
142):
143 """Update the desc.json format for conformance and optionally create result."""
144 ofm_files = []
145 cfm_files = []
146 if not output_dir:
147 output_dir = test_dir
148 for index, ofm in enumerate(test_desc["ofm_file"]):
149 ofm_path = test_dir / ofm
150 if not test_desc["expected_failure"]:
151 cfm = NAME_CONFORMANCE_RESULT_PREFIX + test_desc["ofm_name"][index]
152 if create_result:
153 if ofm_path.is_file():
154 # Use the desc.json name
155 ofm_refmodel = ofm_path
156 else:
157 # Adjust for renaming due to tosa_verif_run_tests
158 ofm_refmodel = ofm_path.with_suffix(NAME_REFMODEL_RUN_RESULT_SUFFIX)
159 # Create conformance result
160 if ofm_refmodel.is_file():
161 convert_numpy_file(ofm_refmodel, output_dir, outname=cfm + ".json")
162 else:
163 logger.error(f"Missing result file {ofm_path}")
164 return None
165 cfm_files.append(cfm + ".npy")
166 # Remove path and "ref-"/"ref_model_" from output filenames
167 ofm_files.append(strip_ref_output_name(ofm_path.name))
168
169 # Rewrite output file names as they can be relative, but keep them npys
170 test_desc["ofm_file"] = ofm_files
171 if not test_desc["expected_failure"]:
172 # Output expected result file for conformance if expected pass
173 test_desc["expected_result_file"] = cfm_files
174 return test_desc
175
176
177def strip_ref_output_name(name):
178 """Remove mentions of reference from output files."""
179 if name.startswith("ref-"):
180 name = name[4:]
181 if name.startswith("ref_model_"):
182 name = name[10:]
183 return name
184
185
186def main(argv=None):
187 """Convert the given directory to a conformance test."""
188 args = parse_args(argv)
189 # Verbosity
190 if args.verbose:
191 logger.setLevel(logging.DEBUG)
192
193 # Check we can get the files we need
194 try:
195 flatc, schema = find_ref_model_artifacts(args.ref_model_dir)
196 except Exception as err:
197 logger.error(err)
198 return 2
199
200 # Work out where the desc.json file is
201 desc_filename = args.test_dir / NAME_DESC_FILENAME
202 framework_conversion = False
203 if desc_filename.is_file():
204 logger.info("Found reference model unit test")
205 else:
206 desc_filename = (
207 args.test_dir
208 / get_framework_name(NAME_FLATBUFFER_DIR, args.framework)
209 / NAME_DESC_FILENAME
210 )
211 if desc_filename.is_file():
212 logger.info(f"Found framework unit test for {args.framework}")
213 framework_conversion = True
214 else:
215 logger.error(f"Could not find {NAME_DESC_FILENAME} in {args.test_dir}")
216 return 2
217 logger.debug(f"desc.json file: {desc_filename}")
218
219 # Check for required files for framework conversion
220 if framework_conversion:
221 try:
222 framework_schema, framework_filename = find_framework_artifacts(
223 args.framework, args.framework_schema, desc_filename
224 )
225 except Exception as err:
226 logger.error(err)
227 return 2
228 else:
229 framework_schema, framework_filename = None, None
230
231 # Open the meta desc.json file
232 with open(desc_filename, mode="r") as fd:
233 test_desc = json.load(fd)
234
235 if "tosa_file" not in test_desc:
236 logger.error(f"Unsupported desc.json file found {desc_filename}")
237 return 2
238
239 # Dictionary fix
240 if "ifm_name" not in test_desc:
241 logger.warn("Old format desc.json file found - attempting to fix up")
242 test_desc["ifm_name"] = test_desc["ifm_placeholder"]
243 del test_desc["ifm_placeholder"]
244
245 # Make the output directory if needed
246 try:
247 args.output_dir.mkdir(parents=True, exist_ok=True)
248 except FileExistsError:
249 logger.error(f"{args.output_dir} is not a directory")
250 return 2
251
252 # Convert the TOSA flatbuffer binary
253 tosa_filename = desc_filename.parent / test_desc["tosa_file"]
254 tosa_filename = convert_flatbuffer_file(
255 flatc, schema, tosa_filename, args.output_dir
256 )
257 if not tosa_filename:
258 # Failed to convert the file, json2fbbin will have printed an error
259 return 1
260 else:
261 # Replace binary with JSON name
262 test_desc["tosa_file"] = tosa_filename.name
263
264 if framework_conversion and framework_filename:
265 # Convert the framework flatbuffer binary
266 framework_filename = convert_flatbuffer_file(
267 flatc, framework_schema, framework_filename, args.output_dir
268 )
269 if not framework_filename:
270 # Failed to convert the file, json2fbbin will have printed an error
271 return 1
272
273 # Convert input files to JSON
274 ifm_files = []
275 for file in test_desc["ifm_file"]:
276 if file is None:
277 ifm_files.append(None)
278 else:
279 path = desc_filename.parent / file
280 convert_numpy_file(path, args.output_dir)
281 ifm_files.append(path.name)
282 # Rewrite input file names to make sure the paths are correct,
283 # but keep them numpys as the test runner will convert them back
284 # before giving them to the SUT
285 test_desc["ifm_file"] = ifm_files
286
287 # Update desc.json and convert result files to JSON
288 test_desc = update_desc_json(
289 desc_filename.parent, test_desc, output_dir=args.output_dir, create_result=True
290 )
291 if not test_desc:
292 # Error from conversion/update
293 return 1
294
295 # Output new desc.json
296 new_desc_filename = args.output_dir / NAME_DESC_FILENAME
297 with open(new_desc_filename, "w") as fd:
298 json.dump(test_desc, fd, indent=2)
299
300 logger.info(f"Converted to {args.output_dir}")
301 return 0
302
303
304if __name__ == "__main__":
305 exit(main())