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

syfeng 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 6bc94d0050 [FFI][REFACTOR] Establish ffi.Module in python (#18214)
6bc94d0050 is described below

commit 6bc94d00502512fd42ed4590f76878c6d79e603a
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Aug 18 10:37:25 2025 -0400

    [FFI][REFACTOR] Establish ffi.Module in python (#18214)
    
    This PR refactors and establishes ffi.Module under the python tvm ffi api.
    Also moves export_library method to executable so it aligns more with
    compiled artifact.
---
 python/tvm/ffi/__init__.py                         |   5 +
 python/tvm/ffi/module.py                           | 258 +++++++++++++
 python/tvm/relax/vm_build.py                       |   6 +-
 python/tvm/runtime/executable.py                   |  46 ++-
 python/tvm/runtime/module.py                       | 426 +++++----------------
 tests/python/runtime/test_runtime_module_export.py |   2 +-
 6 files changed, 401 insertions(+), 342 deletions(-)

diff --git a/python/tvm/ffi/__init__.py b/python/tvm/ffi/__init__.py
index e615e22a0c..801a8d2989 100644
--- a/python/tvm/ffi/__init__.py
+++ b/python/tvm/ffi/__init__.py
@@ -30,6 +30,7 @@ from .ndarray import Device, device
 from .ndarray import cpu, cuda, rocm, opencl, metal, vpi, vulkan, ext_dev, 
hexagon, webgpu
 from .ndarray import from_dlpack, NDArray, Shape
 from .container import Array, Map
+from .module import Module, ModulePropertyMask, system_lib, load_module
 from . import serialization
 from . import access_path
 from . import testing
@@ -71,4 +72,8 @@ __all__ = [
     "testing",
     "access_path",
     "serialization",
+    "Module",
+    "ModulePropertyMask",
+    "system_lib",
+    "load_module",
 ]
diff --git a/python/tvm/ffi/module.py b/python/tvm/ffi/module.py
new file mode 100644
index 0000000000..0895b317c1
--- /dev/null
+++ b/python/tvm/ffi/module.py
@@ -0,0 +1,258 @@
+# 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.
+"""Module related objects and functions."""
+# pylint: disable=invalid-name
+
+from enum import IntEnum
+from . import _ffi_api
+
+from . import core
+from .registry import register_object
+
+__all__ = ["Module", "ModulePropertyMask", "system_lib", "load_module"]
+
+
+class ModulePropertyMask(IntEnum):
+    """Runtime Module Property Mask."""
+
+    BINARY_SERIALIZABLE = 0b001
+    RUNNABLE = 0b010
+    COMPILATION_EXPORTABLE = 0b100
+
+
+@register_object("ffi.Module")
+class Module(core.Object):
+    """Runtime Module."""
+
+    def __new__(cls):
+        instance = super(Module, cls).__new__(cls)  # pylint: 
disable=no-value-for-parameter
+        instance.entry_name = "__tvm_ffi_main__"
+        instance._entry = None
+        return instance
+
+    @property
+    def entry_func(self):
+        """Get the entry function
+
+        Returns
+        -------
+        f : tvm.ffi.Function
+            The entry function if exist
+        """
+        if self._entry:
+            return self._entry
+        self._entry = self.get_function("__tvm_ffi_main__")
+        return self._entry
+
+    @property
+    def kind(self):
+        """Get type key of the module."""
+        return _ffi_api.ModuleGetKind(self)
+
+    @property
+    def imports(self):
+        """Get imported modules
+
+        Returns
+        ----------
+        modules : list of Module
+            The module
+        """
+        return self.imports_
+
+    def implements_function(self, name, query_imports=False):
+        """Returns True if the module has a definition for the global function 
with name. Note
+        that has_function(name) does not imply get_function(name) is non-null 
since the module
+        may be, eg, a CSourceModule which cannot supply a packed-func 
implementation of the function
+        without further compilation. However, get_function(name) non null 
should always imply
+        has_function(name).
+
+        Parameters
+        ----------
+        name : str
+            The name of the function
+
+        query_imports : bool
+            Whether to also query modules imported by this module.
+
+        Returns
+        -------
+        b : Bool
+            True if module (or one of its imports) has a definition for name.
+        """
+        return _ffi_api.ModuleImplementsFunction(self, name, query_imports)
+
+    def get_function(self, name, query_imports=False):
+        """Get function from the module.
+
+        Parameters
+        ----------
+        name : str
+            The name of the function
+
+        query_imports : bool
+            Whether also query modules imported by this module.
+
+        Returns
+        -------
+        f : tvm.ffi.Function
+            The result function.
+        """
+        func = _ffi_api.ModuleGetFunction(self, name, query_imports)
+        if func is None:
+            raise AttributeError(f"Module has no function '{name}'")
+        return func
+
+    def import_module(self, module):
+        """Add module to the import list of current one.
+
+        Parameters
+        ----------
+        module : tvm.runtime.Module
+            The other module.
+        """
+        _ffi_api.ModuleImportModule(self, module)
+
+    def __getitem__(self, name):
+        if not isinstance(name, str):
+            raise ValueError("Can only take string as function name")
+        return self.get_function(name)
+
+    def __call__(self, *args):
+        if self._entry:
+            return self._entry(*args)
+        # pylint: disable=not-callable
+        return self.entry_func(*args)
+
+    def inspect_source(self, fmt=""):
+        """Get source code from module, if available.
+
+        Parameters
+        ----------
+        fmt : str, optional
+            The specified format.
+
+        Returns
+        -------
+        source : str
+            The result source code.
+        """
+        return _ffi_api.ModuleInspectSource(self, fmt)
+
+    def get_write_formats(self):
+        """Get the format of the module."""
+        return _ffi_api.ModuleGetWriteFormats(self)
+
+    def get_property_mask(self):
+        """Get the runtime module property mask. The mapping is stated in 
ModulePropertyMask.
+
+        Returns
+        -------
+        mask : int
+            Bitmask of runtime module property
+        """
+        return _ffi_api.ModuleGetPropertyMask(self)
+
+    def is_binary_serializable(self):
+        """Module 'binary serializable', save_to_bytes is supported.
+
+        Returns
+        -------
+        b : Bool
+            True if the module is binary serializable.
+        """
+        return (self.get_property_mask() & 
ModulePropertyMask.BINARY_SERIALIZABLE) != 0
+
+    def is_runnable(self):
+        """Module 'runnable', get_function is supported.
+
+        Returns
+        -------
+        b : Bool
+            True if the module is runnable.
+        """
+        return (self.get_property_mask() & ModulePropertyMask.RUNNABLE) != 0
+
+    def is_compilation_exportable(self):
+        """Module 'compilation exportable', write_to_file is supported for 
object or source.
+
+        Returns
+        -------
+        b : Bool
+            True if the module is compilation exportable.
+        """
+        return (self.get_property_mask() & 
ModulePropertyMask.COMPILATION_EXPORTABLE) != 0
+
+    def clear_imports(self):
+        """Remove all imports of the module."""
+        _ffi_api.ModuleClearImports(self)
+
+    def write_to_file(self, file_name, fmt=""):
+        """Write the current module to file.
+
+        Parameters
+        ----------
+        file_name : str
+            The name of the file.
+        fmt : str
+            The format of the file.
+
+        See Also
+        --------
+        runtime.Module.export_library : export the module to shared library.
+        """
+        _ffi_api.ModuleWriteToFile(self, file_name, fmt)
+
+
+def system_lib(symbol_prefix=""):
+    """Get system-wide library module singleton.
+
+    System lib is a global module that contains self register functions in 
startup.
+    Unlike normal dso modules which need to be loaded explicitly.
+    It is useful in environments where dynamic loading api like dlopen is 
banned.
+
+    The system lib is intended to be linked and loaded during the entire 
life-cyle of the program.
+    If you want dynamic loading features, use dso modules instead.
+
+    Parameters
+    ----------
+    symbol_prefix: Optional[str]
+        Optional symbol prefix that can be used for search. When we lookup a 
symbol
+        symbol_prefix + name will first be searched, then the name without 
symbol_prefix.
+
+    Returns
+    -------
+    module : runtime.Module
+        The system-wide library module.
+    """
+    return _ffi_api.SystemLib(symbol_prefix)
+
+
+def load_module(path):
+    """Load module from file.
+
+    Parameters
+    ----------
+    path : str
+        The path to the module file.
+
+    Returns
+    -------
+    module : ffi.Module
+        The loaded module
+    """
+    return _ffi_api.ModuleLoadFromFile(path)
diff --git a/python/tvm/relax/vm_build.py b/python/tvm/relax/vm_build.py
index f6db61af61..cf8cd86330 100644
--- a/python/tvm/relax/vm_build.py
+++ b/python/tvm/relax/vm_build.py
@@ -99,6 +99,10 @@ def _auto_attach_system_lib_prefix(
     return tir_mod
 
 
+def _is_device_module(mod: tvm.runtime.Module) -> bool:
+    return mod.kind in ["cuda", "opencl", "metal", "hip", "vulkan", "webgpu"]
+
+
 def _vmlink(
     builder: "relax.ExecBuilder",
     target: Optional[Union[str, tvm.target.Target]],
@@ -153,7 +157,7 @@ def _vmlink(
         tir_mod = _auto_attach_system_lib_prefix(tir_mod, target, system_lib)
         lib = tvm.tir.build(tir_mod, target=target, pipeline=tir_pipeline)
     for ext_mod in ext_libs:
-        if ext_mod.is_device_module():
+        if _is_device_module(ext_mod):
             tir_ext_libs.append(ext_mod)
         else:
             relax_ext_libs.append(ext_mod)
diff --git a/python/tvm/runtime/executable.py b/python/tvm/runtime/executable.py
index 51f0a772e4..47c46959be 100644
--- a/python/tvm/runtime/executable.py
+++ b/python/tvm/runtime/executable.py
@@ -17,9 +17,10 @@
 # pylint: disable=invalid-name, no-member
 
 """Executable object for TVM Runtime"""
-from typing import Any, Callable, Dict, List, Optional, Union
+from typing import Any, Callable, Dict, List, Optional
 
 import tvm
+
 from tvm.contrib import utils as _utils
 from . import PackedFunc, Module
 
@@ -105,20 +106,27 @@ class Executable:
         # by collecting the link and allow export_library skip those modules.
         workspace_dir = _utils.tempdir()
         dso_path = workspace_dir.relpath("exported.so")
-        self.mod.export_library(dso_path, fcompile=fcompile, addons=addons, 
**kwargs)
+        self.export_library(dso_path, fcompile=fcompile, addons=addons, 
**kwargs)
         self._jitted_mod = tvm.runtime.load_module(dso_path)
         return self._jitted_mod
 
     def export_library(
         self,
-        file_name: str,
+        file_name,
         *,
-        fcompile: Optional[Union[str, Callable[[str, List[str], Dict[str, 
Any]], None]]] = None,
-        addons: Optional[List[str]] = None,
-        workspace_dir: Optional[str] = None,
+        fcompile=None,
+        addons=None,
+        workspace_dir=None,
         **kwargs,
-    ) -> Any:
-        """Export the executable to a library which can then be loaded back.
+    ):
+        """
+        Export the module and all imported modules into a single device 
library.
+
+        This function only works on host LLVM modules, other runtime::Module
+        subclasses will work with this API but they must support implement
+        the save and load mechanisms of modules completely including saving
+        from streams and files. This will pack your non-shared library module
+        into a single shared library which can later be loaded by TVM.
 
         Parameters
         ----------
@@ -127,6 +135,15 @@ class Executable:
 
         fcompile : function(target, file_list, kwargs), optional
             The compilation function to use create the final library object 
during
+            export.
+
+            For example, when fcompile=_cc.create_shared, or when it is not 
supplied but
+            module is "llvm," this is used to link all produced artifacts
+            into a final dynamic library.
+
+            This behavior is controlled by the type of object exported.
+            If fcompile has attribute object_format, will compile host library
+            to that format. Otherwise, will use default format "o".
 
         addons : list of str, optional
             Additional object files to link against.
@@ -144,20 +161,9 @@ class Executable:
         result of fcompile()  : unknown, optional
             If the compilation function returns an artifact it would be 
returned via
             export_library, if any.
-
-        Examples
-        --------
-        .. code:: python
-
-            ex = tvm.compile(mod, target)
-            # export the library
-            ex.export_library("exported.so")
-
-            # load it back for future uses.
-            rt_mod = tvm.runtime.load_module("exported.so")
         """
         return self.mod.export_library(
-            file_name=file_name,
+            file_name,
             fcompile=fcompile,
             addons=addons,
             workspace_dir=workspace_dir,
diff --git a/python/tvm/runtime/module.py b/python/tvm/runtime/module.py
index 3925c24365..9cbc06708b 100644
--- a/python/tvm/runtime/module.py
+++ b/python/tvm/runtime/module.py
@@ -18,17 +18,20 @@
 # pylint: disable=invalid-name, unused-import, import-outside-toplevel, 
inconsistent-return-statements
 """Runtime Module namespace."""
 import os
-import ctypes
 import struct
 from typing import Sequence
-import numpy as np
 
-import tvm.ffi
+import numpy as np
 from tvm.base import _RUNTIME_ONLY
 from tvm.libinfo import find_include_path
 
 from . import _ffi_api
-from ..ffi import _ffi_api as _mod_ffi_api
+from ..ffi import (
+    Module as _Module,
+    load_module as _load_module,
+    register_object as _register_object,
+    system_lib,
+)
 
 
 class BenchmarkResult:
@@ -90,301 +93,11 @@ class BenchmarkResult:
         )
 
 
-class ModulePropertyMask(object):
-    """Runtime Module Property Mask."""
-
-    BINARY_SERIALIZABLE = 0b001
-    RUNNABLE = 0b010
-    COMPILATION_EXPORTABLE = 0b100
-
-
[email protected]_object("ffi.Module")
-class Module(tvm.ffi.Object):
+# override the Module class in ffi.Module
+@_register_object("ffi.Module")
+class Module(_Module):
     """Runtime Module."""
 
-    def __new__(cls):
-        instance = super(Module, cls).__new__(cls)  # pylint: 
disable=no-value-for-parameter
-        instance.entry_name = "__tvm_ffi_main__"
-        instance._entry = None
-        return instance
-
-    @property
-    def entry_func(self):
-        """Get the entry function
-
-        Returns
-        -------
-        f : tvm.runtime.PackedFunc
-            The entry function if exist
-        """
-        if self._entry:
-            return self._entry
-        self._entry = self.get_function("__tvm_ffi_main__")
-        return self._entry
-
-    @property
-    def kind(self):
-        """Get type key of the module."""
-        return _mod_ffi_api.ModuleGetKind(self)
-
-    @property
-    def imports(self):
-        """Get imported modules
-
-        Returns
-        ----------
-        modules : list of Module
-            The module
-        """
-        return self.imports_
-
-    def implements_function(self, name, query_imports=False):
-        """Returns True if the module has a definition for the global function 
with name. Note
-        that has_function(name) does not imply get_function(name) is non-null 
since the module
-        may be, eg, a CSourceModule which cannot supply a packed-func 
implementation of the function
-        without further compilation. However, get_function(name) non null 
should always imply
-        has_function(name).
-
-        Parameters
-        ----------
-        name : str
-            The name of the function
-
-        query_imports : bool
-            Whether to also query modules imported by this module.
-
-        Returns
-        -------
-        b : Bool
-            True if module (or one of its imports) has a definition for name.
-        """
-        return _mod_ffi_api.ModuleImplementsFunction(self, name, query_imports)
-
-    def get_function(self, name, query_imports=False):
-        """Get function from the module.
-
-        Parameters
-        ----------
-        name : str
-            The name of the function
-
-        query_imports : bool
-            Whether also query modules imported by this module.
-
-        Returns
-        -------
-        f : tvm.runtime.PackedFunc
-            The result function.
-        """
-        func = _mod_ffi_api.ModuleGetFunction(self, name, query_imports)
-        if func is None:
-            raise AttributeError(f"Module has no function '{name}'")
-        return func
-
-    def import_module(self, module):
-        """Add module to the import list of current one.
-
-        Parameters
-        ----------
-        module : tvm.runtime.Module
-            The other module.
-        """
-        _mod_ffi_api.ModuleImportModule(self, module)
-
-    def __getitem__(self, name):
-        if not isinstance(name, str):
-            raise ValueError("Can only take string as function name")
-        return self.get_function(name)
-
-    def __call__(self, *args):
-        if self._entry:
-            return self._entry(*args)
-        # pylint: disable=not-callable
-        return self.entry_func(*args)
-
-    def inspect_source(self, fmt=""):
-        """Get source code from module, if available.
-
-        Parameters
-        ----------
-        fmt : str, optional
-            The specified format.
-
-        Returns
-        -------
-        source : str
-            The result source code.
-        """
-        return _mod_ffi_api.ModuleInspectSource(self, fmt)
-
-    def get_write_formats(self):
-        """Get the format of the module."""
-        return _mod_ffi_api.ModuleGetWriteFormats(self)
-
-    def get_property_mask(self):
-        """Get the runtime module property mask. The mapping is stated in 
ModulePropertyMask.
-
-        Returns
-        -------
-        mask : int
-            Bitmask of runtime module property
-        """
-        return _mod_ffi_api.ModuleGetPropertyMask(self)
-
-    def is_binary_serializable(self):
-        """Returns true if module is 'binary serializable', ie can be 
serialzed into binary
-         stream and loaded back to the runtime module.
-
-        Returns
-        -------
-        b : Bool
-            True if the module is binary serializable.
-        """
-        return (self.get_property_mask() & 
ModulePropertyMask.BINARY_SERIALIZABLE) != 0
-
-    def is_runnable(self):
-        """Returns true if module is 'runnable'. ie can be executed without 
any extra
-        compilation/linking steps.
-
-        Returns
-        -------
-        b : Bool
-            True if the module is runnable.
-        """
-        return (self.get_property_mask() & ModulePropertyMask.RUNNABLE) != 0
-
-    def is_device_module(self):
-        return self.kind in ["cuda", "opencl", "metal", "hip", "vulkan", 
"webgpu"]
-
-    def is_compilation_exportable(self):
-        """Returns true if module is 'compilation exportable', ie can be 
included in result of
-        export_library by the external compiler directly.
-
-        Returns
-        -------
-        b : Bool
-            True if the module is compilation exportable.
-        """
-        return (self.get_property_mask() & 
ModulePropertyMask.COMPILATION_EXPORTABLE) != 0
-
-    def clear_imports(self):
-        """Remove all imports of the module."""
-        _mod_ffi_api.ModuleClearImports(self)
-
-    def write_to_file(self, file_name, fmt=""):
-        """Write the current module to file.
-
-        Parameters
-        ----------
-        file_name : str
-            The name of the file.
-        fmt : str
-            The format of the file.
-
-        See Also
-        --------
-        runtime.Module.export_library : export the module to shared library.
-        """
-        _mod_ffi_api.ModuleWriteToFile(self, file_name, fmt)
-
-    def time_evaluator(
-        self,
-        func_name,
-        dev,
-        number=10,
-        repeat=1,
-        min_repeat_ms=0,
-        limit_zero_time_iterations=100,
-        cooldown_interval_ms=0,
-        repeats_to_cooldown=1,
-        cache_flush_bytes=0,
-        f_preproc="",
-    ):
-        """Get an evaluator that measures time cost of running function.
-
-        Parameters
-        ----------
-        func_name: str
-            The name of the function in the module.
-
-        dev: Device
-            The device we should run this function on.
-
-        number: int
-            The number of times to run this function for taking average.
-            We call these runs as one `repeat` of measurement.
-
-        repeat: int, optional
-            The number of times to repeat the measurement.
-            In total, the function will be invoked (1 + number x repeat) times,
-            where the first one is warm up and will be discarded.
-            The returned result contains `repeat` costs,
-            each of which is an average of `number` costs.
-
-        min_repeat_ms: int, optional
-            The minimum duration of one `repeat` in milliseconds.
-            By default, one `repeat` contains `number` runs. If this parameter 
is set,
-            the parameters `number` will be dynamically adjusted to meet the
-            minimum duration requirement of one `repeat`.
-            i.e., When the run time of one `repeat` falls below this time, the 
`number` parameter
-            will be automatically increased.
-
-        limit_zero_time_iterations: int, optional
-            The maximum number of repeats when measured time is equal to 0.
-            It helps to avoid hanging during measurements.
-
-        cooldown_interval_ms: int, optional
-            The cooldown interval in milliseconds between the number of 
repeats defined by
-            `repeats_to_cooldown`.
-
-        repeats_to_cooldown: int, optional
-            The number of repeats before the cooldown is activated.
-
-        cache_flush_bytes: int, optional
-            The number of bytes to flush from the cache before each repeat.
-
-        f_preproc: str, optional
-            The preprocess function name we want to execute before executing 
the time evaluator.
-
-        Note
-        ----
-        The function will be invoked  (1 + number x repeat) times,
-        with the first call discarded in case there is lazy initialization.
-
-        Returns
-        -------
-        ftimer : function
-            The function that takes same argument as func and returns a 
BenchmarkResult.
-            The ProfileResult reports `repeat` time costs in seconds.
-        """
-        try:
-            feval = _ffi_api.RPCTimeEvaluator(
-                self,
-                func_name,
-                dev.device_type,
-                dev.device_id,
-                number,
-                repeat,
-                min_repeat_ms,
-                limit_zero_time_iterations,
-                cooldown_interval_ms,
-                repeats_to_cooldown,
-                cache_flush_bytes,
-                f_preproc,
-            )
-
-            def evaluator(*args):
-                """Internal wrapped evaluator."""
-                # Wrap feval so we can add more stats in future.
-                blob = feval(*args)
-                fmt = "@" + ("d" * repeat)
-                results = struct.unpack(fmt, blob)
-                return BenchmarkResult(results)
-
-            return evaluator
-        except NameError:
-            raise NameError("time_evaluator is only supported when RPC is 
enabled")
-
     def _collect_from_import_tree(self, filter_func):
         """Helper function to collect modules from the tree matching a 
filter_func, then return it.
 
@@ -418,6 +131,7 @@ class Module(tvm.ffi.Object):
         return dso_modules
 
     def _collect_dso_modules(self):
+        """Collect all compilation exportable modules from the import tree."""
         return self._collect_from_import_tree(lambda m: 
m.is_compilation_exportable())
 
     def export_library(
@@ -484,9 +198,10 @@ class Module(tvm.ffi.Object):
         # which are only available in when TVM function is available.
         if _RUNTIME_ONLY:
             raise RuntimeError("Cannot call export_library in runtime only 
mode")
+
         # Extra dependencies during runtime.
         from pathlib import Path
-        from tvm.contrib import cc as _cc, tar as _tar, utils as _utils, tvmjs 
as _tvmjs
+        from tvm.contrib import cc as _cc, tar as _tar, tvmjs as _tvmjs, utils 
as _utils
 
         if isinstance(file_name, Path):
             file_name = str(file_name)
@@ -559,7 +274,7 @@ class Module(tvm.ffi.Object):
             if fpack_imports is not None:
                 path_out = fpack_imports(self, is_system_lib, pack_lib_prefix, 
workspace_dir)
                 files.append(path_out)
-            elif enabled("llvm") and llvm_target_string:
+            elif _ffi_api.RuntimeEnabled("llvm") and llvm_target_string:
                 path_obj = os.path.join(
                     workspace_dir, 
f"{pack_lib_prefix}devc.{global_object_format}"
                 )
@@ -587,32 +302,103 @@ class Module(tvm.ffi.Object):
 
         return fcompile(file_name, files, **kwargs)
 
+    def time_evaluator(
+        self,
+        func_name,
+        dev,
+        number=10,
+        repeat=1,
+        min_repeat_ms=0,
+        limit_zero_time_iterations=100,
+        cooldown_interval_ms=0,
+        repeats_to_cooldown=1,
+        cache_flush_bytes=0,
+        f_preproc="",
+    ):
+        """Get an evaluator that measures time cost of running function.
+
+        Parameters
+        ----------
+        func_name: str
+            The name of the function in the module.
+
+        dev: Device
+            The device we should run this function on.
+
+        number: int
+            The number of times to run this function for taking average.
+            We call these runs as one `repeat` of measurement.
+
+        repeat: int, optional
+            The number of times to repeat the measurement.
+            In total, the function will be invoked (1 + number x repeat) times,
+            where the first one is warm up and will be discarded.
+            The returned result contains `repeat` costs,
+            each of which is an average of `number` costs.
+
+        min_repeat_ms: int, optional
+            The minimum duration of one `repeat` in milliseconds.
+            By default, one `repeat` contains `number` runs. If this parameter 
is set,
+            the parameters `number` will be dynamically adjusted to meet the
+            minimum duration requirement of one `repeat`.
+            i.e., When the run time of one `repeat` falls below this time, the 
`number` parameter
+            will be automatically increased.
 
-def system_lib(symbol_prefix=""):
-    """Get system-wide library module singleton.
+        limit_zero_time_iterations: int, optional
+            The maximum number of repeats when measured time is equal to 0.
+            It helps to avoid hanging during measurements.
 
-    System lib is a global module that contains self register functions in 
startup.
-    Unlike normal dso modules which need to be loaded explicitly.
-    It is useful in environments where dynamic loading api like dlopen is 
banned.
+        cooldown_interval_ms: int, optional
+            The cooldown interval in milliseconds between the number of 
repeats defined by
+            `repeats_to_cooldown`.
 
-    To build system lib function, simply specify target option ```llvm 
--system-lib```
-    The system lib will be available as long as the result code is linked by 
the program.
+        repeats_to_cooldown: int, optional
+            The number of repeats before the cooldown is activated.
 
-    The system lib is intended to be linked and loaded during the entire 
life-cyle of the program.
-    If you want dynamic loading features, use dso modules instead.
+        cache_flush_bytes: int, optional
+            The number of bytes to flush from the cache before each repeat.
 
-    Parameters
-    ----------
-    symbol_prefix: Optional[str]
-        Optional symbol prefix that can be used for search. When we lookup a 
symbol
-        symbol_prefix + name will first be searched, then the name without 
symbol_prefix.
+        f_preproc: str, optional
+            The preprocess function name we want to execute before executing 
the time evaluator.
 
-    Returns
-    -------
-    module : runtime.Module
-        The system-wide library module.
-    """
-    return _mod_ffi_api.SystemLib(symbol_prefix)
+        Note
+        ----
+        The function will be invoked  (1 + number x repeat) times,
+        with the first call discarded in case there is lazy initialization.
+
+        Returns
+        -------
+        ftimer : function
+            The function that takes same argument as func and returns a 
BenchmarkResult.
+            The ProfileResult reports `repeat` time costs in seconds.
+        """
+        try:
+            feval = _ffi_api.RPCTimeEvaluator(
+                self,
+                func_name,
+                dev.device_type,
+                dev.device_id,
+                number,
+                repeat,
+                min_repeat_ms,
+                limit_zero_time_iterations,
+                cooldown_interval_ms,
+                repeats_to_cooldown,
+                cache_flush_bytes,
+                f_preproc,
+            )
+
+            def evaluator(*args):
+                """Internal wrapped evaluator."""
+                # Wrap feval so we can add more stats in future.
+                blob = feval(*args)
+                fmt = "@" + ("d" * repeat)
+                results = struct.unpack(fmt, blob)
+                return BenchmarkResult(results)
+
+            return evaluator
+        except NameError:
+            raise NameError("time_evaluator is only supported when RPC is 
enabled")
 
 
 def load_module(path):
@@ -656,7 +442,7 @@ def load_module(path):
         _cc.create_shared(path + ".so", files)
         path += ".so"
     # Redirect to the load API
-    return _mod_ffi_api.ModuleLoadFromFile(path)
+    return _load_module(path)
 
 
 def load_static_library(path, func_names):
diff --git a/tests/python/runtime/test_runtime_module_export.py 
b/tests/python/runtime/test_runtime_module_export.py
index 0db1fa93dc..168d839dbc 100644
--- a/tests/python/runtime/test_runtime_module_export.py
+++ b/tests/python/runtime/test_runtime_module_export.py
@@ -54,7 +54,7 @@ def test_import_static_library():
     # Import mod1 as a static library into mod0 and compile to its own DSO.
     mod0.import_module(mod1_o)
     mod0_dso_path = temp.relpath("mod0.so")
-    mod0.export_library(mod0_dso_path)
+    tvm.runtime.Executable(mod0).export_library(mod0_dso_path)
 
     # The imported mod1 is statically linked into mod0.
     loaded_lib = tvm.runtime.load_module(mod0_dso_path)

Reply via email to