Stub Generation#
TVM-FFI provides tvm-ffi-stubgen, a tool that generates Python type stubs from C++
reflection metadata. It turns registered global functions and classes into proper Python
type hints, enabling IDE auto-completion and static type checking.
Prerequisite
This tutorial uses examples/python_packaging
as a running example. The package registers global functions (my_ffi_extension.add_one,
my_ffi_extension.raise_error) and an object type (my_ffi_extension.IntPair).
CMake-based Generation#
The recommended approach is to use CMake’s tvm_ffi_configure_target function.
This runs stub generation automatically after each build.
tvm_ffi_configure_target(<target>
STUB_DIR <dir>
[STUB_INIT ON|OFF]
[STUB_PKG <pkg>]
[STUB_PREFIX <prefix>]
)
From the example’s CMakeLists.txt:
add_library(my_ffi_extension SHARED src/extension.cc)
tvm_ffi_configure_target(my_ffi_extension STUB_DIR "./python" STUB_INIT ON)
- STUB_DIR
Required. Directory containing Python source files, relative to
CMAKE_CURRENT_SOURCE_DIR.- STUB_INIT (default:
OFF) Optional. When
OFF, fills in existing inline directives without creating new ones. WhenON, generates new files and directives.
STUB_INIT is OFF#
The tool fills in existing inline directives only. It scans files for directive markers and regenerates the content within those blocks:
Class fields and methods (
object/<type_key>)@tvm_ffi.register_object("my_ffi_extension.IntPair") class IntPair(tvm_ffi.Object): # tvm-ffi-stubgen(begin): object/my_ffi_extension.IntPair a: int b: int if TYPE_CHECKING: @staticmethod def __c_ffi_init__(_0: int, _1: int, /) -> Object: ... def sum(self, /) -> int: ... # tvm-ffi-stubgen(end)
Global functions (
global/<prefix>)# tvm-ffi-stubgen(begin): global/my_ffi_extension tvm_ffi.init_ffi_api("my_ffi_extension", __name__) if TYPE_CHECKING: def add_one(_0: int, /) -> int: ... def raise_error(_0: str, /) -> None: ... # tvm-ffi-stubgen(end)
Import sections,
__all__lists, and exports (import-section,__all__,export/<module>)
See Inline Directives for a complete reference of all inline directives.
Important
No new files or directives are created. Use this mode after you have customized the generated files and want to preserve your changes.
STUB_INIT is ON#
The tool generates new files (_ffi_api.py, __init__.py) and new directives from
scratch. This is the recommended starting point for new projects. Two additional options
are available:
- STUB_PKG (default:
SKBUILD_PROJECT_NAMEor CMake target name) Python package name. Determines the directory structure (
<STUB_DIR>/<STUB_PKG>/) and generates the library loading code in_ffi_api.py:LIB = tvm_ffi.libinfo.load_lib_module("<STUB_PKG>", "<cmake-target-name>")
This uses
tvm_ffi.libinfo.load_lib_module()to load the shared library.- STUB_PREFIX (default:
<STUB_PKG>.) Registry prefix filter. Only functions and classes whose registered names start with this prefix are included. The tool generates:
global/<prefix>directives for global functions. In<STUB_DIR>/<STUB_PKG>/_ffi_api.py:# tvm-ffi-stubgen(begin): global/<STUB_PREFIX> ... # tvm-ffi-stubgen(end)
Functions in nested namespaces (e.g.,
<STUB_PREFIX>submodule.func) are placed in corresponding subdirectories.object/<type_key>directives for each registered class. For a class with type key<STUB_PREFIX>IntPair:@tvm_ffi.register_object("<STUB_PREFIX>IntPair") class IntPair(_ffi_Object): # tvm-ffi-stubgen(begin): object/<STUB_PREFIX>IntPair a: int b: int if TYPE_CHECKING: @staticmethod def __c_ffi_init__(_0: int, _1: int, /) -> Object: ... def sum(self, /) -> int: ... # tvm-ffi-stubgen(end)
Classes in nested namespaces are similarly placed in corresponding subdirectories.
CLI-based Generation#
For standalone usage or custom build systems, use the command-line tool directly. The CLI options correspond to CMake options as follows:
CLI Option |
CMake Option |
Description |
|---|---|---|
|
|
Directory containing Python source files |
|
|
Presence of |
|
|
Python package name |
|
|
Registry prefix filter |
|
(target name) |
CMake target name for the shared library |
Basic Usage#
To generate complete stub files from scratch (equivalent to STUB_INIT ON):
tvm-ffi-stubgen \
python/my_ffi_extension \
--dlls build/libmy_ffi_extension.so \
--init-pypkg my-ffi-extension \
--init-lib my_ffi_extension \
--init-prefix "my_ffi_extension."
To update only existing directives (equivalent to STUB_INIT OFF), omit the --init-* flags:
tvm-ffi-stubgen \
python/my_ffi_extension \
--dlls build/libmy_ffi_extension.so
Command Line Options#
Required arguments:
<directory>(positional)Directory to generate/update Python stubs. Corresponds to
STUB_DIR.--dllsShared libraries to load during generation. The stub generator loads these DLLs to discover registered global functions and object types. Without loading the DLLs, the generator cannot know what functions and classes exist. Use semicolons to separate multiple libraries.
Arguments for full generation (corresponds to STUB_INIT ON):
All three are required together. When omitted, the tool operates in directive-only mode
(equivalent to STUB_INIT OFF).
--init-pypkgPython package name. Corresponds to
STUB_PKG.--init-libCMake target name. Used as the second argument in the generated
load_lib_module("<STUB_PKG>", "<init-lib>")call.--init-prefixRegistry prefix filter. Corresponds to
STUB_PREFIX.
Optional arguments:
--verbosePrint a unified diff of changes to each file.
--dry-runPreview changes without writing to files.
--importsAdditional Python modules to import before generation (semicolon-separated).
--indentIndentation width for generated code (default: 4).
For a complete list of options, run tvm-ffi-stubgen --help.
Advanced Topics#
Inline Directives#
Inline directives are special comments that mark regions where the tool should generate or update code. They allow you to:
Add custom code alongside generated stubs
Control the exact placement of generated code
Maintain hand-written documentation or additional methods
Integrate with existing codebases
The tool preserves content outside directive blocks, only modifying marked regions.
Directive Syntax
Directives use comment markers to delimit generated regions:
# tvm-ffi-stubgen(begin): <directive-type>/<parameter>
# ... generated code appears here ...
# tvm-ffi-stubgen(end)
When you run the tool, it:
Parses files for directive markers
Regenerates content between
beginandendmarkersPreserves everything outside the markers
Directive Reference
global/<prefix>- Global FunctionsMarks a section for global function stubs. All functions registered with the given prefix are included.
# tvm-ffi-stubgen(begin): global/my_ext.arith # fmt: off tvm_ffi.init_ffi_api("my_ext.arith", __name__) if TYPE_CHECKING: def add_one(_0: int, /) -> int: ... def add_two(_0: int, /) -> int: ... # fmt: on # tvm-ffi-stubgen(end)
object/<type_key>- Object TypesMarks a section for class field and method stubs. Place inside a class definition decorated with
@tvm_ffi.register_object.@tvm_ffi.register_object("my_ffi_extension.IntPair") class IntPair(_ffi_Object): # tvm-ffi-stubgen(begin): object/my_ffi_extension.IntPair # fmt: off a: int b: int if TYPE_CHECKING: def __init__(self, a: int, b: int) -> None: ... def sum(self) -> int: ... # fmt: on # tvm-ffi-stubgen(end)
import-section- Import SectionPopulates import statements for types used in generated stubs. The tool automatically determines which imports are needed.
# tvm-ffi-stubgen(begin): import-section # fmt: off # isort: off from __future__ import annotations from tvm_ffi import init_ffi_api as _FFI_INIT_FUNC from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Mapping, Sequence from tvm_ffi import Device, Object, Tensor, dtype # isort: on # fmt: on # tvm-ffi-stubgen(end)
export/<module>- ExportRe-exports names from a submodule, typically used in
__init__.pyto aggregate exports.# tvm-ffi-stubgen(begin): export/_ffi_api # fmt: off # isort: off from ._ffi_api import * # noqa: F403 from ._ffi_api import __all__ as _ffi_api__all__ if "__all__" not in globals(): __all__ = [] __all__.extend(_ffi_api__all__) # isort: on # fmt: on # tvm-ffi-stubgen(end)
__all__- Export ListPopulates the
__all__list with all generated class and function names.__all__ = [ # tvm-ffi-stubgen(begin): __all__ "LIB", "IntPair", "add_one", # tvm-ffi-stubgen(end) ]
ty-map- Type MappingMaps a C++ type key to a different Python type path. This is useful when the Python class is defined in a different module than the type key suggests.
# tvm-ffi-stubgen(ty-map): ffi.reflection.AccessStep -> ffi.access_path.AccessStepimport-object- Import ObjectInjects a custom import into generated code. The format is
<full_name>;<type_checking_only>;<alias>.# tvm-ffi-stubgen(import-object): ffi.Object;False;_ffi_ObjectThis imports
ffi.Objectas_ffi_Objectfor use in generated code. The second field (False) indicates the import is not TYPE_CHECKING-only.skip-file- Skip FilePrevents the tool from modifying the file. Place anywhere in the file.
# tvm-ffi-stubgen(skip-file)