news 2026/9/20 18:26:12

LLVM核心架构与llvmpipe向量化:从源码构建到实战优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLVM核心架构与llvmpipe向量化:从源码构建到实战优化

llvm-project 是我这几年接触过的最值得深入研究的开源项目之一。如果你只把它理解成“一个编译器”,那就太可惜了。它实际上是一整套模块化的编译器工具链基础设施,Clang、LLD、libc++、compiler-rt 这些鼎鼎大名的组件,全都生长在 LLVM 这棵大树的根系之上。无论你是做编程语言设计、高性能计算、图形渲染、安全分析,还是单纯想搞懂编译器到底怎么把 C++ 代码变成机器码,llvm-project 都是一座绕不开的技术富矿。

我最初入坑是因为工作中需要为一个特定领域加速器做代码生成,LLVM 的 Retargetable 设计让我可以聚焦在后端指令选择上,前端和优化器直接复用。后来用久了才发现,它在 CPU 指令集生成、GPU Shader 编译、甚至数据库向量化执行引擎里都有应用。现在 LLVM 15 之后的中端 IR 已经内置了向量位宽抽象能力,配合 llvmpipe 这样的软件渲染器,256-bit SIMD 路径可以被完全打通。

这篇文章我不想抄官方文档,而是从实际工程视角出发,把 LLVM 的核心架构、关键组件、从源码构建 LLVM 的完整过程,以及踩过的坑和常见问题排查方法都梳理一遍。尤其是 llvmpipe 和向量化相关的内容,很多人只闻其名不见其形,这次一次说透。

1. 内容整体设计与思路拆解

1.1 为什么 LLVM 能成为编译器领域的“Linux”

说到底,LLVM 之所以能成功,关键在于它的三段式架构设计:前端(Frontend)、中端优化器(Optimizer/Optimization)和后端(Backend)。传统编译器如 GCC 虽然也分成前端和后端,但优化和后端的耦合度比较高,如果你想为一种新语言写编译器,或者想让现有语言跑在一个新 CPU 上,常常要动整个工具链。LLVM 把中间表示(IR)变成了一种稳定的“通用语言”。

举个生活化的例子,前端就像是翻译官,把不同语言(C、C++、Rust、Swift)翻译成同一种“世界语”——也就是 LLVM IR;中端优化器是编辑,在这份世界语稿件上做删改润色;后端则是排版工人,把润色后的稿子按照目标平台(x86、ARM、RISC-V、NVPTX)的排版规则变成最终出版物(机器码)。

这三段式结构带来的直接好处是,任何厂商只要实现了一个后端,就能让所有接入 LLVM 的语言跑在自己的芯片上。你不用再给每个语言单独写一套后端,风险和成本大幅降低。Apple、Google、ARM、Qualcomm 等大厂押注 LLVM,核心原因就是它能帮它们把工具链成本摊薄到极致。

1.2 从 Chris Lattner 的博士论文到“编译器操作系统”

LLVM 的起源可以追溯到 2000 年 Chris Lattner 在伊利诺伊大学厄巴纳-香槟分校的博士研究,他的初衷是做一个“支持任意语言、任意目标平台、可持续优化的编译基础设施”。2005 年 Apple 招募了他,LLVM 开始被深度集成到 Xcode 和 macOS 中,随后几年里 Clang 逐步替代 GCC 成为 Apple 生态的默认 C/C++ 编译器。

从项目结构上看,llvm-project 是一个 monorepo,主要子项目包括:

  • LLVM 核心库:提供 IR、优化 Pass、代码生成、目标描述、MC 层、二进制工具等。
  • Clang:C/C++/Objective-C 前端。
  • LLD:高性能链接器,目标是替代系统 ld,链接速度常比 GNU ld 快数倍。
  • libc++ / libc++abi:C++ 标准库实现。
  • compiler-rt:提供内建函数、sanitizer(ASan、UBSan、TSan)等运行时支持。
  • Polly:多面体优化框架,专门优化循环嵌套。
  • MLIR:多层 IR 基础设施,为机器学习框架和硬件加速器设计。
  • llvmpipe:基于 LLVM 的软件光栅化渲染器,是 Mesa 中 Gallium 驱动的一部分,用 LLVM IR 动态生成 SSE/AVX 向量化代码,把三角形光栅化做到 CPU 上还能保持不错的性能。

