共计 4888 个字符,预计需要花费 13 分钟才能阅读完成。
关注并星标腾讯云开发者
每周 1 | 鹅厂工程师带你审判技术
第 1 期 | 李志瑞:AI 届新语言 Mojo 要火?
前段时间 Modular 公布了一个新语言 Mojo,这语言不止官网放了微小的 emoji,而且它的标准文件后缀一个是「.mojo」另一个是「.」,一副立马要火的样子呢。
说实话,这个用 emoji 做后缀名的操作其实挺无聊,也有点败好感,但如果说这个语言能在齐全兼容 Python 的根底上大幅提高执行效率,并且作者是 LLVM 发起人 Chris Lattner,是不是忽然又有趣味持续理解它了呢?
Mojo 被设计为 Python 语言的超集,并减少了许多个性,包含:
▶︎ Progressive types: 能利用类型信息取得更好性能和动态查看,但又不强制要求写类型。
▶︎ Zero cost abstractions: C++ 的外围设计准则,可能防止用户为了性能放弃正当设计的代码。
▶︎ Ownership + borrow checker: Rust 语言的安全性起源,在编译期防止许多谬误的产生。
▶︎ The full power of MLIR: 原生反对对 MLIR 的间接拜访,可能从底层扩大零碎。
在 Mojo 这个语言的介绍中重复提到 AI,官网也说它是「a new programming language for all AI developers」。那么为什么 AI 开发须要一个新语言呢?首先,咱们晓得在 AI 届具备统治位置的语言就是 Python,Python 是一个语法简略清晰,容易上手,且灵便度很高的语言,深受宽广程序员青睐,XKCD 上有就这么一幅漫画:
当然,受人青睐的语言有很多,Python 成为 AI 届的统治语言除了自身易用之外,也有惯性的因素。因为 Python 上机器学习相干的库多,因而机器学习从业者用的就多,这又反过来令新的机器学习相干库优先为 Python 提供接口,进一步增强了其统治位置。因而,为了逐渐浸透这个用户群,Mojo 兼容 Python 是很正确的一个抉择。Mojo 不仅承诺语法是 Python 的超集,并且它还能间接调用 Python 的库,这意味着 Mojo 不须要从零开始构建本人的生态,自身就能够用上凋敝的 Python 生态了。
尽管 Python 很好,但它有一个家喻户晓的问题,那就是太慢了。而机器学习自身又须要沉重的计算,因而 Python 生态中大量库的底层其实都是用高性能的语言(如 C/C++)进行实现,而后再提供一个 Python 接口供用户调用,典型的如 numpy 这种数学库。在这种状况下,Python 事实上是被作为一个胶水语言来应用,这造成了开发的碎片化,如果一个用户只是简略调一下库那还好说,但一旦到了工业界,开发过程中不可避免地就要波及一些底层库的批改,甚至间接换语言来实现同样的性能以进步性能,这种割裂不止减少了开发成本和精神负担,而且思考到泛滥善于 C/C++ 语言的开发者也并不是 AI 领域专家,这种开发人员能力的不适配也对整个 AI 生态的倒退造成了肯定妨碍。
因而,Mojo 的目标就是要在 Python 生态的根底上,让用户能用一个语言,从应用易用的接口,到开发简单的库,再到实现底层黑科技,对立试验和生产环境所用的语言。为了实现这个目标,Mojo 扩大了 Python 语法,反对了紧凑的内存布局,并引入了一些古代的语言个性(例如 Rust 的安全性查看),使得这个语言可能渐进式地在 AI 界立足。说起来 Chris Lattner 在这方面能够算是经验丰富了,不论是在 gcc/msvc 的统治下实现 clang,还是在 objective-c 的统治下为苹果实现 swift,都是一个逐渐鲸吞对手市场的过程。
说了这么多,该来看看 Mojo 长什么样了。当初 Mojo 还不能间接下载应用,如果想要尝鲜,须要在官网申请,而后在 playground 页面中试用,这是一个基于 Jupyter 的页面,能够混合笔记和可执行的 Mojo 代码。
后面提到,Mojo 的语法是 Python 的超集,因而 Mojo 的 Hello World 也跟 Python 一样简略:
与 Python 一样,Mojo 也应用换行符和缩进来定义代码块:
下面的代码中应用 var 来申明变量 x,应用 let 来申明了不可变量 y。Mojo 像很多较早先的语言一样,让不可变量的申明变得简略,以激励开发者应用不可变的量。另外留神到这里定义函数应用了 fn 而非 Python 的 def,这是因为 Mojo 心愿在兼容 Python 的根底上退出编译期的检查和优化,而 Python 过于动静的语法很难反对这一指标,因而,Mojo 同时反对应用 fn 和 def 两个关键字来申明函数,对于调用者来说,这两种办法申明进去的函数没有什么区别,但对于实现者来说,能够将 fn 看作「严格模式」下的 def,例如上面的代码会编译谬误(如果改成用 def 则不会出错):
尽管官网承诺 Mojo 的语法是 Python 的超集,但目前 Mojo 还在开发中,很多 Python 语法都还不反对,例如目前连 Python 的 class 都无奈被编译通过:
不过,Mojo 当初先提供了另一个用来组织数据的关键字 struct,相比于 class,struct 更加动态可控,便于优化。一方面,struct 反对相似 Python class 格调的函数申明和运算符重载。而另一方面,struct 又相似于 C++ 的 struct 和 class,外部的成员在内存中紧凑排布,而且不反对在运行时动静增加成员和办法,便于编译期进行优化,例如:
尽管有点不同,但整体上看起来还是十分相熟的对吧。说到这里,有一点须要揭示各位留神,只管 Mojo 之后会令语法成为 Python 语法的超集,但其语义则有时会和 Python 不同,这意味着 Python 的代码间接拷到 Mojo 里可能会呈现编译通过但执行后果不同的状况,这里简略提一个比拟常见的例子:函数传参。在 Python 中,函数传参的语义相似于 C++ 的传指针,在函数外部尽管不能更改调用者指向的对象,但能够扭转该对象外部的状态,例如上面的代码:
在 Python 中,这段代码打印进去的后果是两次 [5, 2, 3]。但在 Mojo 中,应用 def 定义的函数默认的传递逻辑是复制值,也就是说,只管在函数中可能批改参数外部的状态,但批改对于调用方来说是不可见的,因而下面这段代码在 Mojo 中打印的后果是 [5, 2, 3](foo 外部)和 [1, 2, 3](foo 内部)。
除了语法像 Python,Mojo 十分求实的一点在于它构建于 Python 的生态之上。因而即使 Mojo 还没能残缺反对 Python 的语法,它还是优先反对了对 Python 库的调用,以便让开发者能受害于宏大欠缺的 Python 的生态。例如上面的代码就应用了 Python 的 numpy 库:
Mojo 作为一个新语言,宽泛排汇许多古代的程序语言设计思维,例如 Rust 的所有权和借用查看,以此晋升代码的安全性。在 Mojo 中,应用 fn 定义的函数的参数默认传的是不可变的援用,即「借用」,调用方依然领有其所有权,因而在函数外部不能够对参数进行批改。Mojo 提供了一个 borrow 关键字来标注这样的参数传递状况,对于 fn 来说是能够省略的,也就是说上面 foo 函数中两个参数的传递形式雷同:
在 Rust 中,传参的默认行为是挪动,如果须要借用则须要在传入时加上 &,这两种形式倒是没有太大的优劣之分,Mojo 的行为可能更靠近于 Python 这类高级语言的习惯。如果想要批改传入的参数,则须要手动注明 inout,例如:
按道理说,Mojo 应该像 Rust 一样躲避一个变量同时被可变和不可变借用,也应该躲避同时被可变借用,但目前 Mojo 编译器仿佛还没实现这一个性,例如上面的代码还是能编译通过的:
从这也能够看出 Mojo 的确还处在比拟晚期的倒退阶段。
另一个重要的内存平安概念是对象的所有权,当一个函数获取了对象的所有权后,调用方就不应该再去应用这个对象了,例如咱们实现了一个只反对挪动的类型 UniquePtr:
同时,咱们有两个函数,其中,use_ptr 应用了后面提到的 borrow 关键字,借用了 UniquePtr 对象,而 take_ptr 则应用 owned 关键字,指明它须要获取传入对象的所有权。那么,在调用 take_ptr 的时候,咱们就须要在参数前面加上 ^ 后缀,用来表明咱们将所有权转移给 take_ptr:
因而,如果咱们将 use_ptr 和 take_ptr 的调用程序调换一下,就会呈现编译谬误:
Mojo 的另一个弱小之处在于它让对 MLIR>) 的操作变得更简略。MLIR 全称是 Multi-Level Intermediate Representation,是一个编译器开发框架,它存在的目标是通过定义多种方言来逐级将代码转换为机器码,以升高编译器的开发成本。在 MLIR 之前,一个广为人熟知的 IR 是 LLVM IR,一个语言的编译器作者能够通过将本人的语言编译为 LLVM IR 来接入 LLVM 的工具链,使得编译器作者不须要关怀底层具体硬件的差异,实现了对底层编译工具链的复用:
但 LLVM IR 层级过低,难以进行特定于语言自身的优化,从下面的图中也能看出,各个语言为了实现语言自身的优化,都在编译为 LLVM IR 之前退出了本人的 IR。另外 LLVM IR 扩大起来也十分艰难,难以适应简单异构计算的要求,而异构计算在 AI 开发中又十分广泛。MLIR 相比于之前的 IR,更加模块化,仅保留了一个十分小的内核,不便开发者进行扩大。很多编译器将代码编译为 MLIR,而 Mojo 提供了间接拜访 MLIR 的能力,这使得 Mojo 可能受害于这些工具。更多对于 MLIR 的内容能够参考这一系列文章:编译器与两头示意: LLVM IR, SPIR-V, 以及 MLIR,这里就不做过多赘述,咱们次要关注在 Mojo 中能够如何操作 MLIR。举例而言,如果咱们心愿实现一个新的 boolean 类型 OurBool,咱们能够这样实现:
这里定义了一个类型为 OurBool 的类型,外面有一个间接应用 MLIR 内置类型 i1 的成员 value。在 Mojo 中,咱们能够通过 mlir_type.typename 的模式来拜访 MLIR 类型。接着,咱们为这个类型提供了两个构造函数,默认状况下结构为 OurFalse 也可基于传入的参数进行构建。最上面的 __bool 也和 Python 的 bool 一样,用于使该类型具备和内置 boolean 类型的性质,此时咱们能够这样应用它:
除了应用 MLIR 之外,Mojo 甚至能够容许开发者应用 MLIR 实现逻辑,例如上面的代码中通过利用 MLIR 的 index.casts 操作来实现类型转换,而后再通过 index.cmp 对值进行比拟:
基于封装好的 eq 办法,咱们能够很容易实现 invert 办法:
此时,咱们就能够对 OurBool 类型的对象应用 ~ 操作符了:
通过这个简略的例子咱们能够看出,在 Mojo 中,开发者能够通过拜访 MLIR 来实现和内置类型等同高效的类型。这使得开发者能够在 Mojo 上为新硬件的数据类型封装高效简略的 Mojo 接口而不须要切换语言。尽管大部分开发者并不需要接触 MLIR,但 Mojo 为更深刻和更底层的优化提供了充沛的可能性。
尽管 Mojo 反复强调它是为 AI 设计的新语言,但以目前 Mojo 的设计方向来看,它的发展前景并不止于 AI。实质上 Mojo 提供了一个可能兼容 Python 生态的高性能语言,且这个语言能够让 Python 开发者简直无痛地切换过来,那 Python 开发者何乐而不为呢?对于应用 Mojo 的开发者来说,下层业务能够将 Mojo 当 Python 一样应用,享受到扼要的语法带来的高开发效率,当呈现性能瓶颈的时候,也不必切换语言去进行优化,间接应用 Mojo 重构模块即可。尽管当初还没法在生产环境中验证这个想法,但这个将来听起来的确十分美妙。对于 Mojo 和 Python 开发性能的比照,您可浏览 Mojo 发布会上的 Jeremy Howard demo for Mojo launch 视频。
目前 Mojo 还在比拟晚期的阶段,不仅许多语言个性都还没实现,而且连本地开发的套件都没有提供。不过其倒退路线和设计思路都十分求实,又有一个足够业余的领导者和公司作为背景撑持,能够说是将来可期,也十分心愿这个语言能在其余畛域失去更宽泛的利用。