You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tvm.apache.org by yo...@apache.org on 2025/03/08 01:00:05 UTC

(tvm) branch main updated: [FFI] Phase out ctypes fallback in favor of cython (#17714)

This is an automated email from the ASF dual-hosted git repository.

yongwww pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new 59d4077842 [FFI] Phase out ctypes fallback in favor of cython (#17714)
59d4077842 is described below

commit 59d40778421451a84a14a5e27c43c151615cfa1e
Author: Tianqi Chen <tq...@users.noreply.github.com>
AuthorDate: Fri Mar 7 19:59:59 2025 -0500

    [FFI] Phase out ctypes fallback in favor of cython (#17714)
    
    * [FFI] Phase out ctypes fallback in favor of cython
    
    In the past we maintained both ctypes fallback and cython path for our FFI.
    One reason was cython was not that widely available then.
    The situation has changed, so this PR removes the ctypes fallback
    so we can focus on building cython based core for FFI.
    
    Notably, the cython core still leverages ctypes, especially for
    some of the features that do not need fast path, and primarily
    uses cython to speedup core FFI features with fast path.
    
    With cython, we can get most FFI costs down to level
    of roughtly 1e-6 sec, which aligns with most of the costs like
    overhead of a numpy operation.
    
    * fix lint
    
    * Fix the dso loading issue
---
 python/tvm/_ffi/_ctypes/__init__.py                |  17 -
 python/tvm/_ffi/_ctypes/ndarray.py                 | 151 ---------
 python/tvm/_ffi/_ctypes/object.py                  | 189 -----------
 python/tvm/_ffi/_ctypes/packed_func.py             | 354 ---------------------
 python/tvm/_ffi/_ctypes/types.py                   | 116 -------
 python/tvm/_ffi/_cy2/__init__.py                   |  17 -
 python/tvm/_ffi/base.py                            |   3 +
 python/tvm/_ffi/registry.py                        |  23 +-
 python/tvm/runtime/ndarray.py                      |  29 +-
 python/tvm/runtime/object.py                       |  25 +-
 python/tvm/runtime/packed_func.py                  |  20 +-
 tests/lint/pylintrc                                |   3 +-
 tests/python/runtime/test_runtime_module_load.py   |   1 -
 tests/python/tvmscript/test_tvmscript_roundtrip.py |   1 +
 14 files changed, 30 insertions(+), 919 deletions(-)

diff --git a/python/tvm/_ffi/_ctypes/__init__.py b/python/tvm/_ffi/_ctypes/__init__.py
deleted file mode 100644
index b2851f4b27..0000000000
--- a/python/tvm/_ffi/_ctypes/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""ctypes specific implementation of FFI"""
diff --git a/python/tvm/_ffi/_ctypes/ndarray.py b/python/tvm/_ffi/_ctypes/ndarray.py
deleted file mode 100644
index fc8768448d..0000000000
--- a/python/tvm/_ffi/_ctypes/ndarray.py
+++ /dev/null
@@ -1,151 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# pylint: disable=invalid-name
-"""Runtime NDArray api"""
-import ctypes
-from ..base import _LIB, check_call, c_str
-from ..runtime_ctypes import TVMArrayHandle
-from .types import RETURN_SWITCH, C_TO_PY_ARG_SWITCH, _wrap_arg_func, _return_handle
-
-
-TVMPyCapsuleDestructor = ctypes.CFUNCTYPE(None, ctypes.c_void_p)
-_c_str_dltensor = c_str("dltensor")
-_c_str_used_dltensor = c_str("used_dltensor")
-
-
-# used for PyCapsule manipulation
-if hasattr(ctypes, "pythonapi"):
-    ctypes.pythonapi.PyCapsule_GetName.restype = ctypes.c_char_p
-    ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
-    ctypes.pythonapi.PyCapsule_New.restype = ctypes.py_object
-
-
-def _from_dlpack(dltensor):
-    dltensor = ctypes.py_object(dltensor)
-    if ctypes.pythonapi.PyCapsule_IsValid(dltensor, _c_str_dltensor):
-        ptr = ctypes.pythonapi.PyCapsule_GetPointer(dltensor, _c_str_dltensor)
-        # enforce type to make sure it works for all ctypes
-        ptr = ctypes.cast(ptr, ctypes.c_void_p)
-        handle = TVMArrayHandle()
-        check_call(_LIB.TVMArrayFromDLPack(ptr, ctypes.byref(handle)))
-        ctypes.pythonapi.PyCapsule_SetName(dltensor, _c_str_used_dltensor)
-        ctypes.pythonapi.PyCapsule_SetDestructor(dltensor, TVMPyCapsuleDestructor(0))
-        return _make_array(handle, False, False)
-    raise ValueError("Expect a dltensor field, PyCapsule can only be consumed once")
-
-
-def _dlpack_deleter(pycapsule):
-    pycapsule = ctypes.cast(pycapsule, ctypes.py_object)
-    if ctypes.pythonapi.PyCapsule_IsValid(pycapsule, _c_str_dltensor):
-        ptr = ctypes.pythonapi.PyCapsule_GetPointer(pycapsule, _c_str_dltensor)
-        # enforce type to make sure it works for all ctypes
-        ptr = ctypes.cast(ptr, ctypes.c_void_p)
-        _LIB.TVMDLManagedTensorCallDeleter(ptr)
-        ctypes.pythonapi.PyCapsule_SetDestructor(pycapsule, None)
-
-
-_c_dlpack_deleter = TVMPyCapsuleDestructor(_dlpack_deleter)
-
-
-class NDArrayBase(object):
-    """A simple Device/CPU Array object in runtime."""
-
-    __slots__ = ["handle", "is_view"]
-    # pylint: disable=no-member
-    def __init__(self, handle, is_view=False):
-        """Initialize the function with handle
-
-        Parameters
-        ----------
-        handle : TVMArrayHandle
-            the handle to the underlying C++ TVMArray
-        """
-        self.handle = handle
-        self.is_view = is_view
-
-    def __del__(self):
-        if not self.is_view and _LIB:
-            check_call(_LIB.TVMArrayFree(self.handle))
-
-    @property
-    def _tvm_handle(self):
-        return ctypes.cast(self.handle, ctypes.c_void_p).value
-
-    def _copyto(self, target_nd):
-        """Internal function that implements copy to target ndarray."""
-        check_call(_LIB.TVMArrayCopyFromTo(self.handle, target_nd.handle, None))
-        return target_nd
-
-    @property
-    def shape(self):
-        """Shape of this array"""
-        return tuple(self.handle.contents.shape[i] for i in range(self.handle.contents.ndim))
-
-    def to_dlpack(self):
-        """Produce an array from a DLPack Tensor without copying memory
-
-        Returns
-        -------
-        dlpack : DLPack tensor view of the array data
-        """
-        handle = ctypes.c_void_p()
-        check_call(_LIB.TVMArrayToDLPack(self.handle, ctypes.byref(handle)))
-        return ctypes.pythonapi.PyCapsule_New(handle, _c_str_dltensor, _c_dlpack_deleter)
-
-
-def _make_array(handle, is_view, is_container):
-    global _TVM_ND_CLS
-    handle = ctypes.cast(handle, TVMArrayHandle)
-    if is_container:
-        tindex = ctypes.c_uint()
-        check_call(_LIB.TVMArrayGetTypeIndex(handle, ctypes.byref(tindex)))
-        cls = _TVM_ND_CLS.get(tindex.value, _CLASS_NDARRAY)
-    else:
-        cls = _CLASS_NDARRAY
-
-    ret = cls.__new__(cls)
-    ret.handle = handle
-    ret.is_view = is_view
-    return ret
-
-
-_TVM_COMPATS = ()
-
-
-def _reg_extension(cls, fcreate):
-    global _TVM_COMPATS
-    _TVM_COMPATS += (cls,)
-    if fcreate:
-        fret = lambda x: fcreate(_return_handle(x))
-        RETURN_SWITCH[cls._tvm_tcode] = fret
-        C_TO_PY_ARG_SWITCH[cls._tvm_tcode] = _wrap_arg_func(fret, cls._tvm_tcode)
-
-
-_TVM_ND_CLS = {}
-
-
-def _register_ndarray(index, cls):
-    global _TVM_ND_CLS
-    _TVM_ND_CLS[index] = cls
-
-
-_CLASS_NDARRAY = None
-
-
-def _set_class_ndarray(cls):
-    global _CLASS_NDARRAY
-    _CLASS_NDARRAY = cls
diff --git a/python/tvm/_ffi/_ctypes/object.py b/python/tvm/_ffi/_ctypes/object.py
deleted file mode 100644
index 8f674eea2e..0000000000
--- a/python/tvm/_ffi/_ctypes/object.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# pylint: disable=invalid-name
-"""Runtime Object api"""
-import ctypes
-from ..base import _LIB, check_call
-from .types import ArgTypeCode, RETURN_SWITCH, C_TO_PY_ARG_SWITCH, _wrap_arg_func
-from .ndarray import _register_ndarray, NDArrayBase
-
-
-ObjectHandle = ctypes.c_void_p
-__init_by_constructor__ = None
-
-"""Maps object type index to its constructor"""
-OBJECT_TYPE = {}
-
-"""Maps object type to its type index"""
-OBJECT_INDEX = {}
-
-_CLASS_OBJECT = None
-
-
-def _set_class_object(object_class):
-    global _CLASS_OBJECT
-    _CLASS_OBJECT = object_class
-
-
-def _register_object(index, cls):
-    """register object class"""
-    if issubclass(cls, NDArrayBase):
-        _register_ndarray(index, cls)
-        return
-    OBJECT_TYPE[index] = cls
-    OBJECT_INDEX[cls] = index
-
-
-def _get_object_type_index(cls):
-    """get the type index of object class"""
-    return OBJECT_INDEX.get(cls)
-
-
-def _return_object(x):
-    handle = x.v_handle
-    if not isinstance(handle, ObjectHandle):
-        handle = ObjectHandle(handle)
-    tindex = ctypes.c_uint()
-    check_call(_LIB.TVMObjectGetTypeIndex(handle, ctypes.byref(tindex)))
-    cls = OBJECT_TYPE.get(tindex.value, _CLASS_OBJECT)
-
-    # Handle return values that subclass from both TVM objects and
-    # python native objects (e.g. runtime.String, a subclass of str).
-    if issubclass(cls, PyNativeObject):
-        obj = _CLASS_OBJECT.__new__(_CLASS_OBJECT)
-        obj.handle = handle
-        return cls.__from_tvm_object__(cls, obj)
-
-    # Avoid calling __init__ of cls, instead directly call __new__
-    # This allows child class to implement their own __init__
-    obj = cls.__new__(cls)
-    obj.handle = handle
-
-    # Handle return values that must be converted from the TVM object
-    # to a python native object.  This should be used in cases where
-    # subclassing the python native object is forbidden.  For example,
-    # `runtime.BoxBool` cannot be a subclass of `bool`, as `bool` does
-    # not allow any subclasses.
-    #
-    # The `hasattr` check is done on the object's class, not the
-    # object itself, to avoid edge cases that can result in invalid
-    # error messages.  If a C++ `LOG(FATAL) << nested_obj;` statement
-    # requires C++ to Python conversions in order to print
-    # `nested_obj`, then the `AttributeError` used internally by
-    # `hasattr` may overwrite the text being collected by
-    # `LOG(FATAL)`.  By checking for the method on the class instead
-    # of the instance, we avoid throwing the `AttributeError`.
-    # if hasattr(type(obj), "__into_pynative_object__"):
-    #     return obj.__into_pynative_object__()
-
-    return obj
-
-
-RETURN_SWITCH[ArgTypeCode.OBJECT_HANDLE] = _return_object
-C_TO_PY_ARG_SWITCH[ArgTypeCode.OBJECT_HANDLE] = _wrap_arg_func(
-    _return_object, ArgTypeCode.OBJECT_HANDLE
-)
-
-C_TO_PY_ARG_SWITCH[ArgTypeCode.OBJECT_RVALUE_REF_ARG] = _wrap_arg_func(
-    _return_object, ArgTypeCode.OBJECT_RVALUE_REF_ARG
-)
-
-
-class PyNativeObject:
-    """Base class of all TVM objects that also subclass python's builtin types."""
-
-    __slots__ = []
-
-    def __init_tvm_object_by_constructor__(self, fconstructor, *args):
-        """Initialize the internal tvm_object by calling constructor function.
-
-        Parameters
-        ----------
-        fconstructor : Function
-            Constructor function.
-
-        args: list of objects
-            The arguments to the constructor
-
-        Note
-        ----
-        We have a special calling convention to call constructor functions.
-        So the return object is directly set into the object
-        """
-        # pylint: disable=assigning-non-slot
-        obj = _CLASS_OBJECT.__new__(_CLASS_OBJECT)
-        obj.__init_handle_by_constructor__(fconstructor, *args)
-        self.__tvm_object__ = obj
-
-
-class ObjectBase(object):
-    """Base object for all object types"""
-
-    __slots__ = ["handle"]
-
-    def __del__(self):
-        if _LIB is not None:
-            try:
-                handle = self.handle
-            except AttributeError:
-                return
-
-            check_call(_LIB.TVMObjectFree(handle))
-
-    def __init_handle_by_constructor__(self, fconstructor, *args):
-        """Initialize the handle by calling constructor function.
-
-        Parameters
-        ----------
-        fconstructor : Function
-            Constructor function.
-
-        args: list of objects
-            The arguments to the constructor
-
-        Note
-        ----
-        We have a special calling convention to call constructor functions.
-        So the return handle is directly set into the Node object
-        instead of creating a new Node.
-        """
-        # assign handle first to avoid error raising
-        # pylint: disable=not-callable
-        self.handle = None
-        handle = __init_by_constructor__(fconstructor, args)
-        if not isinstance(handle, ObjectHandle):
-            handle = ObjectHandle(handle)
-        self.handle = handle
-
-    def same_as(self, other):
-        """Check object identity.
-
-        Parameters
-        ----------
-        other : object
-            The other object to compare against.
-
-        Returns
-        -------
-        result : bool
-             The comparison result.
-        """
-        if not isinstance(other, ObjectBase):
-            return False
-        if self.handle is None:
-            return other.handle is None
-        return self.handle.value == other.handle.value
diff --git a/python/tvm/_ffi/_ctypes/packed_func.py b/python/tvm/_ffi/_ctypes/packed_func.py
deleted file mode 100644
index 6dab1a5db1..0000000000
--- a/python/tvm/_ffi/_ctypes/packed_func.py
+++ /dev/null
@@ -1,354 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# coding: utf-8
-# pylint: disable=invalid-name, protected-access, too-many-branches
-# pylint: disable=global-statement, unused-import, using-constant-test
-"""Function configuration API."""
-import ctypes
-import traceback
-from numbers import Number, Integral
-
-from ..base import _LIB, get_last_ffi_error, py2cerror, check_call, raise_last_ffi_error
-from ..base import c_str, string_types
-from ..runtime_ctypes import DataType, TVMByteArray, Device, ObjectRValueRef
-from . import ndarray as _nd
-from .ndarray import NDArrayBase, _make_array
-from .types import TVMValue, ArgTypeCode
-from .types import TVMPackedCFunc, TVMCFuncFinalizer
-from .types import RETURN_SWITCH, C_TO_PY_ARG_SWITCH, _wrap_arg_func, _device_to_int64
-from .object import ObjectBase, PyNativeObject, _set_class_object
-from . import object as _object
-
-PackedFuncHandle = ctypes.c_void_p
-ModuleHandle = ctypes.c_void_p
-ObjectHandle = ctypes.c_void_p
-TVMRetValueHandle = ctypes.c_void_p
-
-
-def _ctypes_free_resource(rhandle):
-    """callback to free resources when it is not needed."""
-    pyobj = ctypes.cast(rhandle, ctypes.py_object)
-    ctypes.pythonapi.Py_DecRef(pyobj)
-
-
-# Global callback that is always alive
-TVM_FREE_PYOBJ = TVMCFuncFinalizer(_ctypes_free_resource)
-ctypes.pythonapi.Py_IncRef(ctypes.py_object(TVM_FREE_PYOBJ))
-
-
-def _make_packed_func(handle, is_global):
-    """Make a packed function class"""
-    obj = _CLASS_PACKED_FUNC.__new__(_CLASS_PACKED_FUNC)
-    obj.is_global = is_global
-    obj.handle = handle
-    return obj
-
-
-def convert_to_tvm_func(pyfunc):
-    """Convert a python function to TVM function
-
-    Parameters
-    ----------
-    pyfunc : python function
-        The python function to be converted.
-
-    Returns
-    -------
-    tvmfunc: tvm.nd.Function
-        The converted tvm function.
-    """
-    local_pyfunc = pyfunc
-
-    def cfun(args, type_codes, num_args, ret, _):
-        """ctypes function"""
-        num_args = num_args.value if isinstance(num_args, ctypes.c_int) else num_args
-        pyargs = (C_TO_PY_ARG_SWITCH[type_codes[i]](args[i]) for i in range(num_args))
-        # pylint: disable=broad-except
-        try:
-            rv = local_pyfunc(*pyargs)
-        except Exception as err:
-            msg = traceback.format_exc()
-            msg = py2cerror(msg)
-            _LIB.TVMAPISetLastPythonError(ctypes.py_object(err))
-
-            return -1
-
-        if rv is not None:
-            if isinstance(rv, tuple):
-                raise ValueError("PackedFunction can only support one return value")
-            temp_args = []
-            values, tcodes, _ = _make_tvm_args((rv,), temp_args)
-            if not isinstance(ret, TVMRetValueHandle):
-                ret = TVMRetValueHandle(ret)
-            if _LIB.TVMCFuncSetReturn(ret, values, tcodes, ctypes.c_int(1)) != 0:
-                raise_last_ffi_error()
-            _ = temp_args
-            _ = rv
-        return 0
-
-    handle = PackedFuncHandle()
-    f = TVMPackedCFunc(cfun)
-    # NOTE: We will need to use python-api to increase ref count of the f
-    # TVM_FREE_PYOBJ will be called after it is no longer needed.
-    pyobj = ctypes.py_object(f)
-    ctypes.pythonapi.Py_IncRef(pyobj)
-    if _LIB.TVMFuncCreateFromCFunc(f, pyobj, TVM_FREE_PYOBJ, ctypes.byref(handle)) != 0:
-        raise_last_ffi_error()
-    return _make_packed_func(handle, False)
-
-
-def _make_tvm_args(args, temp_args):
-    """Pack arguments into c args tvm call accept"""
-    num_args = len(args)
-    values = (TVMValue * num_args)()
-    type_codes = (ctypes.c_int * num_args)()
-    for i, arg in enumerate(args):
-        if isinstance(arg, ObjectBase):
-            values[i].v_handle = arg.handle
-            type_codes[i] = ArgTypeCode.OBJECT_HANDLE
-        elif arg is None:
-            values[i].v_handle = None
-            type_codes[i] = ArgTypeCode.NULL
-        elif isinstance(arg, NDArrayBase):
-            values[i].v_handle = ctypes.cast(arg.handle, ctypes.c_void_p)
-            type_codes[i] = (
-                ArgTypeCode.NDARRAY_HANDLE if not arg.is_view else ArgTypeCode.DLTENSOR_HANDLE
-            )
-        elif isinstance(arg, PyNativeObject):
-            values[i].v_handle = arg.__tvm_object__.handle
-            type_codes[i] = ArgTypeCode.OBJECT_HANDLE
-        elif isinstance(arg, _nd._TVM_COMPATS):
-            values[i].v_handle = ctypes.c_void_p(arg._tvm_handle)
-            type_codes[i] = arg.__class__._tvm_tcode
-        elif isinstance(arg, bool):
-            # A python `bool` is a subclass of `int`, so this check
-            # must occur before `Integral`.
-            values[i].v_bool = arg
-            type_codes[i] = ArgTypeCode.BOOL
-        elif isinstance(arg, Integral):
-            values[i].v_int64 = arg
-            type_codes[i] = ArgTypeCode.INT
-        elif isinstance(arg, Number):
-            values[i].v_float64 = arg
-            type_codes[i] = ArgTypeCode.FLOAT
-        elif isinstance(arg, DataType):
-            values[i].v_str = c_str(str(arg))
-            type_codes[i] = ArgTypeCode.STR
-        elif isinstance(arg, Device):
-            values[i].v_int64 = _device_to_int64(arg)
-            type_codes[i] = ArgTypeCode.DLDEVICE
-        elif isinstance(arg, (bytearray, bytes)):
-            # from_buffer only takes in bytearray.
-            if isinstance(arg, bytes):
-                byte_arr = bytearray(arg)
-                temp_args.append(byte_arr)
-                arg = byte_arr
-
-            arr = TVMByteArray()
-            arr.data = ctypes.cast(
-                (ctypes.c_byte * len(arg)).from_buffer(arg), ctypes.POINTER(ctypes.c_byte)
-            )
-            arr.size = len(arg)
-            values[i].v_handle = ctypes.c_void_p(ctypes.addressof(arr))
-            temp_args.append(arr)
-            type_codes[i] = ArgTypeCode.BYTES
-        elif isinstance(arg, string_types):
-            values[i].v_str = c_str(arg)
-            type_codes[i] = ArgTypeCode.STR
-        elif isinstance(arg, (list, tuple, dict, _CLASS_OBJECT_GENERIC)):
-            arg = _FUNC_CONVERT_TO_OBJECT(arg)
-            values[i].v_handle = arg.handle
-            type_codes[i] = ArgTypeCode.OBJECT_HANDLE
-            temp_args.append(arg)
-        elif isinstance(arg, _CLASS_MODULE):
-            values[i].v_handle = arg.handle
-            type_codes[i] = ArgTypeCode.MODULE_HANDLE
-        elif isinstance(arg, PackedFuncBase):
-            values[i].v_handle = arg.handle
-            type_codes[i] = ArgTypeCode.PACKED_FUNC_HANDLE
-        elif isinstance(arg, ctypes.c_void_p):
-            values[i].v_handle = arg
-            type_codes[i] = ArgTypeCode.HANDLE
-        elif isinstance(arg, ObjectRValueRef):
-            values[i].v_handle = ctypes.cast(ctypes.byref(arg.obj.handle), ctypes.c_void_p)
-            type_codes[i] = ArgTypeCode.OBJECT_RVALUE_REF_ARG
-        elif callable(arg):
-            arg = convert_to_tvm_func(arg)
-            values[i].v_handle = arg.handle
-            type_codes[i] = ArgTypeCode.PACKED_FUNC_HANDLE
-            temp_args.append(arg)
-        else:
-            raise TypeError("Don't know how to handle type %s" % type(arg))
-    return values, type_codes, num_args
-
-
-class PackedFuncBase(object):
-    """Function base."""
-
-    __slots__ = ["handle", "is_global"]
-
-    # pylint: disable=no-member
-    def __init__(self, handle, is_global):
-        """Initialize the function with handle
-
-        Parameters
-        ----------
-        handle : PackedFuncHandle
-            the handle to the underlying function.
-
-        is_global : bool
-            Whether this is a global function in python
-        """
-        self.handle = handle
-        self.is_global = is_global
-
-    def __del__(self):
-        if not self.is_global and _LIB is not None:
-            if _LIB.TVMFuncFree(self.handle) != 0:
-                raise_last_ffi_error()
-
-    def __call__(self, *args):
-        """Call the function with positional arguments
-
-        args : list
-           The positional arguments to the function call.
-        """
-        temp_args = []
-        values, tcodes, num_args = _make_tvm_args(args, temp_args)
-        ret_val = TVMValue()
-        ret_tcode = ctypes.c_int()
-        if (
-            _LIB.TVMFuncCall(
-                self.handle,
-                values,
-                tcodes,
-                ctypes.c_int(num_args),
-                ctypes.byref(ret_val),
-                ctypes.byref(ret_tcode),
-            )
-            != 0
-        ):
-            raise_last_ffi_error()
-        _ = temp_args
-        _ = args
-        return RETURN_SWITCH[ret_tcode.value](ret_val)
-
-
-def __init_handle_by_constructor__(fconstructor, args):
-    """Initialize handle by constructor"""
-    temp_args = []
-    values, tcodes, num_args = _make_tvm_args(args, temp_args)
-    ret_val = TVMValue()
-    ret_tcode = ctypes.c_int()
-    if (
-        _LIB.TVMFuncCall(
-            fconstructor.handle,
-            values,
-            tcodes,
-            ctypes.c_int(num_args),
-            ctypes.byref(ret_val),
-            ctypes.byref(ret_tcode),
-        )
-        != 0
-    ):
-        raise_last_ffi_error()
-    _ = temp_args
-    _ = args
-    assert ret_tcode.value == ArgTypeCode.OBJECT_HANDLE
-    handle = ret_val.v_handle
-    return handle
-
-
-def _return_module(x):
-    """Return function"""
-    handle = x.v_handle
-    if not isinstance(handle, ModuleHandle):
-        handle = ModuleHandle(handle)
-    return _CLASS_MODULE(handle)
-
-
-def _handle_return_func(x):
-    """Return function"""
-    handle = x.v_handle
-    if not isinstance(handle, PackedFuncHandle):
-        handle = PackedFuncHandle(handle)
-    return _CLASS_PACKED_FUNC(handle, False)
-
-
-def _get_global_func(name, allow_missing=False):
-    handle = PackedFuncHandle()
-    check_call(_LIB.TVMFuncGetGlobal(c_str(name), ctypes.byref(handle)))
-
-    if handle.value:
-        return _make_packed_func(handle, False)
-
-    if allow_missing:
-        return None
-
-    raise ValueError("Cannot find global function %s" % name)
-
-
-# setup return handle for function type
-_object.__init_by_constructor__ = __init_handle_by_constructor__
-RETURN_SWITCH[ArgTypeCode.PACKED_FUNC_HANDLE] = _handle_return_func
-RETURN_SWITCH[ArgTypeCode.MODULE_HANDLE] = _return_module
-RETURN_SWITCH[ArgTypeCode.NDARRAY_HANDLE] = lambda x: _make_array(x.v_handle, False, True)
-C_TO_PY_ARG_SWITCH[ArgTypeCode.PACKED_FUNC_HANDLE] = _wrap_arg_func(
-    _handle_return_func, ArgTypeCode.PACKED_FUNC_HANDLE
-)
-C_TO_PY_ARG_SWITCH[ArgTypeCode.MODULE_HANDLE] = _wrap_arg_func(
-    _return_module, ArgTypeCode.MODULE_HANDLE
-)
-C_TO_PY_ARG_SWITCH[ArgTypeCode.DLTENSOR_HANDLE] = lambda x: _make_array(x.v_handle, True, False)
-C_TO_PY_ARG_SWITCH[ArgTypeCode.NDARRAY_HANDLE] = _wrap_arg_func(
-    lambda x: _make_array(x.v_handle, False, True), ArgTypeCode.NDARRAY_HANDLE
-)
-
-_CLASS_MODULE = None
-_CLASS_PACKED_FUNC = None
-_CLASS_OBJECT_GENERIC = None
-_FUNC_CONVERT_TO_OBJECT = None
-
-
-def _set_class_module(module_class):
-    """Initialize the module."""
-    global _CLASS_MODULE
-    _CLASS_MODULE = module_class
-
-
-def _set_class_packed_func(packed_func_class):
-    global _CLASS_PACKED_FUNC
-    _CLASS_PACKED_FUNC = packed_func_class
-
-
-def _set_class_object_generic(object_generic_class, func_convert_to_object):
-    global _CLASS_OBJECT_GENERIC
-    global _FUNC_CONVERT_TO_OBJECT
-    _CLASS_OBJECT_GENERIC = object_generic_class
-    _FUNC_CONVERT_TO_OBJECT = func_convert_to_object
-
-
-def _init_pythonapi_inc_def_ref():
-    register_func = _LIB.TVMBackendRegisterEnvCAPI
-    register_func(c_str("Py_IncRef"), ctypes.pythonapi.Py_IncRef)
-    register_func(c_str("Py_DecRef"), ctypes.pythonapi.Py_DecRef)
-    register_func(c_str("PyGILState_Ensure"), ctypes.pythonapi.PyGILState_Ensure)
-    register_func(c_str("PyGILState_Release"), ctypes.pythonapi.PyGILState_Release)
-    register_func(c_str("PyErr_CheckSignals"), ctypes.pythonapi.PyErr_CheckSignals)
-
-
-_init_pythonapi_inc_def_ref()
diff --git a/python/tvm/_ffi/_ctypes/types.py b/python/tvm/_ffi/_ctypes/types.py
deleted file mode 100644
index 45f36eafd7..0000000000
--- a/python/tvm/_ffi/_ctypes/types.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""The C Types used in API."""
-# pylint: disable=invalid-name
-import ctypes
-import struct
-from ..base import py_str, check_call, _LIB
-from ..runtime_ctypes import TVMByteArray, ArgTypeCode, Device
-
-
-class TVMValue(ctypes.Union):
-    """TVMValue in C API"""
-
-    _fields_ = [
-        ("v_int64", ctypes.c_int64),
-        ("v_bool", ctypes.c_bool),
-        ("v_float64", ctypes.c_double),
-        ("v_handle", ctypes.c_void_p),
-        ("v_str", ctypes.c_char_p),
-    ]
-
-
-TVMPackedCFunc = ctypes.CFUNCTYPE(
-    ctypes.c_int,
-    ctypes.POINTER(TVMValue),
-    ctypes.POINTER(ctypes.c_int),
-    ctypes.c_int,
-    ctypes.c_void_p,
-    ctypes.c_void_p,
-)
-
-
-TVMCFuncFinalizer = ctypes.CFUNCTYPE(None, ctypes.c_void_p)
-
-
-def _return_handle(x):
-    """return handle"""
-    handle = x.v_handle
-    if not isinstance(handle, ctypes.c_void_p):
-        handle = ctypes.c_void_p(handle)
-    return handle
-
-
-def _return_bytes(x):
-    """return bytes"""
-    handle = x.v_handle
-    if not isinstance(handle, ctypes.c_void_p):
-        handle = ctypes.c_void_p(handle)
-    arr = ctypes.cast(handle, ctypes.POINTER(TVMByteArray))[0]
-    size = arr.size
-    res = bytearray(size)
-    rptr = (ctypes.c_byte * size).from_buffer(res)
-    if not ctypes.memmove(rptr, arr.data, size):
-        raise RuntimeError("memmove failed")
-    return res
-
-
-def _return_device(value):
-    """return Device"""
-    # use bit unpacking from int64 view
-    # We use this to get around ctypes issue on Union of Structure
-    data = struct.pack("=q", value.v_int64)
-    arr = struct.unpack("=ii", data)
-    return Device(arr[0], arr[1])
-
-
-def _wrap_arg_func(return_f, type_code):
-    def _wrap_func(x):
-        tcode = ctypes.c_int(type_code)
-        check_call(_LIB.TVMCbArgToReturn(ctypes.byref(x), ctypes.byref(tcode)))
-        return return_f(x)
-
-    return _wrap_func
-
-
-def _device_to_int64(dev):
-    """Pack context into int64 in native endian"""
-    data = struct.pack("=ii", dev.device_type, dev.device_id)
-    return struct.unpack("=q", data)[0]
-
-
-RETURN_SWITCH = {
-    ArgTypeCode.INT: lambda x: x.v_int64,
-    ArgTypeCode.BOOL: lambda x: x.v_bool,
-    ArgTypeCode.FLOAT: lambda x: x.v_float64,
-    ArgTypeCode.HANDLE: _return_handle,
-    ArgTypeCode.NULL: lambda x: None,
-    ArgTypeCode.STR: lambda x: py_str(x.v_str),
-    ArgTypeCode.BYTES: _return_bytes,
-    ArgTypeCode.DLDEVICE: _return_device,
-}
-
-C_TO_PY_ARG_SWITCH = {
-    ArgTypeCode.INT: lambda x: x.v_int64,
-    ArgTypeCode.BOOL: lambda x: x.v_bool,
-    ArgTypeCode.FLOAT: lambda x: x.v_float64,
-    ArgTypeCode.HANDLE: _return_handle,
-    ArgTypeCode.NULL: lambda x: None,
-    ArgTypeCode.STR: lambda x: py_str(x.v_str),
-    ArgTypeCode.BYTES: _return_bytes,
-    ArgTypeCode.DLDEVICE: _return_device,
-}
diff --git a/python/tvm/_ffi/_cy2/__init__.py b/python/tvm/_ffi/_cy2/__init__.py
deleted file mode 100644
index 4d0a864416..0000000000
--- a/python/tvm/_ffi/_cy2/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""cython2 namespace"""
diff --git a/python/tvm/_ffi/base.py b/python/tvm/_ffi/base.py
index 263ab0bdb3..f2add9d1e4 100644
--- a/python/tvm/_ffi/base.py
+++ b/python/tvm/_ffi/base.py
@@ -83,6 +83,9 @@ _RUNTIME_ONLY = "runtime" in _LIB_NAME
 # The FFI mode of TVM
 _FFI_MODE = os.environ.get("TVM_FFI", "auto")
 
+if _FFI_MODE == "ctypes":
+    raise ImportError("We have phased out ctypes support in favor of cython on wards")
+
 
 # ----------------------------
 # helper function in ctypes.
diff --git a/python/tvm/_ffi/registry.py b/python/tvm/_ffi/registry.py
index 1b6b1dec9a..cbc8dbaebf 100644
--- a/python/tvm/_ffi/registry.py
+++ b/python/tvm/_ffi/registry.py
@@ -15,27 +15,16 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# pylint: disable=invalid-name, unused-import
+# pylint: disable=invalid-name, unused-import, wrong-import-position
 """FFI registry to register function and objects."""
 import sys
 import ctypes
 
-from .base import _LIB, check_call, py_str, c_str, string_types, _FFI_MODE, _RUNTIME_ONLY
-
-try:
-    # pylint: disable=wrong-import-position,unused-import
-    if _FFI_MODE == "ctypes":
-        raise ImportError()
-    from ._cy3.core import _register_object, _get_object_type_index
-    from ._cy3.core import _reg_extension
-    from ._cy3.core import convert_to_tvm_func, _get_global_func, PackedFuncBase
-except (RuntimeError, ImportError) as error:
-    # pylint: disable=wrong-import-position,unused-import
-    if _FFI_MODE == "cython":
-        raise error
-    from ._ctypes.object import _register_object, _get_object_type_index
-    from ._ctypes.ndarray import _reg_extension
-    from ._ctypes.packed_func import convert_to_tvm_func, _get_global_func, PackedFuncBase
+from .base import _LIB, check_call, py_str, c_str, string_types, _RUNTIME_ONLY
+
+from ._cy3.core import _register_object, _get_object_type_index
+from ._cy3.core import _reg_extension
+from ._cy3.core import convert_to_tvm_func, _get_global_func, PackedFuncBase
 
 
 def register_object(type_key=None):
diff --git a/python/tvm/runtime/ndarray.py b/python/tvm/runtime/ndarray.py
index d55334a154..349b7d2d54 100644
--- a/python/tvm/runtime/ndarray.py
+++ b/python/tvm/runtime/ndarray.py
@@ -27,7 +27,7 @@ try:
 except ImportError:
     ml_dtypes = None
 import tvm._ffi
-from tvm._ffi.base import _FFI_MODE, _LIB, c_array, check_call, string_types
+from tvm._ffi.base import _LIB, c_array, check_call, string_types
 from tvm._ffi.runtime_ctypes import (
     DataType,
     DataTypeCode,
@@ -36,30 +36,15 @@ from tvm._ffi.runtime_ctypes import (
     TVMArrayHandle,
     tvm_shape_index_t,
 )
+from tvm._ffi._cy3.core import (
+    NDArrayBase,
+    _from_dlpack,
+    _make_array,
+    _set_class_ndarray,
+)
 
 from . import _ffi_api
 
-try:
-    # pylint: disable=wrong-import-position
-    if _FFI_MODE == "ctypes":
-        raise ImportError()
-    from tvm._ffi._cy3.core import (
-        NDArrayBase,
-        _from_dlpack,
-        _make_array,
-        _set_class_ndarray,
-    )
-except (RuntimeError, ImportError) as error:
-    # pylint: disable=wrong-import-position
-    if _FFI_MODE == "cython":
-        raise error
-    from tvm._ffi._ctypes.ndarray import (
-        NDArrayBase,
-        _from_dlpack,
-        _make_array,
-        _set_class_ndarray,
-    )
-
 
 @tvm._ffi.register_object("runtime.NDArray")
 class NDArray(NDArrayBase):
diff --git a/python/tvm/runtime/object.py b/python/tvm/runtime/object.py
index b57276ebd2..4892a54f75 100644
--- a/python/tvm/runtime/object.py
+++ b/python/tvm/runtime/object.py
@@ -18,28 +18,17 @@
 """Runtime Object API"""
 import ctypes
 
-from tvm._ffi.base import _FFI_MODE, _LIB, _RUNTIME_ONLY, c_str, check_call
+from tvm._ffi.base import _LIB, _RUNTIME_ONLY, c_str, check_call
 from tvm._ffi.runtime_ctypes import ObjectRValueRef
+from tvm._ffi._cy3.core import (
+    ObjectBase,
+    PyNativeObject,
+    _set_class_object,
+    _set_class_object_generic,
+)
 
 from . import _ffi_api, _ffi_node_api
 
-try:
-    # pylint: disable=wrong-import-position,unused-import
-    if _FFI_MODE == "ctypes":
-        raise ImportError()
-    from tvm._ffi._cy3.core import (
-        ObjectBase,
-        PyNativeObject,
-        _set_class_object,
-        _set_class_object_generic,
-    )
-except (RuntimeError, ImportError) as error:
-    # pylint: disable=wrong-import-position,unused-import
-    if _FFI_MODE == "cython":
-        raise error
-    from tvm._ffi._ctypes.object import ObjectBase, PyNativeObject
-    from tvm._ffi._ctypes.packed_func import _set_class_object, _set_class_object_generic
-
 
 def _new_object(cls):
     """Helper function for pickle"""
diff --git a/python/tvm/runtime/packed_func.py b/python/tvm/runtime/packed_func.py
index bcd3cd733d..7f48114878 100644
--- a/python/tvm/runtime/packed_func.py
+++ b/python/tvm/runtime/packed_func.py
@@ -18,22 +18,10 @@
 # pylint: disable=invalid-name, unused-import
 """Packed Function namespace."""
 import ctypes
-from tvm._ffi.base import _LIB, check_call, c_str, string_types, _FFI_MODE
-
-try:
-    # pylint: disable=wrong-import-position
-    if _FFI_MODE == "ctypes":
-        raise ImportError()
-    from tvm._ffi._cy3.core import _set_class_packed_func, _set_class_module
-    from tvm._ffi._cy3.core import PackedFuncBase
-    from tvm._ffi._cy3.core import convert_to_tvm_func
-except (RuntimeError, ImportError) as error:
-    # pylint: disable=wrong-import-position
-    if _FFI_MODE == "cython":
-        raise error
-    from tvm._ffi._ctypes.packed_func import _set_class_packed_func, _set_class_module
-    from tvm._ffi._ctypes.packed_func import PackedFuncBase
-    from tvm._ffi._ctypes.packed_func import convert_to_tvm_func
+from tvm._ffi.base import _LIB, check_call, c_str, string_types
+from tvm._ffi._cy3.core import _set_class_packed_func, _set_class_module
+from tvm._ffi._cy3.core import PackedFuncBase
+from tvm._ffi._cy3.core import convert_to_tvm_func
 
 
 PackedFuncHandle = ctypes.c_void_p
diff --git a/tests/lint/pylintrc b/tests/lint/pylintrc
index 90900b9e00..81e6a9c688 100644
--- a/tests/lint/pylintrc
+++ b/tests/lint/pylintrc
@@ -122,7 +122,8 @@ disable=
     useless-object-inheritance,
     useless-suppression,
     use-list-literal,
-    arguments-renamed
+    arguments-renamed,
+    super-init-not-called
 
 [REPORTS]
 
diff --git a/tests/python/runtime/test_runtime_module_load.py b/tests/python/runtime/test_runtime_module_load.py
index 130a274c35..71f6a79387 100644
--- a/tests/python/runtime/test_runtime_module_load.py
+++ b/tests/python/runtime/test_runtime_module_load.py
@@ -28,7 +28,6 @@ import os
 import sys
 
 os.environ["TVM_USE_RUNTIME_LIB"] = "1"
-os.environ["TVM_FFI"] = "ctypes"
 import tvm
 from tvm import te
 import numpy as np
diff --git a/tests/python/tvmscript/test_tvmscript_roundtrip.py b/tests/python/tvmscript/test_tvmscript_roundtrip.py
index f29c03c640..140136f741 100644
--- a/tests/python/tvmscript/test_tvmscript_roundtrip.py
+++ b/tests/python/tvmscript/test_tvmscript_roundtrip.py
@@ -3895,6 +3895,7 @@ def undefined_data_ptr_in_decl_buffer():
     Allocate/DeclBuffer pair, performing a round-trip through
     TVMScript should not introduce an Allocate node.
     """
+
     # uninitialized var
     @T.prim_func(check_well_formed=False)
     def func():