说起 Chris Lattner,大家一定不陌生。 这位编译器大神,曾经领导了众多大型技术项目。 他不仅是 LLVM 项目的主要发起人,还是 Clang 编译器的创作者。 同时,他是苹果公司编译器开发团队的首席架构师和苹果新编程语言 Swift 创造者。 此外,Chris Lattner 还为 Google Brain 和 TensorFlow 建立和管理了一系列与 AI 相关的编译器、运行时和编程语言团队。
2022 年 1 月,Chris Lattner 和 AI 领域专家 Tim Davis 共同建立了 Modular AI,旨在重建全球 ML 基础设施。
(相关资料图)
而在近日,编译器的大神 Chris Lattner 在社交平台上官宣了一种全新的编程语言: Mojo!
Enlitic 创始人 Jeremy Howard 在试用 Mojo 后表示:「Mojo 是几十年来最大的编程进步」。
Jeremy Howard 直言:
回顾我第一次使用 Visual Basic v1.0 的情景。彼时,它还只是一个针对 DOS 的程序。在此之前,编写程序非常复杂。因此,我除了掌握最基本的玩具应用程序之外,其他方面没有取得太大进展。然而,当我在 VB 屏幕上画了一个按钮,输入了一行代码就得到一个完整的可运行应用程序时,我觉得很惊奇。这种感觉令我难以忘怀 。我人生中第二次有这种感觉,是用 Modular1 推出的新编程语言 Mojo 编写代码。
比 Python 快 3500 倍的 Mojo 有何神奇之处?
Mojo 将 Python 的易用性与 C 的性能相结合,释放了 AI 硬件强大的可编程性和 AI 模型的可扩 展性。使用 Mojo 可以编写比 C 更快、可移植的代码,并与 Python 生态系统无缝交互。最重要的是,Mojo 具备了使用 Python 库整个生态系统的能力。
一、可用性和可编程性
Mojo 能够对大量低级 AI 硬件进行编程,无需 C++ 或 CUDA。
二、性能
在用最先进的编译器和异构运行时,Mojo 能够利用硬件的全部功能,包括多核、向量单元和加速器单元,在不复杂的前提下,实现与 C++ 和 CUDA 相当的性能。
除此之外,根据官网测试结果,Mojo 比 Python 快 35000 倍。
三、互操作性
Mojo 可以访问 Numpy 和 Matplotlib 等任意库以及所有用户自定义代码。
四、可扩展性
Mojo 可以通过预处理和后处理操作轻松扩展模型,或将操作替换为自定义操作。并且能够利用内核融合、图形重写、形状函数等。
目前,Mojo 仍在不断完善中,但开发者可以在 Modular AI 基于 JupyterHub 的 Playground 进行试用。用户可以通过阅读教程编写自己的 Mojo 代码。
试用地址:https://docs.modular.com/mojo/get-started.html
Mojo 的官宣在网上引起了不小的讨论,Jeremy Howard 体验后更是发长文向大家安利了 Mojo。在他看来,Mojo 是 Python++ 更为恰当,并且比 Swift 更实用。他直言自己更喜欢使用一种既像 Python 优雅,又像 C 一样快的语言,能够允许其使用一个语言来编写从应用程序服务器到模型架构以及安装程序的所有内容,并且能在编写代码的语言中直接调试和分析其代码。
缺点一:性能缺陷
Python 有几乎完美的核心,它可以做任何事情。但它也有一个缺点:性能。Python 比像 C++ 这样的语言慢几千倍。因此,将 Python 用于代码性能的关键部分不切实际。
但 Python 有一个绝招:它可以调用快速语言编写的代码。因此,Python程序员不用 Python 来实现性能关键部分的代码。比如 Numpy 和 PyTorch 这样的库为高性能代码提供了“pythonic”接口,让 Python 程序员有种宾至如归的感觉。
缺点二:“双语言"问题
如今,几乎所有的 AI 模型都是用 Python 开发,这要归功于其灵活、完美的编程语言、高效的工具和生态系统,以及高性能的编译库。
但这种“双语言”方法有严重的缺点。例如,AI 模型通常必须用 Python 转换为更快的实现,如 ONNX 或 torchscript。但这些部署方法不能支持 Python 的所有功能,因此 Python 程序员必须学会使用与其部署目标匹配的语言子集。并且,很难对部署版本的代码进行分析或调试,也不能保证它能够和 Python 版本完全相同地运行。
这种双语言问题妨碍了学习,使得开发者无法在代码运行时执行算法的实现,或者跳转到感兴趣的方法定义,而是陷入 C 库和二进制文件的细节中。对于 AI 领域的快速发展,开发者们都需要学习。但无论是经验丰富的开发者还是入门的学生,仍面临很多困难。
当尝试调试代码或查找和解决性能问题时,同样会出现这个问题。双语言问题意味着,一旦我们进入后端实现语言,Python 程序员熟悉的工具就不再适用。
即使使用最快的编译实现语言来编写库,也会出现无法避免的性能问题。一个主要问题是缺乏“融合”——也就是说,连续调用一堆编译函数会带来大量的开销,因为数据被转换为 Python 格式和从 Python 格式转换,并且必须重复从 Python 到 C 的切换成本。
因此,我们必须编写常见函数组合的特殊“融合”版本,并从 Python 调用这些融合版本。这意味着需要实现和记忆更多的库函数,稍有差错,就很难有一个合适的融合版本。
我们还必须处理 Python 中缺乏有效并行处理的问题。如今,我们都有具备多个内核的计算机,但 Python 通常只会一次使用一个内核。有一些笨重的方法可以编写使用多个核的并行代码,但它们要么必须在完全分离的内存上工作(并且启动开销大),要么必须交替访问内存(可怕的“全局解释器锁定”通常使并行代码比单线程代码实际上更慢!)
像 PyTorch 这样的库一直在开发越来越巧妙的方法来处理这些性能问题,新发布的 PyTorch 2 甚至包括一个 compile() 函数,该函数使用复杂的编译后端来创建 Python 代码的高性能实现。然而,这样的功能不能发挥魔力:基于语言本身的设计,Python 的可能性存在根本性限制。
除此之外,它们总体上都是非常基本的算法,例如,transformer 模型几乎完全由多层感知器(MLP)和注意力实现,只需几行 Python 和 PyTorch 即可实现。以下是 MLP 的实现:
nn.Sequential(nn.Linear(ni,nh), nn.GELU(), nn.LayerNorm(nh), nn.Linear(nh,ni))
这是一个自我注意层:
def forward(self, x):
x = self.qkv(self.norm(x))
x = rearrange(x, "n s (h d) ->(n h) s d", h=self.nheads)
q,k,v = torch.chunk(x, 3, dim=-1)
s = (q@k.transpose(1,2))/self.scale
x = s.softmax(dim=-1)@v
x = rearrange(x, "(n h) s d ->n s (h d)", h=self.nheads)
return self.proj(x)
但实际上,这些操作很复杂。例如,在 CUDA C 中进行内存优化的“闪存注意”实现。它还掩盖了这些构建模型通用方法所留下的大量性能事实。例如,“块稀疏”方法可以显著提高速度和内存使用。研究人员正在对常见架构的每个部分进行调整,并提出新的架构(以及 SGD 优化器,数据增强方法等)——我们甚至还没有适合每个人都会永久使用的整齐包装系统。
在实践中,许多用于语言模型的最快代码都是用 C 和 C++ 编写的。例如,Fabrice Bellard 的 TextSynth 和 Georgi Gerganov 的 ggml 都使用 C 语言,因此能够充分利用完全编译语言的性能优势。
Chris Lattner 开发的 LLVM,从根本上改变了编译器的创建方式,也为世界上许多广泛使用的语言生态系统打下了坚实的基础。
之后,他推出了一个建立在 LLVM 之上的 C 和 C++ 编译器 Clang,并被广泛使用。LLVM 包括“中间表示”(IR),这是一种为机器读写(而不是人)设计的特殊语言,它使一个庞大的软件社区能够协同合作,为更广泛的硬件提供更好的编程语言功能。
Chris Lattner 认为 C 和 C++ 并没有完全充分利用 LLVM 的威力,因此他在苹果工作时,设计了一种名为“Swift”的新语言,他将其描述为“LLVM 的语法糖”。如今,Swift 已经成为世界上使用最广泛的编程语言之一,它是 iPhone、iPad、MacOS 和 Apple TV 创建 iOS 应用程序的主要方式。
不幸的是,苹果对 Swift 的控制导致它还没有真正在技术领域大放异彩。Chris Lattner 曾在 Google 试图将 Swift 从苹果的舒适区移出来,成为 AI 模型开发中 Python 的替代品。但遗憾的是,它没有得到苹果或 Google 的支持,最终没有成功。
话虽如此,在 Google 期间,Chris 开发了另一个非常成功的项目:MLIR。MLIR 是 LLVM IR 的替代品,适用于现代多核计算和 AI 工作负载。这对于充分利用 GPU、TPU 等硬件的强大功能以及越来越多地添加到服务器级 CPU 中的向量单元至关重要。
那么,如果 Swift 是“LLVM 的语法糖”,那么“MLIR 的语法糖”是什么?答案是:Mojo!Mojo 是一种全新的语言,旨在充分利用 MLIR。并且,Mojo 也是 Python。
也许说 Mojo 是 Python++ 更为恰当。它将(完成后)是 Python 语言的严格超集。但它还会有额外的功能,因此,开发者可以编写利用现代加速器的高性能代码。
在 Jeremy Howard 看来,Mojo 似乎比 Swift 更实用。虽然 Swift 是一种包含了基于编程语言设计最新研究的各种炫酷功能的全新语言,但 Mojo 的核心是 Python。因为 Python 已经被数百万程序员广泛使用,并且经过几十年的使用,它的功能和局限性已经得到了充分完善。
用最新的编程语言进行研究很酷,但它可能具有潜在危险。就 Jeremy Howard 个人而言,他经常被 Swift 强大但古怪的类型系统搞糊涂,有时甚至成功地被 Swift 编译器混淆并且完全崩溃!
Mojo 的一个关键技巧是,作为开发人员,您可以随时选择加入更快的“模式”。通过使用“fn”而不是“def”来创建函数。在这种模式下,您必须准确确定每个变量的类型,Mojo 可以创建优化的机器代码来实现函数。此外,如果使用“struct”而不是“class”,您的属性将被打包到内存中,这样它们可以在数据结构中使用,而无需追踪指针。这些特性使得像 C 这样的语言变得很快,而现在 Python 程序员只需学习一点点新语法即可使用。
几十年来,为了创建简洁、灵活、快速、实用和易于使用的编程语言,开发者们进行了数百次尝试。但并没有取得太大成效。而 Mojo 似乎做到了。我们可以得出一些假设:
1.Mojo 实际上并没有实现这些目标,精彩的演示掩盖了令人失望的现实表现;
2.Modular 是一家拥有数百名开发人员的大型公司,公司为实现未曾实现过的目标付出了多年的努力。
事实上,这两个假设都不成立。我们举两个例子,(matmul 和 mandelbrot)并不是在尝试了数十种方法后才精心选择的唯一有效方法;相反,它们是我们为演示而尝试的一种方法,并且第一次就成功了!虽然在早期阶段还有很多功能的缺失(除了在线“playground”之外,Mojo 还没有发布给公众),但在演示中看到的确实是它的工作方式。事实上,您现在可以在 playground 中自己运行它。
Modulal 是一家仅成立一年的小初创公司。一个小团队是如何在短时间内做到这样的呢?
关键一:基础强大
关键在于 Mojo 是建立在一些非常强大的基础之上的。Jeremy Howard 表示,很少见到有软件项目愿意花费时间来打好坚实的基础,并且往往会因此积累大量的技术债务。随着时间的推移,添加功能和修复错误变得越来越困难。然而,在 Mojo 这样一个设计良好的系统中,每个功能都比上一个更容易添加,速度也更快,出错率低,每个功能所构建的基础都越来越好。
其核心是最初是由 Chris Lattner 在 Google 开发的并且使用多年的 MLIR。Chris Lattner 已经意识到“AI 时代编程语言”的核心基础将需要什么,并专注于构建它们。MLIR 就是其中的一个关键部分。就像 LLVM 在过去十年中极大地简化了强大的新编程语言的开发一样(如 Rust、Julia 和Swift,它们都基于 LLVM),MLIR 为建立在其上的语言提供了更强大的核心。
关键二:使用 Python 作为语法
Mojo 快速发展的另一个关键因素是使用 Python 作为语法。开发和迭代语法是语言开发中最容易出错、最复杂和最有争议的一部分。通过简单地将其外包给现有的语言,整个部分就会消失!因此,需要少量地在 Python 之上添加的新语法部分来自然适应。
接下来是创建一个最小的 Pythonic 方法直接调用 MLIR。这并不是一个大项目,但它需要在此基础上创建所有 Mojo 所需的全部内容,并直接在 Mojo 中处理其他所有工作。这意味着 Mojo 开发人员从一开始就能“dog-food” Mojo。当他们在开发 Mojo 并发现某些东西不太好用时,他们可以为 Mojo 本身添加所需的功能,以便更容易开发下一个 Mojo 部分!
这一点和 Julia 很像。Julia 是在最小类 LISP 内核上开发的,该内核提供 Julia 语言元素,然后将其绑定到基本的 LLVM 操作。Julia 中的几乎所有内容都是建立在它之上。
Modular 团队决定通过视频(包括演示)推出 Mojo,并且他们将时间定在了未来几周之内。那时 Mojo 只是最基本的语言,没有可用的笔记本内核,几乎没有实现任何 Python 语法,也没有进行任何优化。
起初,Jeremy Howard 并不理解这个行为。但这段时间,Mojo 的表现让他叹为观止。每隔一两天,就有全新的语言功能被实现。一旦有足够的语言功能来尝试运行算法,它们就能够立即达到或接近最先进的性能水平!Jeremy Howard 突然意识到 Mojo 所有的基础都已经建好了,并且它们被明确设计用于构建现在正在开发的东西。因此,一切都很顺利,而且运作良好。
这是 Jeremy Howard 对 Mojo 未来持乐观态度的理由。虽然这个项目还处于早期阶段,但根据 Jeremy Howard 在过去几周中观察到的情况,他的猜测是它将比大多数人预期的发展得更快、更远……
这是很关键的一点。如果你想把你酷炫的 Python 程序给朋友,你需要让他们 先安装 Python!或者,你可以给他们一个包含 Python 和你使用的所有库的巨大文件。
由于 Python 是一种解释型语言,所以程序的执行将取决于所安装 Python 的确切版本、存在的库的版本以及它们的配置方式。为了避免维护问题,Python 社区已经确定了几种选项来安装 Python 应用程序:环境,每个程序都有一个单独的 Python 安装或容器,为每个应用程序设置一整个操作系统。但这两种方法都会在开发和部署 Python 应用程序时产生许多混乱和开销。
将其与部署静态编译的 C 应用程序进行比较:您只需将编译的程序直接下载即可。它的大小可能只有 100k 左右,可以快速启动和运行。
还有一种 Go 采用的方法,它不能生成像 C 这样的小型应用程序,而是将“运行时”集成到每个打包的应用程序中。这种方法是 Python 和 C 之间的折中,仍需要数十兆字节的二进制文件,但比 Python 更容易部署。
作为一种编译语言,Mojo 的部署方式与 C 基本相同。例如,一个包含从头 开始编写的 matmul 版本的程序大约是 100 k。
这意味着Mojo不仅仅是用于AI/ML应用的语言。它实际上是Python的一个版本,允许开发者编写快速、小巧、易于部署的应用程序,并利用所有可用的核心和加速器!
Mojo 并不是解决 Python 性能和部署问题的唯一办法。在语言方面,Julia 可能是目前最强的替代品。它具有 Mojo 的许多优点,并且构建出了很多优秀的项目。但在 Jeremy Howard 看来,Julia 也并不完美。
Julia 面临的最大挑战源于其大型运行时,决定在该语言中使用垃圾回收。此外,Julia 中使用的多调度方法虽然打开了很多用语言做很酷的事情的大门,但也会让开发人员的工作变得非常复杂。
在 Python 中,目前最好的解决方案可能是 Jax,它有效地使用 Python 创建了一个特定领域的语言(DSL)。这种语言的输出是 XLA,它是一个早于 MLIR 的机器学习编译器。Jax 继承了 Python(例如,该语言无法表示结构,或直接分配内存或创建快速循环)和 XLA(主要针对 TPU,很大程度上限制在机器学习特定的概念上)的限制,但具有巨大的优势,即不需要新的语言或新的编译器。
如前所述,还有新的 PyTorch 编译器,Tensorflow 也能够生成 XLA 代码。就 Jeremy Howard 个人而言,这种方式使用 Python 最终并不令人满意。Jeremy Howard 实际上并没有使用 Python 的所有功能,但必须使用与其所针对的后端兼容的子集。Jeremy Howard 不能轻松地调试和分析编译后的代码,因为很多不确定性,甚至很难知道最终执行的内容。
Jeremy Howard 甚至无法得到一个独立的二进制文件,而必须使用特殊的运行时并处理复杂的 API。
Python 的另一个有趣的方向是 Numba 和 Cython。Numba 使用一个特殊的装饰器,使 Python 函数被编译成使用 LLVM 优化的机器代码。Cython 类似,但还提供了一个类似于 Python 的语言,具有 Mojo 的一些特性,并将这个 Python 方言转换成 C,然后进行编译。这些语言都没有解决部署问题,但它们可以帮助解决性能问题。
两者都无法针对具有通用跨平台代码的一系列加速器,尽管 Numba 确实提供了一种非常有用的方法来编写 CUDA 代码(因此可以针对 NVIDIA GPU)。Jeremy Howard 对于 Numba 和 Cython 的存在表示感激,并从中受益匪浅。然而,它们并不像使用完整语言和编译器生成独立二进制文件一样。它们只是 Python 性能问题的临时解决方案,在仅需要这些功能的情况下可以使用。
Jeremy Howard 最后表示,他 更喜欢使用一种既优雅如 Python,又快速如专业编写的 C 语言,允许他使用同一种语言编写应用程序服务器、模型架构以及安装程序,并可以直接在自己编写代码的语言中进行调试和分析。
你喜欢这样的语言吗?欢迎在评论区留言讨论。
参考链接:
https://www.fast.ai/posts/2023-05-03-mojo-launch.html
https://www.modular.com/mojo
关键词: