.. 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.

Quick Start
===========

.. note::

  All the code in this tutorial is under `examples/quickstart <https://github.com/apache/tvm-ffi/tree/main/examples/quickstart>`_ in the repository.

This guide walks through shipping a minimal ``add_one`` function that computes
``y = x + 1`` in C++ and CUDA.
TVM-FFI's Open ABI and FFI make it possible to **ship one library** for multiple frameworks and languages.
We can build a single shared library that works across:

- **ML frameworks**, e.g. PyTorch, JAX, PaddlePaddle, NumPy, CuPy, and others;
- **Languages**, e.g. C++, Python, Rust, and others;
- **Python ABI versions**, e.g. one wheel that supports all Python versions, including free-threaded ones.

.. admonition:: Prerequisite
   :class: hint
   :name: prerequisite

   - Python: 3.9 or newer
   - Compiler: C++17-capable toolchain (GCC/Clang/MSVC)
   - Optional ML frameworks for testing: NumPy, PyTorch, JAX, CuPy, PaddlePaddle
   - CUDA: Any modern version (if you want to try the CUDA part)
   - TVM-FFI installed via:

     .. code-block:: bash

        pip install --reinstall --upgrade apache-tvm-ffi


Write a Simple ``add_one``
--------------------------

Source Code
~~~~~~~~~~~

Suppose we implement a C++ function ``AddOne`` that performs elementwise ``y = x + 1`` for a 1-D ``float32`` vector. The source code (C++ and CUDA) is:

.. hint::

  Include the umbrella header to access all the core C++ APIs.

  .. code-block:: cpp

    #include <tvm/ffi/tvm_ffi.h>

.. tabs::

  .. group-tab:: C++

    .. _cpp_add_one_kernel:

    .. literalinclude:: ../../examples/quickstart/compile/add_one_cpu.cc
      :language: cpp
      :emphasize-lines: 7, 16
      :start-after: [example.begin]
      :end-before: [example.end]

  .. group-tab:: CUDA

    .. literalinclude:: ../../examples/quickstart/compile/add_one_cuda.cu
      :language: cpp
      :emphasize-lines: 14, 21, 25
      :start-after: [example.begin]
      :end-before: [example.end]


The macro :c:macro:`TVM_FFI_DLL_EXPORT_TYPED_FUNC` exports the C++ function ``AddOne``
as a TVM-FFI-compatible symbol ``__tvm_ffi_add_one_cpu/cuda``. If :c:macro:`TVM_FFI_DLL_EXPORT_INCLUDE_METADATA` is set to 1,
it also exports the function's metadata as a symbol ``__tvm_ffi__metadata_add_one_cpu/cuda`` for type checking and stub generation.

The class :cpp:class:`tvm::ffi::TensorView` enables zero-copy interop with tensors from different ML frameworks:

- NumPy, CuPy,
- PyTorch, JAX, PaddlePaddle, or
- any array type that supports the standard :external+data-api:doc:`DLPack protocol <design_topics/data_interchange>`.

Finally, :cpp:func:`TVMFFIEnvGetStream` can be used in the CUDA code to launch kernels on the caller's stream.

.. seealso::

   - :doc:`../guides/export_func_cls`: All three export mechanisms
     (C symbols, global functions, classes) with complete examples.
   - :doc:`../guides/kernel_library_guide`: Production-grade CUDA kernel
     patterns including input validation, device guard, stream handling, and
     dtype dispatch.
   - :doc:`../concepts/tensor`: Tensor concepts,
     :cpp:class:`~tvm::ffi::TensorView` API, and DLPack interop details.

.. _sec-cpp-compile-with-tvm-ffi:

Compile with TVM-FFI
~~~~~~~~~~~~~~~~~~~~

**Raw command.** Use the following minimal commands to compile the source code:

.. tabs::

  .. group-tab:: C++

    .. literalinclude:: ../../examples/quickstart/raw_compile.sh
      :language: bash
      :start-after: [cpp_compile.begin]
      :end-before: [cpp_compile.end]

  .. group-tab:: CUDA

    .. literalinclude:: ../../examples/quickstart/raw_compile.sh
      :language: bash
      :start-after: [cuda_compile.begin]
      :end-before: [cuda_compile.end]

These steps produce shared libraries ``add_one_cpu.so`` and ``add_one_cuda.so`` that can be used across languages and frameworks.

.. hint::

   For a single-file C++/CUDA project, :py:func:`tvm_ffi.cpp.load_inline`
   minimizes boilerplate for compilation, linking, and loading.


**CMake.** CMake is the preferred approach for building across platforms.
TVM-FFI integrates with CMake via ``find_package`` as demonstrated below:

.. tabs::

  .. group-tab:: C++

    .. code-block:: cmake

      # Run `tvm-ffi-config --cmakedir` to set `tvm_ffi_ROOT`
      find_package(Python COMPONENTS Interpreter REQUIRED)
      execute_process(COMMAND "${Python_EXECUTABLE}" -m tvm_ffi.config --cmakedir OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE tvm_ffi_ROOT)
      find_package(tvm_ffi CONFIG REQUIRED)

      # Link C++ target to `tvm_ffi::header` and `tvm_ffi::shared`
      add_library(add_one_cpu SHARED compile/add_one_cpu.cc)
      tvm_ffi_configure_target(add_one_cpu)

  .. group-tab:: CUDA

    .. code-block:: cmake

      enable_language(CUDA)
      # Run `tvm-ffi-config --cmakedir` to set `tvm_ffi_ROOT`
      find_package(Python COMPONENTS Interpreter REQUIRED)
      execute_process(COMMAND "${Python_EXECUTABLE}" -m tvm_ffi.config --cmakedir OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE tvm_ffi_ROOT)
      find_package(tvm_ffi CONFIG REQUIRED)

      # Link CUDA target to `tvm_ffi::header` and `tvm_ffi::shared`
      add_library(add_one_cuda SHARED compile/add_one_cuda.cu)
      tvm_ffi_configure_target(add_one_cuda)

**Artifact.** The resulting ``add_one_cpu.so`` and ``add_one_cuda.so`` are small libraries that are agnostic to:

- Python version/ABI. They are not compiled or linked with Python and depend only on TVM-FFI's stable C ABI;
- Languages, including C++, Python, Rust, or any other language that can interop with the C ABI;
- ML frameworks, such as PyTorch, JAX, PaddlePaddle, NumPy, CuPy, or any array library that implements the standard :external+data-api:doc:`DLPack protocol <design_topics/data_interchange>`.

.. seealso::

   :doc:`../packaging/cpp_tooling` for the full build toolchain guide covering
   CMake integration, raw compiler commands, and cross-platform library
   distribution.

.. _sec-use-across-framework:

Ship Across ML Frameworks
-------------------------

TVM-FFI's Python package provides :py:func:`tvm_ffi.load_module` to load either
``add_one_cpu.so`` or ``add_one_cuda.so`` into a :py:class:`tvm_ffi.Module`.

.. code-block:: python

  import tvm_ffi
  mod  : tvm_ffi.Module   = tvm_ffi.load_module("add_one_cpu.so")
  func : tvm_ffi.Function = mod.add_one_cpu

``mod.add_one_cpu`` retrieves a callable :py:class:`tvm_ffi.Function` that accepts tensors from host frameworks
directly. This is zero-copy, requires no boilerplate code, and adds very little overhead.

.. seealso::

   :ref:`sec:module` in :doc:`../concepts/func_module` for details on the module
   system, function retrieval, and the calling convention that enables
   cross-language interop.

We can then use these functions in the following ways:

.. _ship-to-pytorch:

PyTorch
~~~~~~~

.. literalinclude:: ../../examples/quickstart/load/load_pytorch.py
  :language: python
  :start-after: [example.begin]
  :end-before: [example.end]

.. _ship-to-jax:

JAX
~~~

Support is provided via `nvidia/jax-tvm-ffi <https://github.com/nvidia/jax-tvm-ffi>`_. Install it with:

.. code-block:: bash

  pip install jax-tvm-ffi

After installation, ``add_one_cuda`` can be registered as a target for JAX's ``ffi_call``.

.. code-block:: python

  # Step 1. Load `build/add_one_cuda.so`
  import tvm_ffi
  mod = tvm_ffi.load_module("build/add_one_cuda.so")

  # Step 2. Register `mod.add_one_cuda` into JAX
  import jax_tvm_ffi
  jax_tvm_ffi.register_ffi_target("add_one", mod.add_one_cuda, platform="gpu")

  # Step 3. Run `mod.add_one_cuda` with JAX
  import jax
  import jax.numpy as jnp
  jax_device, *_ = jax.devices("gpu")
  x = jnp.array([1, 2, 3, 4, 5], dtype=jnp.float32, device=jax_device)
  y = jax.ffi.ffi_call(
    "add_one",  # name of the registered function
    jax.ShapeDtypeStruct(x.shape, x.dtype),  # shape and dtype of the output
    vmap_method="broadcast_all",
  )(x)
  print(y)

.. _ship-to-paddle:

PaddlePaddle
~~~~~~~~~~~~

Since PaddlePaddle 3.3.0, full TVM FFI support is provided.

.. literalinclude:: ../../examples/quickstart/load/load_paddle.py
  :language: python
  :start-after: [example.begin]
  :end-before: [example.end]

.. _ship-to-numpy:

NumPy/CuPy
~~~~~~~~~~

.. literalinclude:: ../../examples/quickstart/load/load_numpy.py
  :language: python
  :start-after: [example.begin]
  :end-before: [example.end]


.. literalinclude:: ../../examples/quickstart/load/load_cupy.py
  :language: python
  :start-after: [example.begin]
  :end-before: [example.end]


Ship Across Languages
---------------------

TVM-FFI's core loading mechanism is ABI-stable and works across language boundaries.
A single library can be loaded in any language TVM-FFI supports,
without recompiling for different ABIs or languages.