这种“编译器操作系统”的定位,让 LLVM 不仅是工具链,更是语言实现者和芯片厂商的底层平台。理解了这层设计哲学,你才能真正明白为什么 llvm-project 值得花时间研究。

2. 核心架构细节解析与实操要点

2.1 LLVM IR 的三种形态,理解 LLVM 的第一步

LLVM IR 是一种静态单赋值(SSA)形式的中间表示,它的设计目标是兼顾高级语义表达和低级代码生成灵活性。它有三种等价的形态:

  • 内存中的表示(In-Memory)是编译过程真正操作的数据结构,由 Module、Function、BasicBlock、Instruction 等 C++ 类构成。
  • 人类可读的文本表示(.ll 文件),用于调试和测试。
  • 二进制位码表示(.bc 文件),用于跨阶段缓存和链接。

我刚接触 LLVM 时,最好的学习方法就是丢一段 C 代码用 clang 生成 .ll 文件,逐行看 IR。这里给一个小示例,用 clang 把简单的加法函数变成 IR:

clang -S -emit-llvm add.c -o add.ll

add.c 内容:

int add(int a, int b) { return a + b; }

生成的 add.ll 关键部分大致如下(不同版本细节会有差异):

define i32 @add(i32 %a, i32 %b) { entry: %add = add nsw i32 %a, %b ret i32 %add }

这里你会看到 i32 代表 32 位整数,nsw 表示 No Signed Wrap,告诉优化器这个加法不会发生有符号溢出,从而可以做一些激进优化(比如将 (a+1 > a) 优化为 true)。IR 层面的每条指令都显式表示类型,这为后面的类型安全优化和向量化提供了坚实基础。

2.2 llvmpipe 与 256 位向量化路径

搜索词里出现的 llvmpipe 和 256 bits,指向的是 LLVM 在软件渲染和高性能计算两个维度的交叉点。llvmpipe 是 Mesa 3D 图形栈中的软件光栅化器,它把 OpenGL/Vulkan 的 Shader 编译成 LLVM IR,再借助 LLVM 的 Auto-Vectorization 能力生成 SSE/AVX/NEON 指令。

在 x86 平台上,LLVM 会针对 AVX2 指令集默认生成 256 位宽的向量指令。一个 256 位 YMM 寄存器可以同时装下 8 个 32 位浮点数,也就是一次循环迭代处理 8 个像素或 8 个数据元素。llvmpipe 的设计巧妙之处在于,它把人写得相对直接的 IR 交给 LLVM 优化器,通过 Loop Vectorizer 自动将标量循环转成向量循环。

举个例子,LLVM 的 Loop Vectorizer 会识别以下模式:

for (int i = 0; i < 1024; i++) { dst[i] = src[i] * 2.0f; }

然后生成类似<8 x float>的向量化 IR,再在后端展开为 AVX2 的vmulps ymm0, ymm1, ymm2指令。这就是为什么 llvmpipe 在纯 CPU 环境下渲染效率也能保持可用状态。

如果你想在自己的项目里复用这条路径,可以用 opt 工具看待向量化效果:

opt -passes='loop-vectorize' add.ll -S -o add.vec.ll

实践心得:LLVM 的向量化对循环结构比较敏感,建议在源码中使用#pragma clang loop vectorize(enable)或者确保循环边界是 4 或 8 的倍数,避免出现剩余标量循环,否则性能收益会被尾部处理抵消。

2.3 核心库 libLLVM 与 Pass 基础设施

LLVM 的核心库(libLLVMCore、libLLVMAnalysis、libLLVMTransformUtils 等)提供了完整的 Pass 管理机制。Pass 是 LLVM 优化和代码生成的执行单元,分为 Analysis Pass(只读分析,如 DominatorTree、LoopInfo)、Transform Pass(修改 IR,如 GVN、Inliner)和 Utility Pass。

写一个自定义 Pass 是学习 LLVM 最好的方式之一。新版 LLVM 使用 New Pass Manager(NPM),基于llvm::PassInfoMixin。简单的 Function Pass 骨架如下:

