交叉编译和 RPC

本文档介绍了如何在TVM中使用RPC进行交叉编译,以便在本地编译并在远程设备如树莓派和Firefly-RK3399上运行。教程详细阐述了在远程设备上构建TVMruntime、设置RPC服务器、在本地声明和编译内核,以及通过RPC运行CPU和OpenCL内核的步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本篇文章译自英文文档 Cross Compilation and RPC 作者是 Ziheng Jiang,Lianmin Zheng。更多 TVM 中文文档可访问 →TVM 中文站

本教程介绍了如何在 TVM 中使用 RPC 进行交叉编译和远程设备执行。

利用交叉编译和 RPC,可以实现程序在本地机器编译,在远程设备运行。这个特性在远程设备资源有限时(如在树莓派和移动平台上)尤其有用。本教程将把树莓派作为 CPU 示例,把 Firefly-RK3399 作为 OpenCL 示例进行演示。

在设备上构建 TVM Runtime

首先在远程设备上构建 TVM runtime。

注意 本节和下一节中的所有命令都应在目标设备(例如树莓派)上执行。假设目标设备运行 Linux 系统。

由于在本地机器上只做编译,而远程设备用于运行生成的代码。所以只需在远程设备上构建 TVM runtime。

git clone --recursive https://github.com/apache/tvm tvm
cd tvm
make runtime -j2

成功构建 runtime 后,要在 ~/.bashrc 文件中设置环境变量。可以用 vi ~/.bashrc命令编辑 ~/.bashrc,在这个文件里添加下面这行代码(假设 TVM 目录在 ~/tvm 中):

export PYTHONPATH=$PYTHONPATH:~/tvm/python

执行 source ~/.bashrc 来更新环境变量。

在设备上设置 RPC 服务器

在远程设备(本例为树莓派)上运行以下命令来启动 RPC 服务器:

python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090

看到下面这行提示,则表示 RPC 服务器已成功启动。

INFO:root:RPCServer: bind to 0.0.0.0:9090

在本地机器上声明和交叉编译内核

备注 现在回到本地机器(已经用 LLVM 安装了完整的 TVM)。

在本地机器上声明一个简单的内核:

import numpy as np

import tvm
from tvm import te
from tvm import rpc
from tvm.contrib import utils

n = tvm.runtime.convert(1024)
A = te.placeholder((n,), name="A")
B = te.compute((n,), lambda i: A[i] + 1.0, name="B")
s = te.create_schedule(B.op)

然后交叉编译内核。对于树莓派 3B,target 是“llvm -mtriple=armv7l-linux-gnueabihf”,但这里用的是“llvm”,使得本教程可以在网页构建服务器上运行。请参阅下面的详细说明。

local_demo = True

if local_demo:
    target = "llvm"
else:
    target = "llvm -mtriple=armv7l-linux-gnueabihf"

func = tvm.build(s, [A, B], target=target, name="add_one")
# 将 lib 存储在本地临时文件夹
temp = utils.tempdir()
path = temp.relpath("lib.tar")
func.export_library(path)

备注

要使本教程运行在真正的远程设备上,需要将 local_demo 改为 False,并将 build 中的 target 替换为适合设备的 target 三元组。不同设备的 target 三元组可能不同。例如,对于树莓派 3B,它是 llvm -mtriple=armv7l-linux-gnueabihf;对于 RK3399,它是 llvm -mtriple=aarch64-linux-gnu。

通常,可以在设备上运行 gcc -v 来查询 target,寻找以 Target 开头的行:(尽管它可能仍然是一个松散的配置。)
除了 -mtriple,还可设置其他编译选项,例如:

  • -mcpu=< cpuname>

指定生成的代码运行的芯片架构。默认情况这是从 target 三元组推断出来的,并自动检测到当前架构。

  • -mattr=a1,+a2,-a3,…

覆盖或控制 target 的指定属性,例如是否启用 SIMD 操作。默认属性集由当前 CPU 设置。要获取可用属性列表,执行:

  llc -mtriple=<your device target triple> -mattr=help

这些选项与 llc 一致。建议设置 target 三元组和功能集,使其包含可用的特定功能,这样我们可以充分利用单板的功能。查看 LLVM 交叉编译指南获取有关交叉编译属性的详细信息。

通过 RPC 远程运行 CPU 内核

下面将演示如何在远程设备上运行生成的 CPU 内核。首先,从远程设备获取 RPC 会话:

if local_demo:
    remote = rpc.LocalSession()
else:
    # 下面是我的环境,将这个换成你目标设备的 IP 地址
    host = "10.77.1.162"
    port = 9090
    remote = rpc.connect(host, port)

