Skip to content

Commit f63631f

Browse files
authored
[RUNTIME] Scaffold structured error handling. (#2838)
1 parent a1c2fd1 commit f63631f

15 files changed

Lines changed: 715 additions & 45 deletions

File tree

docs/api/python/error.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
tvm.error
2+
---------
3+
.. automodule:: tvm.error
4+
:members:
5+
:imported-members:

docs/api/python/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Python API
1111
target
1212
build
1313
module
14+
error
1415
ndarray
1516
container
1617
function

docs/contribute/error_handling.rst

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
.. _error_guide:
2+
3+
Error Handling Guide
4+
====================
5+
TVM contains structured error classes to indicate specific types of error.
6+
Please raise a specific error type when possible, so that users can
7+
write code to handle a specific error category if necessary.
8+
9+
All the error types are defined in :any:`tvm.error` namespace.
10+
You can directly raise the specific error object in python.
11+
In other languages like c++, you simply add ``<ErrorType>:`` prefix to
12+
the error message(see below).
13+
14+
Raise a Specific Error in C++
15+
-----------------------------
16+
You can add ``<ErrorType>:`` prefix to your error message to
17+
raise an error of the corresponding type.
18+
Note that you do not have to add a new type
19+
:any:`tvm.error.TVMError` will be raised by default when
20+
there is no error type prefix in the message.
21+
This mechanism works for both ``LOG(FATAL)`` and ``CHECK`` macros.
22+
The following code gives an example on how to do so.
23+
24+
.. code:: c
25+
26+
// src/api_test.cc
27+
void ErrorTest(int x, int y) {
28+
CHECK_EQ(x, y) << "ValueError: expect x and y to be equal."
29+
if (x == 1) {
30+
LOG(FATAL) << "InternalError: cannot reach here";
31+
}
32+
}
33+
34+
The above function is registered as PackedFunc into the python frontend,
35+
under the name ``tvm._api_internal._ErrorTest``.
36+
Here is what will happen if we call the registered function:
37+
38+
.. code::
39+
40+
>>> import tvm
41+
>>> tvm._api_internal._ErrorTest(0, 1)
42+
Traceback (most recent call last):
43+
File "<stdin>", line 1, in <module>
44+
File "/path/to/tvm/python/tvm/_ffi/_ctypes/function.py", line 190, in __call__
45+
raise get_last_ffi_error()
46+
ValueError: Traceback (most recent call last):
47+
[bt] (3) /path/to/tvm/build/libtvm.so(TVMFuncCall+0x48) [0x7fab500b8ca8]
48+
[bt] (2) /path/to/tvm/build/libtvm.so(+0x1c4126) [0x7fab4f7f5126]
49+
[bt] (1) /path/to/tvm/build/libtvm.so(+0x1ba2f8) [0x7fab4f7eb2f8]
50+
[bt] (0) /path/to/tvm/build/libtvm.so(+0x177d12) [0x7fab4f7a8d12]
51+
File "/path/to/tvm/src/api/api_test.cc", line 80
52+
ValueError: Check failed: x == y (0 vs. 1) : expect x and y to be equal.
53+
>>>
54+
>>> tvm._api_internal._ErrorTest(1, 1)
55+
Traceback (most recent call last):
56+
File "<stdin>", line 1, in <module>
57+
File "/path/to/tvm/python/tvm/_ffi/_ctypes/function.py", line 190, in __call__
58+
raise get_last_ffi_error()
59+
tvm.error.InternalError: Traceback (most recent call last):
60+
[bt] (3) /path/to/tvm/build/libtvm.so(TVMFuncCall+0x48) [0x7fab500b8ca8]
61+
[bt] (2) /path/to/tvm/build/libtvm.so(+0x1c4126) [0x7fab4f7f5126]
62+
[bt] (1) /path/to/tvm/build/libtvm.so(+0x1ba35c) [0x7fab4f7eb35c]
63+
[bt] (0) /path/to/tvm/build/libtvm.so(+0x177d12) [0x7fab4f7a8d12]
64+
File "/path/to/tvm/src/api/api_test.cc", line 83
65+
InternalError: cannot reach here
66+
TVM hint: You hit an internal error. Please open a thread on https://discuss.tvm.ai/ to report it.
67+
68+
As you can see in the above example, TVM's ffi system combines
69+
both the python and c++'s stacktrace into a single message, and generate the
70+
corresponding error class automatically.
71+
72+
73+
How to choose an Error Type
74+
---------------------------
75+
You can go through the error types are listed below, try to use common
76+
sense and also refer to the choices in the existing code.
77+
We try to keep a reasonable amount of error types.
78+
If you feel there is a need to add a new error type, do the following steps:
79+
80+
- Send a RFC proposal with a description and usage examples in the current codebase.
81+
- Add the new error type to :any:`tvm.error` with clear documents.
82+
- Update the list in this file to include the new error type.
83+
- Change the code to use the new error type.
84+
85+
We also recommend to use less abstraction when creating the short error messages.
86+
The code is more readable in this way, and also opens path to craft specific
87+
error messages when necessary.
88+
89+
.. code:: python
90+
91+
def preferred():
92+
# Very clear about what is being raised and what is the error message.
93+
raise OpNotImplemented("Operator relu is not implemented in the MXNet fronend")
94+
95+
def _op_not_implemented(op_name):
96+
return OpNotImplemented("Operator {} is not implemented.").format(op_name)
97+
98+
def not_preferred():
99+
# Introduces another level of indirection.
100+
raise _op_not_implemented("relu")
101+
102+
If we need to introduce a wrapper function that constructs multi-line error messages,
103+
please put wrapper in the same file so other developers can look up the implementation easily.
104+
105+
106+
System-wide Errors
107+
------------------
108+
109+
.. autoclass:: tvm.error.TVMError
110+
111+
.. autoclass:: tvm.error.InternalError
112+
113+
114+
Frontend Errors
115+
---------------
116+
.. autoclass:: tvm.error.OpNotImplemented
117+
118+
.. autoclass:: tvm.error.OpAttributeInvalid
119+
120+
.. autoclass:: tvm.error.OpAttributeRequired
121+
122+
.. autoclass:: tvm.error.OpAttributeNotImplemented

docs/contribute/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ Here are guidelines for contributing to various aspect of the project:
2828
committer_guide
2929
document
3030
code_guide
31+
error_handling
3132
pull_request
3233
git_howto

python/tvm/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from . import generic
2020
from . import hybrid
2121
from . import testing
22+
from . import error
2223

2324
from . import ndarray as nd
2425
from .ndarray import context, cpu, gpu, opencl, cl, vulkan, metal, mtl

python/tvm/_ffi/_ctypes/function.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import traceback
88
from numbers import Number, Integral
99

10-
from ..base import _LIB, check_call
10+
from ..base import _LIB, get_last_ffi_error, py2cerror
1111
from ..base import c_str, string_types
1212
from ..node_generic import convert_to_node, NodeGeneric
1313
from ..runtime_ctypes import TVMType, TVMByteArray, TVMContext
@@ -55,6 +55,7 @@ def cfun(args, type_codes, num_args, ret, _):
5555
rv = local_pyfunc(*pyargs)
5656
except Exception:
5757
msg = traceback.format_exc()
58+
msg = py2cerror(msg)
5859
_LIB.TVMAPISetLastError(c_str(msg))
5960
return -1
6061

@@ -65,7 +66,8 @@ def cfun(args, type_codes, num_args, ret, _):
6566
values, tcodes, _ = _make_tvm_args((rv,), temp_args)
6667
if not isinstance(ret, TVMRetValueHandle):
6768
ret = TVMRetValueHandle(ret)
68-
check_call(_LIB.TVMCFuncSetReturn(ret, values, tcodes, ctypes.c_int(1)))
69+
if _LIB.TVMCFuncSetReturn(ret, values, tcodes, ctypes.c_int(1)) != 0:
70+
raise get_last_ffi_error()
6971
_ = temp_args
7072
_ = rv
7173
return 0
@@ -76,8 +78,9 @@ def cfun(args, type_codes, num_args, ret, _):
7678
# TVM_FREE_PYOBJ will be called after it is no longer needed.
7779
pyobj = ctypes.py_object(f)
7880
ctypes.pythonapi.Py_IncRef(pyobj)
79-
check_call(_LIB.TVMFuncCreateFromCFunc(
80-
f, pyobj, TVM_FREE_PYOBJ, ctypes.byref(handle)))
81+
if _LIB.TVMFuncCreateFromCFunc(
82+
f, pyobj, TVM_FREE_PYOBJ, ctypes.byref(handle)) != 0:
83+
raise get_last_ffi_error()
8184
return _CLASS_FUNCTION(handle, False)
8285

8386

@@ -168,7 +171,8 @@ def __init__(self, handle, is_global):
168171

169172
def __del__(self):
170173
if not self.is_global and _LIB is not None:
171-
check_call(_LIB.TVMFuncFree(self.handle))
174+
if _LIB.TVMFuncFree(self.handle) != 0:
175+
raise get_last_ffi_error()
172176

173177
def __call__(self, *args):
174178
"""Call the function with positional arguments
@@ -180,9 +184,10 @@ def __call__(self, *args):
180184
values, tcodes, num_args = _make_tvm_args(args, temp_args)
181185
ret_val = TVMValue()
182186
ret_tcode = ctypes.c_int()
183-
check_call(_LIB.TVMFuncCall(
184-
self.handle, values, tcodes, ctypes.c_int(num_args),
185-
ctypes.byref(ret_val), ctypes.byref(ret_tcode)))
187+
if _LIB.TVMFuncCall(
188+
self.handle, values, tcodes, ctypes.c_int(num_args),
189+
ctypes.byref(ret_val), ctypes.byref(ret_tcode)) != 0:
190+
raise get_last_ffi_error()
186191
_ = temp_args
187192
_ = args
188193
return RETURN_SWITCH[ret_tcode.value](ret_val)
@@ -194,9 +199,10 @@ def __init_handle_by_constructor__(fconstructor, args):
194199
values, tcodes, num_args = _make_tvm_args(args, temp_args)
195200
ret_val = TVMValue()
196201
ret_tcode = ctypes.c_int()
197-
check_call(_LIB.TVMFuncCall(
198-
fconstructor.handle, values, tcodes, ctypes.c_int(num_args),
199-
ctypes.byref(ret_val), ctypes.byref(ret_tcode)))
202+
if _LIB.TVMFuncCall(
203+
fconstructor.handle, values, tcodes, ctypes.c_int(num_args),
204+
ctypes.byref(ret_val), ctypes.byref(ret_tcode)) != 0:
205+
raise get_last_ffi_error()
200206
_ = temp_args
201207
_ = args
202208
assert ret_tcode.value == TypeCode.NODE_HANDLE

python/tvm/_ffi/_cython/base.pxi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from ..base import TVMError
1+
from ..base import get_last_ffi_error
22
from libcpp.vector cimport vector
33
from cpython.version cimport PY_MAJOR_VERSION
44
from cpython cimport pycapsule
@@ -148,7 +148,7 @@ cdef inline c_str(pystr):
148148

149149
cdef inline CALL(int ret):
150150
if ret != 0:
151-
raise TVMError(py_str(TVMGetLastError()))
151+
raise get_last_ffi_error()
152152

153153

154154
cdef inline object ctypes_handle(void* chandle):

python/tvm/_ffi/_cython/function.pxi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import ctypes
22
import traceback
33
from cpython cimport Py_INCREF, Py_DECREF
44
from numbers import Number, Integral
5-
from ..base import string_types
5+
from ..base import string_types, py2cerror
66
from ..node_generic import convert_to_node, NodeGeneric
77
from ..runtime_ctypes import TVMType, TVMContext, TVMByteArray
88

@@ -38,6 +38,7 @@ cdef int tvm_callback(TVMValue* args,
3838
rv = local_pyfunc(*pyargs)
3939
except Exception:
4040
msg = traceback.format_exc()
41+
msg = py2cerror(msg)
4142
TVMAPISetLastError(c_str(msg))
4243
return -1
4344
if rv is not None:

0 commit comments

Comments
 (0)