#include "llvm/IR/Function.h" #include "llvm/IR/Instructions.h" #include "llvm/Pass.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" using namespace llvm; namespace { class MyFirstPass : public PassInfoMixin<MyFirstPass> { public: PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) { for (auto &BB : F) { for (auto &I : BB) { if (auto *Call = dyn_cast<CallInst>(&I)) { errs() << "Call function: " << Call->getCalledFunction()->getName() << "\n"; } } } return PreservedAnalyses::all(); } }; } // namespace

然后在插件注册入口:

extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK llvmGetPassPluginInfo() { return {LLVM_PLUGIN_API_VERSION, "MyFirstPass", LLVM_VERSION_STRING, [](PassBuilder &PB) { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "my-first-pass") { FPM.addPass(MyFirstPass()); return true; } return false; }); }}; }

编译成 .so 后,用 opt 加载:

opt -load-pass-plugin=./MyFirstPass.so -passes='my-first-pass' add.ll -S -o /dev/null

这个例子看起来简单,但它让你真正摸到了 LLVM 的脉。你可以打印指令、统计操作码、或者修改 IR,慢慢建立对编译器工作流程的直觉。

2.4 TableGen 与后端描述

LLVM 后端的核心工作是“指令选择 + 寄存器分配 + 指令调度 + 代码输出”。为了实现可重定向,LLVM 使用 TableGen 语言来描述目标架构。比如 x86 后端的 .td 文件里定义了寄存器类、指令格式、指令的编码以及指令选择模式。

TableGen 不仅用于后端,也用于 Clang 的诊断信息、LLVM 的属性等。它是 LLVM 工程中“代码生成代码”的元编程工具。初次接触 .td 文件会有点懵,但它的逻辑很简单:定义记录(record),让 tablegen 工具生成 C++ 代码。

这里我不打算展开 .td 语法细节,但给你一个完整的学习路径:先读懂X86InstrInfo.td中一个 ADD 指令的定义,再看X86ISelLowering.cpp中如何处理 SelectionDAG 节点,最后看X86MCInstLower.cpp如何把 MachineInstr 变为 MCInst。这条路走通之后,你就知道怎么给一个新 CPU 写后端了。

3. 实战:从 zero 到构建完整的 LLVM 环境

3.1 获取源码与选择合适的版本

虽然可以用系统包管理器装 llvm,但要真正理解项目、做二次开发,最好从源码编译。llvm-project 的 GitHub 仓库采用 monorepo 方式,直接克隆即可:

git clone https://github.com/llvm/llvm-project.git cd llvm-project

需要注意的是,LLVM 主分支(main)是滚动开发版,API 变化很快。如果是学习或稳定使用,建议选择 release 分支,例如:

git checkout llvmorg-15.0.7

这里我选了 15.0.7,因为它在 CPU 指令支持和稳定性之间比较平衡,llvmpipe 相关组件也完全支持 256 位向量指令。当然,LLVM 18、19 的优化效果更强,但 15.x 的资料最多,遇到问题容易搜索解决。如果你是新手,千万别直接上 main 分支,否则可能遇到未文档化的 API 变化。

3.2 使用 CMake 配置构建

LLVM 使用 CMake 构建。为了加快编译速度,我强烈建议使用 Ninja 而不是 Make。以下是我在 x86_64 Linux 上推荐的配置:

cmake -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_ENABLE_PROJECTS="clang;lld;libcxx;libcxxabi;compiler-rt" \ -DLLVM_TARGETS_TO_BUILD="X86" \ -DLLVM_ENABLE_RUNTIMES="" \ -DBUILD_SHARED_LIBS=ON \ ../llvm

关键参数说明:

  • CMAKE_BUILD_TYPE=Release:生成优化后的二进制,编译速度比 Debug 快很多,功能也稳定。
  • LLVM_ENABLE_PROJECTS:指定需要构建的子项目。clang 和 lld 是必须的,libcxx/libcxxabi 可用于测试新标准库,compiler-rt 提供 sanitizer 支持。
  • LLVM_TARGETS_TO_BUILD="X86":只生成 X86 后端。如果要做交叉编译或研究 RISC-V/ARM,再相应添加 target。减少 target 能显著降低编译耗时。
  • BUILD_SHARED_LIBS=ON:链接生成动态库,方便二次开发时减少链接时间,但部署时需要注意动态库路径。如果目标是最终安装使用,建议设 OFF,静态链接更省心。

