Skip to content

Commit 2a3db61

Browse files
Add custom exception handling to builder (#69)
* Introduces custom exceptions that are handled in the top level builder loop. * In the future these exceptions should be introduced throughout the entire code base. --------- Co-authored-by: bwintermann <bjarne.wintermann@uni-paderborn.de>
1 parent 6cf6c7b commit 2a3db61

3 files changed

Lines changed: 118 additions & 39 deletions

File tree

src/finn/builder/build_dataflow.py

Lines changed: 74 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import json
3333
import logging
3434
import os
35+
from pathlib import Path
3536
from typing import Callable
3637

3738
import pdb # isort: split
@@ -43,6 +44,7 @@
4344

4445
from finn.builder.build_dataflow_config import DataflowBuildConfig, default_build_dataflow_steps
4546
from finn.builder.build_dataflow_steps import build_dataflow_step_lookup
47+
from finn.util.exception import FINNConfigurationError, FINNDataflowError, FINNError, FINNUserError
4648

4749

4850
# adapted from https://stackoverflow.com/a/39215961
@@ -140,12 +142,20 @@ def resolve_build_steps(cfg: DataflowBuildConfig, partial: bool = True) -> list[
140142

141143
def resolve_step_filename(step_name: str, cfg: DataflowBuildConfig, step_delta: int = 0):
142144
step_names = list(map(lambda x: x.__name__, resolve_build_steps(cfg, partial=False)))
143-
assert step_name in step_names, "start_step %s not found" + step_name
145+
if step_name not in step_names:
146+
raise FINNConfigurationError(
147+
f"Cannot restart from step {step_name}.Step {step_name} for restarting not found."
148+
)
144149
step_no = step_names.index(step_name) + step_delta
145-
assert step_no >= 0, "Invalid step+delta combination"
146-
assert step_no < len(step_names), "Invalid step+delta combination"
150+
if step_no < 0 or step_no >= len(step_names):
151+
raise FINNDataflowError("Invalid step+delta combination")
147152
filename = cfg.output_dir + "/intermediate_models/"
148153
filename += "%s.onnx" % (step_names[step_no])
154+
if not Path(filename).exists():
155+
raise FINNConfigurationError(
156+
f"Expected model file at {filename} to start from step "
157+
f"{step_name}, but could not find it!"
158+
)
149159
return filename
150160

151161

@@ -155,19 +165,6 @@ def build_dataflow_cfg(model_filename, cfg: DataflowBuildConfig):
155165
:param model_filename: ONNX model filename to build
156166
:param cfg: Build configuration
157167
"""
158-
# if start_step is specified, override the input model
159-
if cfg.start_step is None:
160-
print(f"Building dataflow accelerator from {model_filename}")
161-
model = ModelWrapper(model_filename)
162-
else:
163-
intermediate_model_filename = resolve_step_filename(cfg.start_step, cfg, -1)
164-
out = (
165-
f"Building dataflow accelerator from intermediate"
166-
f" checkpoint {intermediate_model_filename}"
167-
)
168-
print(out)
169-
model = ModelWrapper(intermediate_model_filename)
170-
assert type(model) is ModelWrapper
171168
finn_build_dir = os.environ["FINN_BUILD_DIR"]
172169

173170
print(f"Intermediate outputs will be generated in {finn_build_dir}")
@@ -205,27 +202,50 @@ def build_dataflow_cfg(model_filename, cfg: DataflowBuildConfig):
205202

206203
if cfg.console_log_level != "NONE":
207204
# set up console logger
208-
console = RichHandler(show_time=True, show_path=False, console=console)
205+
consoleHandler = RichHandler(show_time=True, show_path=False, console=console)
209206

210207
if cfg.console_log_level == "DEBUG":
211-
console.setLevel(logging.DEBUG)
208+
consoleHandler.setLevel(logging.DEBUG)
212209
elif cfg.console_log_level == "INFO":
213-
console.setLevel(logging.INFO)
210+
consoleHandler.setLevel(logging.INFO)
214211
elif cfg.console_log_level == "WARNING":
215-
console.setLevel(logging.WARNING)
212+
consoleHandler.setLevel(logging.WARNING)
216213
elif cfg.console_log_level == "ERROR":
217-
console.setLevel(logging.ERROR)
214+
consoleHandler.setLevel(logging.ERROR)
218215
elif cfg.console_log_level == "CRITICAL":
219-
console.setLevel(logging.CRITICAL)
220-
logging.getLogger().addHandler(console)
221-
222-
# start processing
223-
step_num = 1
224-
time_per_step = dict()
225-
build_dataflow_steps = resolve_build_steps(cfg)
216+
consoleHandler.setLevel(logging.CRITICAL)
217+
logging.getLogger().addHandler(consoleHandler)
226218

227-
for transform_step in build_dataflow_steps:
228-
try:
219+
# Setup done, start processing
220+
try:
221+
# if start_step is specified, override the input model
222+
if cfg.start_step is None:
223+
print(f"Building dataflow accelerator from {model_filename}")
224+
model = ModelWrapper(model_filename)
225+
else:
226+
if model_filename != "":
227+
log.warning(
228+
"When using a start-step, FINN automatically searches "
229+
"for the correct model to use from previous runs, overwriting your "
230+
"passed model file (but still using it's path for the location of the "
231+
"temporary file directory, etc.). This behaviour might change "
232+
"in future versions!"
233+
)
234+
intermediate_model_filename = resolve_step_filename(cfg.start_step, cfg, -1)
235+
out = (
236+
f"Building dataflow accelerator from intermediate"
237+
f" checkpoint {intermediate_model_filename}"
238+
)
239+
print(out)
240+
model = ModelWrapper(intermediate_model_filename)
241+
assert type(model) is ModelWrapper
242+
243+
# start processing
244+
step_num = 1
245+
time_per_step = dict()
246+
build_dataflow_steps = resolve_build_steps(cfg)
247+
248+
for transform_step in build_dataflow_steps:
229249
step_name = transform_step.__name__
230250
print(f"Running step: {step_name} [{step_num}/{len(build_dataflow_steps)}]")
231251

@@ -241,17 +261,33 @@ def build_dataflow_cfg(model_filename, cfg: DataflowBuildConfig):
241261
os.makedirs(intermediate_model_dir)
242262
model.save(os.path.join(intermediate_model_dir, chkpt_name))
243263
step_num += 1
244-
except: # noqa
264+
except KeyboardInterrupt:
265+
print("KeyboardInterrupt detected. Aborting...")
266+
print("Build failed")
267+
return -1
268+
except (Exception, FINNError) as e:
269+
# Print full traceback if we are on debug log level
270+
# or encountered a non-user error
271+
print_full_traceback = True
272+
if issubclass(type(e), FINNUserError) and log.level != logging.DEBUG:
273+
print_full_traceback = False
274+
275+
extype, value, tb = sys.exc_info()
276+
if print_full_traceback:
245277
# print exception info and traceback
246-
extype, value, tb = sys.exc_info()
278+
log.error("FINN Internal compiler error:")
247279
console.print_exception(show_locals=False)
248-
# start postmortem debug if configured
249-
if cfg.enable_build_pdb_debug:
250-
pdb.post_mortem(tb)
251-
else:
252-
print("enable_build_pdb_debug not set in build config, exiting...")
280+
else:
281+
console.print(f"[bold red]FINN Error: [/bold red]{e}")
282+
log.error(f"{e}")
253283
print("Build failed")
254-
return -1
284+
return -1 # A user error shouldn't be need to be fixed using PDB
285+
286+
# start postmortem debug if configured
287+
if cfg.enable_build_pdb_debug:
288+
pdb.post_mortem(tb)
289+
print("Build failed")
290+
return -1
255291

256292
with open(os.path.join(cfg.output_dir, "time_per_step.json"), "w") as f:
257293
json.dump(time_per_step, f, indent=2)

src/finn/builder/build_dataflow_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ class DataflowBuildConfig(DataClassJSONMixin, DataClassYAMLMixin):
341341
enable_hw_debug: Optional[bool] = False
342342

343343
#: Whether pdb postmortem debuggig will be launched when the build fails
344-
enable_build_pdb_debug: Optional[bool] = True
344+
enable_build_pdb_debug: Optional[bool] = False
345345

346346
#: When True, additional verbose information will be written to the log file.
347347
#: Otherwise, these additional information will be suppressed.

src/finn/util/exception.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""
2+
FINNError is the base class for all errors.
3+
FINNUserError is a purely user-facing error that has nothing to do with FINNs internals
4+
FINNInternalError is a compiler internal error
5+
6+
Every error should subclass FINNUserError or FINNInternalError
7+
"""
8+
9+
10+
class FINNError(Exception):
11+
"""Base-class for FINN exceptions. Useful to differentiate exceptions while catching."""
12+
13+
def __init__(self, *args: object) -> None:
14+
super().__init__(*args)
15+
16+
17+
class FINNInternalError(FINNError):
18+
"""Custom exception class for internal compiler errors"""
19+
20+
def __init__(self, *args: object) -> None:
21+
super().__init__(*args)
22+
23+
24+
class FINNUserError(FINNError):
25+
"""Custom exception class which should be used to
26+
print errors without stacktraces if debug is disabled."""
27+
28+
def __init__(self, *args: object) -> None:
29+
super().__init__(*args)
30+
31+
32+
class FINNConfigurationError(FINNUserError):
33+
"""Error emitted when FINN is configured incorrectly"""
34+
35+
def __init__(self, *args: object) -> None:
36+
super().__init__(*args)
37+
38+
39+
class FINNDataflowError(FINNInternalError):
40+
"""Errors regarding the dataflow, dataflow config, step resolution, etc."""
41+
42+
def __init__(self, *args: object) -> None:
43+
super().__init__(*args)

0 commit comments

Comments
 (0)