1.Metal 动态库 MTLDynamicLibrary 使用指南
2.LLVM IR 指南
3.Clang概述
4.MLIR多层中间表示——用MLIR构建编译器(下)
5.OpenAI/Triton MLIR 第零章: 源码编译
6.V8 编译浅谈
Metal 动态库 MTLDynamicLibrary 使用指南
本文旨在解析 Metal 动态库 MTLDynamicLibrary 的转源使用方法,以帮助读者清晰理解该特性的转源应用。动态库特性目前主要服务于 Compute Pipeline 的转源使用,其中包含可链接的转源导出函数,但无法直接通过 API 获取函数对象。转源动态库类似于传统的转源html网页设计源码解释"动态链接库"。
查询动态库兼容性时,转源需检查 MTLDevice 上的转源 supportsDynamicLibraries 属性。尽管官方尚未提供设备支持列表,转源A 及后续芯片已确认支持。转源
MTLLibrary 并非动态库,转源但理解它有助于掌握动态库概念。转源MTLLibrary 是转源在 Xcode 工程中,用于存储 Metal shader 源码的转源文件,工程构建时这些文件会被编译成 Apple IR(Intermediate Representation),转源然后保存为 .air 文件。多个包含 .air 文件的库可以组合使用,生成名为 MyKernels.metallib 的库。
在 Metal 的编程接口中,MTLLibrary 对应包含中间语言可执行函数库的库。通过函数如 newLibraryWithFile:error: 或 newLibraryWithURL:error: 加载 .metallib 文件至内存中,查询库中 public 函数。使用 newFunctionWith* 方法加载 MTLLibrary 中的 MTLFunction。创建 Compute Pipeline State 所需填写的 MTLComputePipelineDescriptor 中的关键参数 computeFunction 即可通过这种方式获取。
动态库 MTLDynamicLibrary 包含可用于链接的导出函数,这些函数不应是 kernel 函数,因为无法获取 MTLFunction 且不能被管线直接使用。然而,kernel 函数可以调用动态库中的函数,动态库中的函数同样可以调用其他动态库中的函数。利用命令行生成 utility1.air 和 utility2.air 文件,然后生成动态库 libUtility.metallib。通过 newDynamicLibraryWithURL:error: 加载动态库,得到 MTLDynamicLibrary 对象,用于链接其中的函数。
运行时链接涉及在调用 newLibraryWithSource:options:error: 或 newLibraryWithSource:options:completionHandler: 时,将动态库中的函数定义在源码中,并通过 extern 声明,告知编译器函数的位置。MTLCompileOption 的 libraries 属性用于指定包含实现的动态库。成功生成管线后,动态库中的函数可通过 k() 函数调用。
离线链接中,生成包含 kernel 函数 k() 的普通库,并调用动态库 libUtility.metallib 中的函数 foo()。通过 -L 和 -l 指令指定库的路径和库名,生成包含 kernel 和动态库的函数库 MyKernels.metallib。当 MyKernels.metallib 加载时,Metal 自动加载 libUtility.metallib,排课 源码从而实现调用 foo() 函数。
动态库加载机制涉及 install_name 参数,该参数指定动态库的安装位置。MyKernels.metallib 加载时,根据安装名称找到 libUtility.metallib。使用 @executable_path 和 @loader_path 可以灵活指定路径,确保动态库在不同安装位置仍能正确加载。
运行时生成动态库可通过命令行或代码实现,涉及编译 .metal 文件为 .air,然后链接生成 .metallib。动态库 MTLDynamicLibrary 包含 AIR 代码和当前架构的机器码,可用于保存和重复使用。serializeToURL:error: 可将动态库保存,包含 MTLLibrary 和当前架构机器码。注意,serialize 下来的动态库不是多架构的,只会加载当前架构的机器码。
金属-lipo 工具可生成包含不同架构机器码的 fat 文件,用于创建 universal binaries。在 MTLComputePipelineState 创建时,使用 insertLibraries 属性设置动态库查找优先级,加速 AIR 到机器码的转换过程。
时间有限,文中可能存在疏漏,请读者指出并提供反馈。
LLVM IR 指南
LLVM IR是一种通用的程序表示形式,编程语言编译器通过前端生成并经过一系列分析和转换(称为pass)生成优化后的IR。这种表示允许跨语言和硬件的隔离,便于优化,并支持在不同阶段进行优化,比如runtime时,IR会被保留并在发现可优化点时进行重新编译。
LLVM IR有三种形式:内存中的ir、硬盘上的bitcode文件(ir.bc)和供人阅读的文本形式(ir.ll)。在编译过程中,ir的内存格式用于全阶段优化,特别是在需要runtime优化时。
LLVM工具链包含了编译LLVM源码所需的工具,通常在编译目录的bin目录下。要生成IR的基本结构,可以使用clang命令。IR的基本结构由module、function、basicblock和instruction组成,每个模块可能包含多个函数,每个函数由多个基本块构成,体现了控制流的执行逻辑。
LLVM的小区 源码Pass Manager执行分析和转换,包括analysis pass和transform pass。新旧Pass Manager在结构和命令行使用上有所不同。Pass的执行顺序通常从module开始,逐步深入到function、loop等层次,涉及到如别名分析、MemorySSA和Loop-Invariant-code-motion等优化策略。
例如,别名分析分析变量的load/store操作产生的别名,通过构建语句间的约束和迭代生成alias,提供函数间的内存依赖信息。MemorySSA则在此基础上,提供内存依赖查询,便于IR的分析和transform pass。
Link-time优化是LLVM的另一大优势,它允许在链接阶段对整个程序的IR进行优化,利用内存中的IR进行更深入的分析和改进。这比传统编译过程中的优化更为灵活和高效。
调试和命令行使用方面,LLVM提供了丰富的工具和技巧,帮助开发者在编译过程中进行调试和优化,比如MakeFile中的关键语句和调试技巧。
Clang概述
LLVM项目的一个子项目,基于LLVM架构的C/C++/Objective-C编译器前端
Clang将C/C++/Object-C源码转换成LLVM IR,指令选择将LLVM IR转换成Selection DAG node(SDNode),指令调度将SDNode转换成MachineInstr,代码输出将MachineInstr转换成MCInst。
Clang的两层含义:自动调用后端程序包括预处理(preprocessing),编译(compiling),链接(linking)并生成可执行程序,将C/C++/Object-C源码编译成LLVM IR。
Compiler Driver本质是调度管理程序,Clang Driver划分成五个阶段:Parse、Pipeline、Bind、Translation、Execute。其执行过程大致如下:Driver::ExecuteCompilation -> Compilation::ExecuteJobs -> Compilation::ExecuteCommand-> Command::Execute -> llvm::sys::ExecuteAndWait。其执行过程调用相关操作系统,执行其系统相关的执行程序,并等待执行过程完成。
Clang的核心组件包括Tokens、抽象语法树(AST)、语法分析、递归下降、Precedence Climbing算法等。Tokens是通过词法分析产生的单词记号,词法分析在预处理过程中初始化。画图 源码抽象语法树(AST)是语法分析的输出,表示源代码语法结构的抽象表示。递归下降解析中缀表达式语法一般有两个问题,Precedence Climbing算法的主要思想是将表达式视为一堆嵌套的子表达式,其中每个子表达式都具有其包含的运算符的最低优先级。
Clang的入口位于tools/driver/driver.cpp中的int main(int Argc, const char **Argv)函数,如果程序第一个参数是-cc1则直接执行函数static int ExecuteCC1Tool(SmallVectorImpl &ArgV),此时为前端模式,直接执行cc1_main或cc1as_mian;执行完毕后程序退出;如果不是-cc1,则进行相关命令解释,生成相容的命令行,由int Driver::ExecuteCompilation(Compilation &C,SmallVectorImpl> &FailingCommands)执行相容的命令行。
Clang通过Action完成具体的操作,CompilerInstance是一个编译器实例,综合了一个 Compiler 需要的 objects,如 Preprocessor,ASTContext,DiagnosticsEngine,TargetInfo 等。CompilerInvocation为编译器执行提供各种参数,它综合了TargetOptions、DiagnosticOptions、HeaderSearchOptions、CodeGenOptions、DependencyOutputOptions、FileSystemOptions、PreprocessorOutputOptions等各种参数。FrontendAction::ExecuteAction()是一个纯虚函数,通过继承这个方法来实现具体的Front End Action,Clang还提供了几个继承子类 ASTFrontendAction,PluginASTAction,PreprocessorFrontendAction。 Action及其派生的Action定义如下,大多数Front end Action都继承ASTFrontendAction,每一个ASTFrontendAction都会创建一个或者多个ASTConsumer,ASTConsumer也是一个纯虚类,通过继承ASTConsumer去实现特定的AST Consumer。
ASTConsumer中可以重载下面两个函数:HandleTopLevelDecl()解析顶级的声明(像全局变量,函数定义等)的时候被调用;HandleTranslationUnit()在整个文件都解析完后会被调用。大概流程如下:初始化CompilerInstance之后,调用其成员函数ExcutionAction, ExcutionAction会间接依次调用FrontendAction的6个成员函数(直接调用的是FrontendAction的三个public 接口,BeginSourceFile,Execute,EndSourceFile),而FrontendAction的ExecuteAction会最终调用语法分析函数ParseAST(未强制要求ParseAST放入ExcuteAction,但ASTFrontendAction如此)。 ParseAST在分析过程中,又会插入ASTConsumer的矿工 源码多个句柄(用得最多是HandleTopLevelDecl和 HandleTranslationUnit)。
Clang的Parser是通过void clang::ParseAST(Sema &S, bool PrintStats, bool SkipFunctionBodies)执行的,ParseAST()函数对个top level decleration(包括变量和函数)调用parser解析得到一颗正确的语法树。Clang使用递归下降(recursive-decent)的语法分析,具体来说,采用的是基于中缀表达式分析的precedence climbing算法。
Clang的Parser(lib/Parse和lib/AST)是通过void clang::ParseAST(Sema &S, bool PrintStats, bool SkipFunctionBodies)执行的,ParseAST()函数对个top level decleration(包括变量和函数)调用parser解析得到一颗正确的语法树。
MLIR多层中间表示——用MLIR构建编译器(下)
在构建编译器的过程中,将方言翻译到LLVM的策略为走向代码生成的关键步骤。此过程涉及到将源代码的表示转换为执行代码的中间表示,以便进一步优化并最终生成可执行文件。
在MLIR中,实现这一目标的方法是利用LLVM方言,这是一种预定义的模式集合,允许从源语言到目标语言的转换。通过连接现有的转换框架和组件,可以建立一个完整的端到端系统,用于从源语言编译至可执行代码。
系统的核心构建包括定义转换目标、运算转换和类型转换。转换目标说明了哪些运算是合法的以及在何种情况下,运算转换负责将非法运算转化为合法形式,而类型转换则规范了非法类型如何被合法化。在方言转换中,有两种模式可供选择:部分模式允许不是所有输入运算都必须对目标合法化,而完整模式则要求所有输入运算都必须对目标合法化。
通过定义转换目标和收集运算转换模式,构建了一个可以将Toy语言转换为可执行代码的系统。在过程中,需要处理那些现有运算可能无法合法化的特殊情况,允许在不了解整个IR的情况下转换已知非法运算的子集。
当转换系统成功地应用在函数上时,它会尝试将非法运算转换为合法运算,如果任何非法运算未能成功转换,则转换过程会失败。在实际应用中,示例代码展示了如何从Toy语言转换到Affine语言,并执行了简单的计算操作,最终输出结果并返回。
在完成方言转换后,可以将MLIR LLVM方言导出为LLVM IR,实现从LLVM方言到LLVM IR的映射。此过程涉及将MLIR模块转换为LLVM IR,以便进一步在LLVM环境中执行和优化。
整个过程展示了从源语言到可执行代码的高效转换路径,利用MLIR和LLVM的集成能力,构建出强大的编译器系统。此系统不仅能够实现语言之间的转换,还能在转换过程中进行代码优化,最终生成高性能的执行代码。
OpenAI/Triton MLIR 第零章: 源码编译
本文旨在深入探讨开源AI项目OpenAI Triton MLIR,着重介绍Triton作为编程语言与编译器在GPU加速计算领域的应用与优化。Triton为用户提供了一种全新的方式,通过将其后端接入LLVM IR,利用NVPTX生成GPU代码,进而提升计算效率。相较于传统CUDA编程,Triton无需依赖NVIDIA的nvcc编译器,直接生成可运行的机器代码,体现出其在深度学习与数据科学领域的高性能计算潜力。Triton不仅支持NVIDIA GPU,还计划扩展至AMD与Intel GPU,其设计基于MLIR框架,通过Dialect支持多样化后端。本文将从源码编译角度出发,逐步解析Triton的设计理念与优化策略,为研究编译技术和系统优化的工程师提供宝贵资源。
首先,需要访问Triton的官方网站,克隆其官方代码库,以便后续操作。构建过程涉及两个重要依赖:LLVM与pybind。LLVM作为Triton的核心后端,通过将高级Python代码逐步转换至LLVM IR,最终生成GPU可运行代码,体现了其在计算优化领域的优势。pybind组件则用于封装C++/CUDA或汇编代码,实现Python DSL与高性能组件的无缝集成。
接下来,将LLVM与pybind分别编译安装,通过手动配置指定路径,确保编译过程顺利进行。LLVM的安装对于基于Triton进行二次开发的工程师和研究人员至关重要,因为它为Triton提供了强大的计算基础。在特定的commit ID下编译Triton,确保与后续版本兼容。
在编译过程中,配置pybind同样至关重要,它允许用户通过Python API调用高性能组件,实现自动化生成高性能算子。完成编译后,生成的.so文件(libtriton.so)为后续Triton的Python接口提供了支持。
将libtriton.so移动至triton/python/triton/_C目录下,确保Python路径正确配置,实现无缝导入与调用。通过简单的import triton命令,即可开启Triton的开发之旅。验证Triton性能,可以选择tutorials目录下的示例代码,如-matrix-multiplication.py,通过运行该脚本,观察Triton在GPU上的性能表现。
Triton在NVGPU上的成熟映射路线,从抽象的Python DSL到贴近GPU层面的IR,最终生成高效机器代码,体现了其在高性能计算领域的优越性。Triton未来的发展蓝图将支持更多前端语言,对接不同硬件厂商的硬件,实现高效映射,满足多样化计算需求。
V8 编译浅谈
V8 编译原理详解 本文旨在介绍 JavaScript 在 V8 编译器中的解析过程,帮助读者理解 JavaScript 如何在 V8 中高效运行。V8 作为 Chrome 浏览器和 Node.js 的核心引擎,采用了混合动态编译技术,通过编译器组件如Ignition和TurboFan来提升性能。编译器与解释器
首先,区分解释器和编译器:解释器如Perl直接执行源代码,而编译器如Java,先将源码转化为机器可执行的中间表示(IR),通过多轮迭代优化。编译器的关键组件包括IR,用于优化源码并生成高效目标代码。JIT编译与混合动态编译
早期,Web前端对启动速度有高要求,因此采用解释器。为提高运行时性能,V8 引入JIT编译技术,结合混合编译,实时优化代码。这种编译框架解决了JavaScript性能问题,让代码运行更快。V8 编译原理详解
1. Ignition解释器:将抽象语法树(AST)转化为字节码,并利用类型反馈优化热点代码,生成Feedback Vector,指示优化方向。 2. TurboFan优化编译器:利用JIT技术,根据运行时信息生成优化后的机器代码,通过反馈向量进行动态编译优化和去优化。运行时表现
通过D8调试工具,可以查看代码的编译和运行信息,如AST、字节码、优化和去优化过程。通过分析,加深对V8编译过程的理解。 通过实战操作,如生成AST、字节码和检查运行时反馈,可以直观地体验V8的编译与优化策略。 要深入探究,可以尝试使用D8工具和V8的Native API,如%DebugPrint,探索更多细节。AI编译器技术剖析(二)-传统编译器
AI技术的广泛应用中,智能家居和自动驾驶都依赖于NLP和计算机视觉等AI模型,这些模型部署在云、专用设备和物联网设备中。在将AI模型从研发到实际应用的过程中,编译器的作用日益凸显,特别是在处理非标准算子的模型部署上。AI编译器的兴起预示着未来十年的快速发展。
AI编译器技术建立在传统编译器的基础之上。它首先在IR层面优化模型,然后通过lowering将高级IR转换为传统编译器理解的低级IR,最后依赖传统编译器生成机器码。要理解AI编译器,先要掌握传统编译器的基本原理,包括其预处理、编译和链接流程,以及前端、优化器和后端的分工。
传统编译器的核心是源代码到机器码的转换过程。它通常由预处理器、编译器(分前端、优化器和后端)和链接器组成。编译器负责将高级语言转换为机器代码,而解释器则在运行时进行转换。AOT和JIT编译的区别在于执行时间:AOT在编译前完成,JIT则在运行时动态优化。
主流编译器如GCC,其源代码庞大且复杂,包含语言相关的代码、通用代码和根据机器描述生成的代码。GCC的流程包括词法分析、语法分析、优化,以及目标代码生成。而LLVM提供了一种模块化的编译器框架,支持自定义前端和后端,比如Apple的Clang,它直接支持C++等语言并转化为LLVM IR。
编译器优化是提升性能的关键,包括常量传播、常量折叠、复写传播等。它们通过消除冗余计算和改进代码结构来提高执行效率。例如,通过公共子表达式消除,可以避免不必要的计算;通过函数调用优化,如尾递归优化,减少函数调用的开销。
总的来说,本文概述了传统编译器的基础,以及AI编译器如何在其基础上发展,展示了编译器的架构、优化策略和不同编译器工具的特性,为理解AI编译器技术提供了基础。
TVM 自底向上(二):TIR 的概念和编译原理
在深入探讨TVM中的编译过程与中间表示(IR)时,特别是TIR(Tensor IR)的概念及其编译原理,本节将重点聚焦于如何将神经网络模型转化为硬件源代码,以帮助读者更深入地理解这一复杂过程,并找到学习TVM的乐趣。
TIR是TVM中最接近目标硬件的数据结构,无论前端模型(如pytorch、tensorflow或ONNX)经过了哪些转换,最终在被编译为目标后端代码前,都需要被转化为TIR。TVM的编译流程中,TIR起着核心作用,其位置如图所示。
在TIR的实现中,抽象语法树(AST)扮演着关键角色。AST是一种通用的数据结构,用于表示任何编程语言的语法结构。它由节点组成,每个节点代表一种语言元素,如变量、函数调用或控制结构。在TIR中,AST为编译为不同硬件(如C++、CUDA、LLVM IR等)的代码提供了一个通用的结构。
通过将AST转换为源代码(CodeGen过程),TIR能够解决神经网络推理计算中遇到的两个主要问题:首先,它能够表示深度学习算子(如卷积、池化、ReLU)和控制结构(如min、max、if-else),这些算子和控制结构都基于基本的数学运算。其次,TIR的通用性使得加速逻辑可以被抽象化并应用于各种硬件架构,从而实现跨平台的加速。
TVM中的关键概念包括:IRModule、PrimFunc和CodeGen。IRModule是TVM中最小的编译单元,用于封装待编译的TIR和其他中间表示。PrimFunc封装了完整的AST,作为IRModule的API,对应生成.so库的函数入口。CodeGen负责将AST转换为目标硬件的源代码,本质上是一个树遍历迭代器。
TVMScript提供了一种简化TIR AST开发的方法,它利用Python AST(Python的语法树实现,如Cython使用),允许直接使用Python语法编写AST,从而简化了TIR的开发流程。TVMScript还支持双向转换,即可以从TIR AST生成TVMScript,也可以从TVMScript解析回TIR AST。
通过调用tvm.build函数,可以将IRModule编译为目标代码并运行,该过程根据所选的目标(如CPU、GPU或LLVM IR)选择适当的CodeGen。对于不同的目标,CodeGen过程涉及将TIR AST转换为目标硬件的源代码,然后使用相应的编译器生成可执行文件。例如,对于C++目标,CodeGen过程包括TIR到C++源代码的转换,而CUDA目标则涉及TIR到CUDA源代码的转换。
最后,本节概述了使用TVMScript编写TIR AST和调用适当CodeGen编译为源代码的完整流程,并强调了其他相关章节的内容。通过了解这些概念和原理,读者能够更深入地理解TVM编译过程的内在机制,从而为探索和应用TVM提供坚实的基础。