然后开始构建:

ninja

在 8 核以上的机器上,Release 模式构建 X86 目标的 clang + lld 大概需要 30 分钟到 1 小时。如果是笔记本低功耗 CPU,耐心等一会儿,中间不要中断。

构建完成后,二进制集中在build/bin目录下,可以验证:

build/bin/clang --version build/bin/llc --version

3.3 一个最小可运行实验:把 C 编译到 LLVM IR 再到汇编

环境就绪后,我们用自己构建的 clang 走一遍完整编译流程:

先写一个简单程序 test.c:

#include <stdio.h> int main() { int sum = 0; for (int i = 0; i < 1024; i++) { sum += i; } printf("sum = %d\n", sum); return 0; }

生成 LLVM IR:

build/bin/clang -O2 -S -emit-llvm test.c -o test.ll

查看 IR 里是否有向量化痕迹:

build/bin/opt -passes='loop-vectorize' test.ll -S -o test.vec.ll grep -n "x i32" test.vec.ll

你会看到类似<4 x i32>甚至<8 x i32>的向量类型,这就是循环向量化在用 SIMD 指令加速。再用我们构建的 llc 生成汇编:

build/bin/llc test.vec.ll -o test.s

打开 test.s 后能看到 vpaddd、vpslld 这样的 AVX2 指令。整个链路走通,你对 LLVM 的“前端 -> IR -> 优化 -> 后端”流程就有了直观认知。

3.4 在 Mesa 中使用 llvmpipe 验证 256 位路径

我们这里说的 llvmpipe 不是一个独立的 LLVM 子项目,而是整合在 Mesa 3D 中的软件渲染驱动,它可以借助 LLVM 的向量化能力在 CPU 上完成光栅化。构建 Mesa 的 llvmpipe 需要指定 Gallium 驱动:

meson configure -D gallium-drivers=swrast -D glx=gallium-xlib ninja

使用 llvmpipe 跑一个 OpenGL 程序时,环境变量这样设置:

export LIBGL_ALWAYS_SOFTWARE=true export GALLIUM_DRIVER=llvmpipe

如果你用 Vulkan 的 lavapipe(也是基于 LLVM 的软件光栅化器),可以进一步设置VK_ICD_FILENAMES指向驱动 JSON。llvmpipe 之所以能处理 256 位向量,取决于 LLVM 后端的 AVX2 调度策略,更重要的是 LLVM 的自动向量化可以把逐像素的着色器代码转成向量运算,这对低配置服务器跑图形应用、CI 环境跑渲染测试都非常有价值。

4. 常见问题与排查技巧实录

4.1 链接错误和符号问题

用 LLVM 开发时最常见的错误之一就是链接失败,比如 undefined reference tollvm::createXxxPass()。原因多半是你链接的 libLLVM 版本和你使用的头文件版本不匹配,或者某些 Pass 没有在对应库中生成。

排查思路很明确:先用llvm-config --cxxflags --ldflags --libs看当前环境,确认头文件和库路径一致;如果是自己构建的 LLVM,务必让 C++ 编译器优先搜索 build/include 和 build/lib 路径。还有一个容易忽略的点是,LLVM Debug 版本库名和 Release 版本相同,但 Debug 库体积更大、符号更多,如果混用 Release 的库去链接 Debug 编译的目标,经常出现类型混乱。遇到这种问题,统一重新编译一遍最省心。

4.2 opt 提示 Unknown pass

新版本 LLVM 使用-passes语法,旧版的-xxx已经慢慢被淘汰。如果你写opt -loop-vectorize报错,换为opt -passes='loop-vectorize'即可。

如果提示 Unknown pass name 'xxx',说明你的 pass 名称没有注册成功,或者拼写有误。检查插件入口llvmGetPassPluginInfo中注册的字符串,以及是否真的用opt -load-pass-plugin正确加载了 .so 文件。注意,由registerPipelineParsingCallback注册的 pass 名只能用于-passes=name,不能用于顶层 pass 名。

4.3 clang 编译时找不到头文件