将 lib 上传到远程设备,然后调用设备的本地编译器重新链接它们。其中 func 是一个远程模块对象。

remote.upload(path)
func = remote.load_module("lib.tar")

# 在远程设备上创建数组
dev = remote.cpu()
a = tvm.nd.array(np.random.uniform(size=1024).astype(A.dtype), dev)
b = tvm.nd.array(np.zeros(1024, dtype=A.dtype), dev)
# 这个函数将在远程设备上运行
func(a, b)
np.testing.assert_equal(b.numpy(), a.numpy() + 1)

要想评估内核在远程设备上的性能,避免网络开销很重要。time_evaluator 返回一个远程函数,这个远程函数多次运行 func 函数,并测试每一次在远程设备上运行的成本,然后返回测试的成本(不包括网络开销)。

time_f = func.time_evaluator(func.entry_name, dev, number=10)
cost = time_f(a, b).mean
print("%g secs/op" % cost)

输出结果:

1.369e-07 secs/op

通过 RPC 远程运行 OpenCL 内核

远程 OpenCL 设备的工作流程与上述内容基本相同。可以定义内核、上传文件,然后通过 RPC 运行。

备注
树莓派不支持 OpenCL,下面的代码是在 Firefly-RK3399 上测试的。可以按照 教程 为 RK3399 设置 OS 及 OpenCL 驱动程序。
在 rk3399 板上构建 runtime 也需启用 OpenCL。在 TVM 根目录下执行:

cp cmake/config.cmake .
sed -i "s/USE_OPENCL OFF/USE_OPENCL ON/" config.cmake
make runtime -j4

下面的函数展示了如何远程运行 OpenCL 内核:

def run_opencl():
    # 注意:这是 rk3399 板的设置。你需要根据你的环境进行修改
    opencl_device_host = "10.77.1.145"
    opencl_device_port = 9090
    target = tvm.target.Target("opencl", host="llvm -mtriple=aarch64-linux-gnu")

    # 为上面的计算声明 "add one" 创建 schedule
    s = te.create_schedule(B.op)
    xo, xi = s[B].split(B.op.axis[0], factor=32)
    s[B].bind(xo, te.thread_axis("blockIdx.x"))
    s[B].bind(xi, te.thread_axis("threadIdx.x"))
    func = tvm.build(s, [A, B], target=target)

    remote = rpc.connect(opencl_device_host, opencl_device_port)

    # 导出并上传
    path = temp.relpath("lib_cl.tar")
    func.export_library(path)
    remote.upload(path)
    func = remote.load_module("lib_cl.tar")

    # 运行
    dev = remote.cl()
    a = tvm.nd.array(np.random.uniform(size=1024).astype(A.dtype), dev)
    b = tvm.nd.array(np.zeros(1024, dtype=A.dtype), dev)
    func(a, b)
    np.testing.assert_equal(b.numpy(), a.numpy() + 1)
    print("OpenCL test passed!")

总结

本教程介绍了 TVM 中的交叉编译和 RPC 功能。

  • 在远程设备上设置 RPC 服务器。
  • 设置目标设备配置,使得可在本地机器上交叉编译内核。
  • 通过 RPC API 远程上传和运行内核。

下载 Python 源代码
下载 Jupyter Notebook

以上就是该文档的全部内容,查看更多 TVM 中文文档,请访问→TVM 中文站

