Skip to content

Commit

Permalink
[RUNTIME] Scaffold structured error handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
tqchen committed Mar 18, 2019
1 parent c5c1787 commit 928d6ab
Show file tree
Hide file tree
Showing 15 changed files with 710 additions and 45 deletions.
2 changes: 1 addition & 1 deletion 3rdparty/dmlc-core
5 changes: 5 additions & 0 deletions docs/api/python/error.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tvm.error
---------
.. automodule:: tvm.error
:members:
:imported-members:
1 change: 1 addition & 0 deletions docs/api/python/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Python API
target
build
module
error
ndarray
container
function
Expand Down
118 changes: 118 additions & 0 deletions docs/contribute/error_handling.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
.. _error_guide:

Error Handling Guide
====================
TVM contains structured error classes to indicate specific types of error.
Please raise a specific error type when possible, so that users can
write code to handle a specific error category if necessary.

All the error types are defined in :any:`tvm.error` namespace.
You can directly raise the specific error object in python.
In other languages like c++, you simply add ``<ErrorType>:`` prefix to
the error message(see below).

Raise a Specific Error in C++
-----------------------------
You can add ``<ErrorType>:`` prefix to your error message to
raise an error of the corresponding type.
Note that you do not have to add a new type
:any:`tvm.error.TVMError` will be raised by default when
there is no error type prefix in the message.
This mechanism works for both ``LOG(FATAL)`` and ``CHECK`` macros.
The following code gives an example on how to do so.

.. code:: c
// src/api_test.cc
void ErrorTest(int x, int y) {
CHECK_EQ(x, y) << "ValueError: expect x and y to be equal."
if (x == 1) {
LOG(FATAL) << "InternalError: cannot reach here";
}
}
The above function is registered as PackedFunc into the python frontend,
under the name ``tvm._api_internal._ErrorTest``.
Here is what will happen if we call the registered function:

.. code::
>>> import tvm
>>> tvm._api_internal._ErrorTest(0, 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/to/tvm/python/tvm/_ffi/_ctypes/function.py", line 190, in __call__
raise get_last_ffi_error()
ValueError: Traceback (most recent call last):
[bt] (3) /path/to/tvm/build/libtvm.so(TVMFuncCall+0x48) [0x7fab500b8ca8]
[bt] (2) /path/to/tvm/build/libtvm.so(+0x1c4126) [0x7fab4f7f5126]
[bt] (1) /path/to/tvm/build/libtvm.so(+0x1ba2f8) [0x7fab4f7eb2f8]
[bt] (0) /path/to/tvm/build/libtvm.so(+0x177d12) [0x7fab4f7a8d12]
File "/path/to/tvm/src/api/api_test.cc", line 80
ValueError: Check failed: x == y (0 vs. 1) : expect x and y to be equal.
>>>
>>> tvm._api_internal._ErrorTest(1, 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/to/tvm/python/tvm/_ffi/_ctypes/function.py", line 190, in __call__
raise get_last_ffi_error()
tvm.error.InternalError: Traceback (most recent call last):
[bt] (3) /path/to/tvm/build/libtvm.so(TVMFuncCall+0x48) [0x7fab500b8ca8]
[bt] (2) /path/to/tvm/build/libtvm.so(+0x1c4126) [0x7fab4f7f5126]
[bt] (1) /path/to/tvm/build/libtvm.so(+0x1ba35c) [0x7fab4f7eb35c]
[bt] (0) /path/to/tvm/build/libtvm.so(+0x177d12) [0x7fab4f7a8d12]
File "/path/to/tvm/src/api/api_test.cc", line 83
InternalError: cannot reach here
TVM hint: You hit an internal error. Please open a thread on https://discuss.tvm.ai/ to report it.
As you can see in the above example, TVM's ffi system combines
both the python and c++'s stacktrace into a single message, and generate the
corresponding error class automatically.


How to choose an Error Type
---------------------------
You can go through the error types are listed below, try to us common
sense and also refer to the choices in the existing code.
We try to keep a reasonable amount of error types.
If you feel there is a need to add a new error type, do the following steps:

- Send a RFC proposal with a description and usage examples in the current codebase.
- Add the new error type to :any:`tvm.error` with clear documents.
- Update the list in this file to include the new error type.
- Change the code to use the new error type.

We also recommend to use less abstraction when creating the short error messages.
The code is more readable in this way, and also opens path to craft specific
error messages when necessary.

.. code:: python
def preferred():
# very clear about what is being raised and what is the error message.
raise OpNotImplemented("Operator relu is not implemented in the MXNet fronend")
def _op_not_implemented(op_name):
return OpNotImpelemented("Operator {} is not implemented.").format(op_name)
def not_preferred():
raise _op_not_implemented("relu")
System-wide Errors
------------------

.. autoclass:: tvm.error.TVMError

.. autoclass:: tvm.error.InternalError


Frontend Errors
---------------
.. autoclass:: tvm.error.OpNotImplemented

.. autoclass:: tvm.error.OpAttributeInvalid

.. autoclass:: tvm.error.OpAttributeRequired

.. autoclass:: tvm.error.OpAttributeNotImplemented
1 change: 1 addition & 0 deletions docs/contribute/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ Here are guidelines for contributing to various aspect of the project:
committer_guide
document
code_guide
error_handling
pull_request
git_howto
1 change: 1 addition & 0 deletions python/tvm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from . import generic
from . import hybrid
from . import testing
from . import error

from . import ndarray as nd
from .ndarray import context, cpu, gpu, opencl, cl, vulkan, metal, mtl
Expand Down
28 changes: 17 additions & 11 deletions python/tvm/_ffi/_ctypes/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import traceback
from numbers import Number, Integral

from ..base import _LIB, check_call
from ..base import _LIB, get_last_ffi_error, py2cerror
from ..base import c_str, string_types
from ..node_generic import convert_to_node, NodeGeneric
from ..runtime_ctypes import TVMType, TVMByteArray, TVMContext
Expand Down Expand Up @@ -55,6 +55,7 @@ def cfun(args, type_codes, num_args, ret, _):
rv = local_pyfunc(*pyargs)
except Exception:
msg = traceback.format_exc()
msg = py2cerror(msg)
_LIB.TVMAPISetLastError(c_str(msg))
return -1

Expand All @@ -65,7 +66,8 @@ def cfun(args, type_codes, num_args, ret, _):
values, tcodes, _ = _make_tvm_args((rv,), temp_args)
if not isinstance(ret, TVMRetValueHandle):
ret = TVMRetValueHandle(ret)
check_call(_LIB.TVMCFuncSetReturn(ret, values, tcodes, ctypes.c_int(1)))
if _LIB.TVMCFuncSetReturn(ret, values, tcodes, ctypes.c_int(1)) != 0:
raise get_last_ffi_error()
_ = temp_args
_ = rv
return 0
Expand All @@ -76,8 +78,9 @@ def cfun(args, type_codes, num_args, ret, _):
# TVM_FREE_PYOBJ will be called after it is no longer needed.
pyobj = ctypes.py_object(f)
ctypes.pythonapi.Py_IncRef(pyobj)
check_call(_LIB.TVMFuncCreateFromCFunc(
f, pyobj, TVM_FREE_PYOBJ, ctypes.byref(handle)))
if _LIB.TVMFuncCreateFromCFunc(
f, pyobj, TVM_FREE_PYOBJ, ctypes.byref(handle)) != 0:
raise get_last_ffi_error()
return _CLASS_FUNCTION(handle, False)


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

def __del__(self):
if not self.is_global and _LIB is not None:
check_call(_LIB.TVMFuncFree(self.handle))
if _LIB.TVMFuncFree(self.handle) != 0:
raise get_last_ffi_error()

def __call__(self, *args):
"""Call the function with positional arguments
Expand All @@ -180,9 +184,10 @@ def __call__(self, *args):
values, tcodes, num_args = _make_tvm_args(args, temp_args)
ret_val = TVMValue()
ret_tcode = ctypes.c_int()
check_call(_LIB.TVMFuncCall(
self.handle, values, tcodes, ctypes.c_int(num_args),
ctypes.byref(ret_val), ctypes.byref(ret_tcode)))
if _LIB.TVMFuncCall(
self.handle, values, tcodes, ctypes.c_int(num_args),
ctypes.byref(ret_val), ctypes.byref(ret_tcode)) != 0:
raise get_last_ffi_error()
_ = temp_args
_ = args
return RETURN_SWITCH[ret_tcode.value](ret_val)
Expand All @@ -194,9 +199,10 @@ def __init_handle_by_constructor__(fconstructor, args):
values, tcodes, num_args = _make_tvm_args(args, temp_args)
ret_val = TVMValue()
ret_tcode = ctypes.c_int()
check_call(_LIB.TVMFuncCall(
fconstructor.handle, values, tcodes, ctypes.c_int(num_args),
ctypes.byref(ret_val), ctypes.byref(ret_tcode)))
if _LIB.TVMFuncCall(
fconstructor.handle, values, tcodes, ctypes.c_int(num_args),
ctypes.byref(ret_val), ctypes.byref(ret_tcode)) != 0:
raise get_last_ffi_error()
_ = temp_args
_ = args
assert ret_tcode.value == TypeCode.NODE_HANDLE
Expand Down
4 changes: 2 additions & 2 deletions python/tvm/_ffi/_cython/base.pxi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ..base import TVMError
from ..base import get_last_ffi_error
from libcpp.vector cimport vector
from cpython.version cimport PY_MAJOR_VERSION
from cpython cimport pycapsule
Expand Down Expand Up @@ -148,7 +148,7 @@ cdef inline c_str(pystr):

cdef inline CALL(int ret):
if ret != 0:
raise TVMError(py_str(TVMGetLastError()))
raise get_last_ffi_error()


cdef inline object ctypes_handle(void* chandle):
Expand Down
3 changes: 2 additions & 1 deletion python/tvm/_ffi/_cython/function.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ctypes
import traceback
from cpython cimport Py_INCREF, Py_DECREF
from numbers import Number, Integral
from ..base import string_types
from ..base import string_types, py2cerror
from ..node_generic import convert_to_node, NodeGeneric
from ..runtime_ctypes import TVMType, TVMContext, TVMByteArray

Expand Down Expand Up @@ -38,6 +38,7 @@ cdef int tvm_callback(TVMValue* args,
rv = local_pyfunc(*pyargs)
except Exception:
msg = traceback.format_exc()
msg = py2cerror(msg)
TVMAPISetLastError(c_str(msg))
return -1
if rv is not None:
Expand Down
Loading

0 comments on commit 928d6ab

Please sign in to comment.