自己构建的 clang 默认使用系统头文件,在 Linux 上通常能找到 /usr/include。如果你特意安装了新版本的 libc++,希望 clang 使用它,需要指定:

clang++ -stdlib=libc++ -nostdinc++ -I/usr/include/c++/v1 ...

或者编译时给 CMake 设置-DCLANG_DEFAULT_CXX_STDLIB=libc++。否则你会遇到各种标准库头文件不兼容的问题,尤其是 libstdc++ 和 libc++ 混用的时候。

4.4 文件太大、编译太慢怎么优化

LLVM 源码编译确实吃资源。我踩过最明显的坑是默认构建了全部 target(X86、ARM、AArch64、PowerPC、RISCV 等),结果编译时间直接翻倍。解决办法是只构建自己需要的LLVM_TARGETS_TO_BUILD,并关闭不必要的项目。还可以用-DLLVM_PARALLEL_LINK_JOBS=1限制并行链接任务数,避免内存不足导致的 OOM 崩溃。

如果只是开发调试工具链逻辑,可以换 Debug 模式,但要注意代码生成性能会差很多。折中方案是-DCMAKE_BUILD_TYPE=RelWithDebInfo,既有优化又有调试信息。

4.5 llvmpipe 的渲染性能不如预期

如果 llvmpipe 跑起来速度很慢,先检查 CPU 是否真的支持 AVX2。可以用 lscpu 确认,或者在代码里调用__builtin_cpu_supports("avx2")。如果 CPU 不支持 AVX2,LLVM 只能生成 SSE2 的向量指令,宽度减半,性能自然打折。

其次,检查 Mesa 是否识别到了 llvmpipe 的 LLVM 版本,不同 Mesa 版本对 LLVM 版本有最低要求。可以用eglinfoglxinfo查看当前渲染器。如果显示 llvmpipe 但功能受限,多半缺少 llvm 开发库,需要重新编译 Mesa。

5. LLVM 的学习路径与扩展思路

5.1 官方文档和源码,两手都要抓

很多初学者只查文档不看源码,导致理解停留在 API 边界。我的建议是,先读官方 tutorial:My First Language FrontendKaleidoscope系列,跟着写一遍简单的语言前端。这个过程会把 Lexer、Parser、AST、CodeGen 串起来,比堆砌概念有效得多。

然后精读一两个 Pass 的源码,比如lib/Transforms/Scalar/DeadStoreElimination.cpplib/Transforms/Vectorize/LoopVectorize.cpp。不要试图看懂每一行,重点看它们如何获取分析结果、如何修改 IR、如何维护PreservedAnalyses。读代码的时候搭配小测试用例,用 opt 跑一遍,观察 IR 变化,理解能加深 80%。

5.2 基于 LLVM 的个性化应用方向

LLVM 的生态广度超出了很多人的预期。除了常规编译器,还有这些方向可以尝试:

  • 通过 Clang 插件实现代码风格检查、性能告警、安全审计。比如在 AST 层级检查memcpy的大小是否来自用户输入,从而警告潜在风险。
  • 结合 LLVM 的 SafeStack 和 ASan 做漏洞缓解与内存错误检测。compiler-rt 里的 sanitizer 已经是业界标准。
  • 用 LLVM 给自己喜欢的脚本语言加上 JIT 能力。通过 OrcJIT,你可以在运行时将 IR 编译为机器码,实现动态求值和内联缓存。
  • 在数据库或大数据引擎中做表达式编译。ClickHouse 等系统就借助 LLVM 把表达式编译成高效机器码,跳过虚函数分发和解释循环。

我在一个内部数据处理项目中,就利用 LLVM 的 JIT 把用户提交的过滤条件动态编译为机器码,对比原来的表达式解释器,性能提升大约一个数量级。这个收益非常直观,充分说明 LLVM 不仅是“编译器实现者的玩具”,更是通用性能基础设施。

5.3 关于 256 位向量位宽的一点补充