<think>好的,我现在需要帮助用户找到关于如何使用NDK对gRPC进行交叉编译的指南或解决方案。首先,用户提到了NDKgRPC,所以我要先确认这两个技术的基本信息,以及它们交叉编译时可能遇到的问题。 用户提供的引用中有三个来源。引用1是关于Android NDK使用ndk-build编译生成so库的教程,这可能对基础步骤有帮助。引用2提到NDK工具链对std::cerrstd::cout的支持不完全,导致改用fprintf(stderr),这说明在交叉编译时需要注意标准库的问题。引用3提到OpenHarmony的框架层支持C/C++,可能涉及到不同平台的兼容性,但可能当前问题关联不大,暂时先不考虑。 接下来,我需要考虑用户的具体需求:使用NDK交叉编译gRPC。gRPC是一个高性能的RPC框架,依赖较多,如protobuf、openssl等。交叉编译时通常需要处理依赖库的编译,以及正确配置工具链。 可能的步骤包括: 1. 准备NDK工具链,设置环境变量。 2. 下载gRPC源码,并处理其子模块依赖。 3. 编写CMake或Android.mk文件,配置交叉编译参数。 4. 处理依赖库,比如静态编译protobuf等。 5. 解决编译过程中出现的错误,如标准库问题、ABI兼容性等。 用户引用2中提到的问题,即NDK对C++标准库支持不完全,可能需要替换日志输出方式,比如使用fprintf代替std::cerr,这点在编译gRPC时需要注意,因为gRPC本身可能有日志输出部分。 另外,可能需要参考现有的开源项目或社区解决方案,比如GitHub上的相关项目,或者官方文档是否有针对Android NDK的指导。例如,gRPC官方可能有关于交叉编译到Android的说明,或者社区贡献的脚本。 可能的挑战包括: - 依赖库的交叉编译,尤其是像openssl这样的库,需要正确配置目标平台。 - 处理C++异常RTTI,因为NDK默认可能禁用这些,而gRPC可能依赖它们。 - 不同ABI(armeabi-v7a, arm64-v8a等)的适配。 解决方案可能需要分步骤指导,比如先编译protobuf,再编译gRPC,同时配置正确的工具链路径编译选项。另外,使用CMake的toolchain文件来指定NDK工具链的位置参数。 现在需要验证这些思路是否正确。例如,查找是否有官方文档提到Android NDK的交叉编译步骤,或者社区中的教程。例如,gRPC的官方文档可能有“Building for Android”的章节,或者GitHub issues中讨论过相关问题。 另外,用户提供的引用1中的教程可能展示了如何用ndk-build编译简单的库,但gRPC更复杂,可能需要使用CMake而不是ndk-build。因此,可能需要指导用户使用CMake来配置项目,并引用NDK的toolchain.cmake文件。 总结步骤可能包括: 1. 安装NDK并设置工具链路径。 2. 使用CMake配置gRPC,指定工具链文件。 3. 处理依赖项的交叉编译。 4. 修改代码或配置以适配NDK的限制(如日志输出)。 5. 编译并测试生成的库。 同时,需要注意不同NDK版本gRPC版本的兼容性,可能需要指定特定版本组合以避免已知问题。</think>### 如何使用NDK对gRPC进行交叉编译? 以下是基于NDK对gRPC进行交叉编译的步骤指南及注意事项: --- #### **1. 环境准备** - **NDK工具链**:下载并配置Android NDK(建议使用r21+版本),设置环境变量: ```bash export NDK_HOME=/path/to/your/ndk export TOOLCHAIN=$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64 ``` 引用[1]中提到的`ndk-build`工具链配置逻辑与此类似[^1]。 - **gRPC源码**:克隆gRPC仓库并初始化子模块: ```bash git clone --recurse-submodules https://github.com/grpc/grpc ``` --- #### **2. 交叉编译依赖库** gRPC依赖protobuf、openssl等库,需先交叉编译这些库: - **protobuf编译示例**: ```bash cd protobuf mkdir build && cd build cmake -DCMAKE_TOOLCHAIN_FILE=$NDK_HOME/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ -DANDROID_NATIVE_API_LEVEL=21 \ -DBUILD_SHARED_LIBS=OFF .. make -j4 ``` - **openssl编译**:需使用NDK的clang编译器,通过`Configure`脚本指定目标平台。 --- #### **3. 配置gRPC的CMake参数** 在gRPC的CMake中指定NDK工具链依赖路径: ```cmake set(CMAKE_TOOLCHAIN_FILE $ENV{NDK_HOME}/build/cmake/android.toolchain.cmake) set(ANDROID_ABI arm64-v8a) set(ANDROID_NATIVE_API_LEVEL 21) set(Protobuf_LIBRARIES /path/to/protobuf/build/lib/libprotobuf.a) include_directories(/path/to/protobuf/build/include) ``` --- #### **4. 处理NDK兼容性问题** - **日志输出问题**:如引用[2]所述,避免使用`std::cerr`,可修改gRPC代码中的日志输出为`fprintf(stderr, ...)`[^2]。 - **异常与RTTI**:在`CMakeLists.txt`中启用C++异常RTTI: ```cmake add_compile_options(-fexceptions -frtti) ``` --- #### **5. 编译gRPC** 执行CMake构建命令: ```bash mkdir build && cd build cmake .. make -j4 ``` --- #### **6. 验证与集成** 将生成的静态库(如`libgrpc.a`)集成到Android项目中,并在`Android.mk`或`CMakeLists.txt`中链接: ```cmake target_link_libraries(your_project grpc protobuf ssl crypto) ``` --- ### **注意事项** - **ABI兼容性**:需统一所有依赖库的ABI(如`arm64-v8a`)。 - **静态链接**:建议静态编译依赖库以减少运行时问题。 - **NDK版本**:gRPC对NDK版本较敏感,建议使用NDK r21+。 相关问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值