.. _ship-to-python:

Python
~~~~~~

As shown in the :ref:`previous section<sec-use-across-framework>`, :py:func:`tvm_ffi.load_module` loads a language-
and framework-independent ``add_one_cpu.so`` or ``add_one_cuda.so`` and can be used with any Python
array framework that implements the standard :external+data-api:doc:`DLPack protocol <design_topics/data_interchange>`.

.. _ship-to-cpp:

C++
~~~

TVM-FFI's C++ API :cpp:func:`tvm::ffi::Module::LoadFromFile` loads ``add_one_cpu.so`` or ``add_one_cuda.so`` and
can be used directly from C/C++ without a Python dependency.

.. literalinclude:: ../../examples/quickstart/load/load_cpp.cc
   :language: cpp
   :start-after: [main.begin]
   :end-before: [main.end]

.. dropdown:: Auxiliary Logics

  .. literalinclude:: ../../examples/quickstart/load/load_cpp.cc
    :language: cpp
    :start-after: [aux.begin]
    :end-before: [aux.end]

Compile and run it with:

.. literalinclude:: ../../examples/quickstart/raw_compile.sh
   :language: bash
   :start-after: [load_cpp.begin]
   :end-before: [load_cpp.end]

.. note::

  Prefer not to load shared libraries? Static linking is also supported.

  In such cases, use :cpp:func:`tvm::ffi::Function::FromExternC` to create a
  :cpp:class:`tvm::ffi::Function` from the exported symbol, or directly use
  :cpp:func:`tvm::ffi::Function::InvokeExternC` to invoke the function.

  This feature can be useful on iOS, or when the exported module is generated
  by another DSL compiler targeting the ABI.

  .. code-block:: cpp

    // Linked with `add_one_cpu.o` or `add_one_cuda.o`
    #include <tvm/ffi/tvm_ffi.h>

    // declare reference to the exported symbol
    extern "C" int __tvm_ffi_add_one_cpu(void*, const TVMFFIAny*, int32_t, TVMFFIAny*);

    namespace ffi = tvm::ffi;

    int bundle_add_one(ffi::TensorView x, ffi::TensorView y) {
      void* closure_handle = nullptr;
      ffi::Function::InvokeExternC(closure_handle, __tvm_ffi_add_one_cpu, x, y);
      return 0;
    }

.. _ship-to-rust:

Rust
~~~~

TVM-FFI's Rust API ``tvm_ffi::Module::load_from_file`` loads ``add_one_cpu.so`` or ``add_one_cuda.so`` and
then retrieves a function ``add_one_cpu`` or ``add_one_cuda`` from it.
This mirrors the C++ and Python flows:

.. code-block:: rust

  fn run_add_one(x: &Tensor, y: &Tensor) -> Result<()> {
    let module = tvm_ffi::Module::load_from_file("add_one_cpu.so")?;
    let func = module.get_function("add_one_cpu")?;
    let typed_fn = into_typed_fn!(func, Fn(&Tensor, &Tensor) -> Result<()>);
    typed_fn(x, y)?;
    Ok(())
  }


.. hint::

    You can also use the Rust API to target the TVM-FFI ABI. This lets you write the function
    implementation in Rust and export it to Python/C++ in the same way.


Troubleshooting
---------------

- ``OSError: cannot open shared object file``: Add an rpath (Linux/macOS) or ensure the DLL is on ``PATH`` (Windows). Example run-path: ``-Wl,-rpath,$(tvm-ffi-config --libdir)``.
- ``undefined symbol: __tvm_ffi_add_one_cpu``: Ensure you used :c:macro:`TVM_FFI_DLL_EXPORT_TYPED_FUNC` and compiled with default symbol visibility (``-fvisibility=hidden`` is fine; the macro ensures export).
- ``CUDA error: invalid device function``: Rebuild with the correct ``-arch=sm_XX`` for your GPU, or include multiple ``-gencode`` entries.


Further Reading
---------------

- :doc:`stable_c_abi`: The stable C ABI layout, calling convention, and end-to-end
  C examples from both the callee and caller sides.
- :doc:`../guides/export_func_cls`: Export C symbols, global functions, and classes
  across C, C++, and Python.
- :doc:`../guides/kernel_library_guide`: Production-grade CUDA kernel patterns
  (validation, device guard, stream, dtype dispatch).
- :doc:`../concepts/func_module`: Calling convention, module system, and global
  registry concepts.
- :doc:`../concepts/tensor`: Tensor representation, DLPack interop, and stream
  handling.
- :doc:`../concepts/abi_overview`: Low-level ABI specification for all TVM-FFI types.
- :doc:`../concepts/exception_handling`: Throwing, catching, and propagating
  exceptions across language boundaries.
- :doc:`../packaging/python_packaging`: Packaging extensions as ABI-agnostic Python
  wheels.
- :doc:`../packaging/cpp_tooling`: Build toolchain, CMake integration, and library
  distribution.
- :doc:`../packaging/stubgen`: Generating Python type stubs from C++ metadata.