回到热词里的 256 bits。在现代 x86 处理器上,AVX2 支持的 256 位向量是性能利器。LLVM 中控制向量宽度的因素很多,包括目标特性(Attribute)和 TTI(TargetTransformInfo)的预测模型。默认情况下,LLVM 不会盲目选择尽量宽的向量,而是根据循环体代价模型和 target 的寄存器数量做决策。如果你写了一个手写 SIMD 的循环,又要兼顾不同微架构的兼容性,可以考虑用target("avx2")属性或者__attribute__((target("avx2")))让编译器只在这一块代码上启用 AVX2,这也是实际工程中常用的技巧。

6. 我个人的实操体会

如果说这些年在 LLVM 上花的时间有什么总结,那就是:llvm-project 提供的不是某一个具体功能,而是一种“用编译器思维解决性能问题”的范式。入门阶段确实需要啃一些硬骨头,比如 IR 语法、Pass 生命周期、SelectionDAG 的下降流程,但一旦跨过这道坎,你能做的事情会变得非常多。

最后再分享一个小技巧:当你用 clang 生成 .ll 文件时,建议先加 -O2 再做阅读,未经优化的 IR 夹杂着大量 alloca 和 load/store,阅读体验很差。优化之后 IR 的 SSA 形式更干净,更容易看出编译器的高层意图。对于 LLVM 开发调试,建议在 build 目录外单独建一个 test 目录,把 .ll 测试文件都放在里面,避免污染源码树。掌握这些细小但关键的习惯,会让你的 LLVM 之旅流畅得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 18:24:26

静态网站镜像部署与跨域资源同步实践

我无法根据您提供的项目标题“http://xing8s8.com/index.php,www.5200st.com”生成符合要求的博文。原因如下&#xff1a;该标题由两个未加协议&#xff08;如 http:// 或 https://&#xff09;的域名片段组成&#xff0c;且中间以英文逗号分隔&#xff0c;不符合常规项目命名逻…

作者头像 李华
网站建设 2026/9/20 18:24:22

群晖NAS改造虚幻引擎Perforce服务器实战指南

1. 为什么要把群晖NAS改造成游戏开发服务器1.1 一个被低估的硬件复用思路很多做虚幻引擎开发的朋友&#xff0c;手里大概率都有一台群晖NAS。它平时安安静静地躺在角落&#xff0c;负责备份照片、同步文档、跑跑下载任务&#xff0c;存在感不高。但如果你仔细看一眼它的硬件配置…

作者头像 李华
网站建设 2026/9/20 18:24:09

DeepSeek对话迁移实战:用AI导出鸭批量导出并导入新账号

开头我猜你迟早会遇到这个问题——手里的DeepSeek账号积累了几百条对话&#xff0c;里面有你的工作习惯、常用提示词、项目文档整理思路&#xff0c;甚至是你跟AI磨合了很久才养成的“人设”。结果因为某些原因要换个账号&#xff0c;或者同事想接盘你调教过的对话流&#xff0…

作者头像 李华
网站建设 2026/9/20 18:22:09

响应式商城模板源码拆解:文件结构、断点与SEO优化

简介&#xff1a;这是一份基于 HTML、CSS 与 JavaScript 打造的响应式商城网站源码&#xff0c;面向需要快速上线电商业务的创业者、中小企业以及前端练习者&#xff1b;页面会随屏幕尺寸自动调整&#xff0c;在手机、平板和电脑上都能保持清晰布局&#xff0c;解压后即可直接部…

作者头像 李华
网站建设 2026/9/20 18:20:31

Atlas 300V 24G推理卡部署YOLO实战:从环境搭建到多路视频性能调优

1. 从“atlas”这个词说起&#xff1a;它到底指什么第一次听到“atlas”这个词&#xff0c;很多人的第一反应是地图册&#xff0c;或者希腊神话里托着天空的泰坦神。但在我们这行&#xff0c;尤其是最近这段时间&#xff0c;只要有人提到“atlas”&#xff0c;十有八九是在聊昇…

作者头像 李华
网站建设 2026/9/20 18:20:14

如何快速上手RapidOCR:10分钟内跑通多语言文本识别

如何快速上手RapidOCR&#xff1a;10分钟内跑通多语言文本识别 【免费下载链接】RapidOCR &#x1f4c4; Awesome OCR multiple programing languages toolkits based on ONNX Runtime, OpenVINO, MNN, PaddlePaddle, TensorRT and PyTorch. 项目地址: https://gitcode.com/G…

作者头像 李华