我的学习笔记

土猛的员外

AI编程语言--Mojo

为什么要发明Mojo?

当我们开始创建Modular(公司名)时,并没有打算构建一种新的编程语言。但是,当我们致力于统一全球ML/AI基础设施的平台时,我们意识到整个技术栈上的编程过于复杂。此外,我们手工编写了大量的MLIR(一种用于编译器和工具链的领域特定语言,旨在提高代码生成的可重用性和灵活性)代码,并且还不太遂人愿。

我们想要的是一种创新和可扩展的编程模型,可以针对加速器和其他在机器学习中普遍存在的异构系统。这意味着需要具有强大的编译期元编程自适应编译技术集成、整个编译流程中缓存等功能,但这些功能在现有语言上都不支持。

虽然加速器很重要,但最常见而被忽视的“加速器”之一是主机CPU。今天,CPU拥有许多类似张量核心加速块和其他AI加速单元,但它们也作为专用加速器无法处理(例如数据加载、预处理和后处理,以及与外部系统集成)操作时候使用。因此,显而易见我们不能使用一种仅适用于特定处理器的“加速语言”来提升AI。

应用人工智能系统需要解决所有这些问题,我们认为没有理由不能只用一种语言来完成。于是,Mojo诞生了。

我们决定将Mojo的创新放在编译器内部,也会对当前和新兴加速器进行优化和支持。但在语言层面,并不需要进行大刀阔斧地创新。因此,我们选择采用Python生态系统,因为它被广泛使用、受到AI生态系统的喜爱,并且非常好用!

Mojo 是 Python 家族的一员

Mojo语言有着崇高的目标——我们希望与Python生态系统完全兼容,同时也需要可预测的底层(语言的)性能和底层(系统级)控制,并且需要能够将代码子集部署到加速器上。我们还不希望出现生态系统分裂的情况——我们希望人们随着时间推移发现我们的工作是有用的,并且不想再次发生像Python 2 => Python 3迁移这样的事情。

这些都是非常重要的目标!

幸运的是,虽然Mojo是一个全新的代码库,但在概念上并没有从零开始。采用Python大大简化了我们的设计工作,因为大多数语法已经被指定了。相反,我们可以把精力集中在构建编译模型和设计特定系统编程功能上。此外,我们还受益于其他语言(例如Clang、Rust、Swift、Julia、Zig、Nim等)所做出来巨大贡献,并利用MLIR编译器生态系统。同时,由于对Swift编程语言有经验,在将庞大Objective-C社区迁移到新语言方面也获得了很多好处。

此外,我们认为Mojo正确长期目标应该提供Python超集(即与现有程序兼容),并立即采用CPython以实现长尾生态系统。对于Python程序员来说,我们期望Mojo会让你有亲切感,并它还提供开发系统级代码的能力,使您能够做到Python无法胜任那些本来需要使用C和C++来做的事情。我们不参与“静态更好”或者“动态更好”的观点——我们认为在正确使用他们,两者都很好,并且语言应该使程序员能够做出决策。

Mojo和Python有多兼容?

Mojo已经支持许多Python的核心功能,包括async/await、错误处理、可变参数等等,但是…它仍然处于早期阶段,并且缺少许多功能 ——所以今天它们并不是非常兼容。 Mojo甚至还没有支持类(Class)!

尽管如此,我们还是要谈一下两个非常显著但却不同的兼容性方面的经验: “Clang”编译器的作用对象是LLVM中的C、C++和Objective-C(以及CUDA、OpenCL等),其主要目标是成为GCC、MSVC和其他现有编译器的“兼容替代品”。虽然和我们今天的话题很难进行直接比较,但Clang问题的复杂性似乎比实现Python兼容替代品大一个数量级。所以这件事情的完美解决可以给大家带来信心,相信我们能够正确地为Python社区做出贡献。

另一个例子就是Swift编程语言,它采用了Objective-C运行时和语言生态系统,并逐步将数百万程序员(以及大量代码)转移到完全不同的编程语言上。通过Swift, 我们学会了如何“运行时兼容”,并与遗留运行时合作。在Python和Mojo方面,在CPython运行时下直接协作,并具有与CPython类和对象集成类似的支持,而无需编译代码。这将使我们能够与现有代码的大量生态系统进行交流,同时提供渐进式迁移方法,其中为迁移付出的增量工作将产生增量收益。

总体而言,我们相信兼容性、设计持续警惕和逐步实现完全兼容性的北极星会在时间上帮助我们达到目标。

与Python的有意区别

兼容性和可迁移性是成功的关键,但我们也希望Mojo成为一种一流的语言,并且不能因为无法引入新关键字或添加几个语法规则而受到限制。因此,我们对兼容性的处理方式有两个方面:

  1. 我们利用CPython来运行所有现有的Python3代码,“开箱即用”,不需要修改并使用其运行时,在整个生态系统中实现完全兼容。但以这种方式运行代码将得不到使用Mojo的任何好处,这个生态系统存在和可用将快速加速Mojo的启动,并利用Python已经非常适合高级编程这一事实。

  2. 我们将提供一个机械化迁移工具,为那些想要将Python代码转移到Mojo上提供很好的兼容性。例如,Mojo提供了反引号功能,可以使用任何关键字作为标识符,从而为使用这些关键字作为标识符或关键字参数的代码提供了一个简单机械化迁移路径。迁移到Mojo上后,则可以利用先进的系统编程特性。

总之,这使得Mojo能够在大多数基于CPython环境下良好地集成,并且让程序员能够逐步地(每次模块或文件)将代码转移到Mojo上。苹果公司执行Objective-C到Swift迁移时也采用了这种方法。Swift代码能够子类化和利用Objective-C类,并且程序员可以逐步地在他们的应用程序中采用Swift。Swift还支持构建对于Objective-C程序员有用的API,我们期望Mojo也是实现CPython API的好方式。

构建Mojo和迁移支持需要一些时间,但我们相信这将使我们集中精力,避免分心。我们也认为与CPython的关系可以从两个方向建立——如果CPython团队最终在Mojo而不是C中重新实现解释器,那不是很酷吗?

关于动机

Mojo的目标是将创新的编程模型带到加速器和其他在机器学习中普遍存在的异构系统中。话虽如此,最重要和普遍的“加速器”之一实际上是主机CPU。这些CPU正在获得许多类似张量核心的加速块和其他专用AI加速单元,但它们也重要地作为“后备”,以支持加速器不支持的操作。这包括数据加载、预处理、后处理以及与使用C++编写的外部系统集成等任务。

因此,我们意识到无法构建一个仅针对问题狭窄子集(例如仅适用于张量)的有限制语言。我们需要支持全面通用编程范畴。同时,我们没有看到需要在语法或社区方面进行创新,因此决定采用并完善Python生态系统。

为什么是Python?

Python是机器学习领域和其他许多领域的主导力量。它易于学习,被重要的程序员群体(例如数据科学家)所知晓,拥有一个惊人的社区、大量有价值的包以及各种良好的工具。通过其动态编程特性,Python支持开发美观而富有表现力的API,这使得像TensorFlow和PyTorch这样的机器学习框架将Python作为前端来使用他们在C++中实现高性能运行时。

对于Modular来说,Python是我们API使用端技术栈中不可动摇的一部分——这是由我们客户决定的。考虑到我们技术栈中其他所有内容都可以协商处理,因此“Python优先”方法似乎很合理。

更加主观地说,我们认为Python是一种优美语言——用简单和可组合的抽象来设计,避免了不必要的、多余的、缩进式的标点符号,并且构建了强大的(动态的)元编程特性,这些特性是扩展到我们需要的模块化的一个途径。我们希望那些处于Python生态系统中的人们看到我们新方向时会将其视为推动Python迈向下一个级别——完善它——而不是试图与之竞争。

Python的问题

Python存在着众所周知的问题,最明显的是低级性能差和CPython实现决策(如GIL)。虽然有许多积极的项目正在进行中以改善这些挑战,但Python带来的问题更深层次地影响了人工智能领域。在本文中,我们不会谈论那些技术限制,而是将重点放在这些限制对2023年产生的影响上。

请注意,在本节中提到的Python都指CPython实现。稍后我们将讨论其他实现。

(Python和C/C++)两个世界的问题

由于各种原因,Python并不适合系统编程。幸运的是,Python在作为粘合层方面有着惊人的优势,并且与C和C++之类的低级绑定允许使用具有更好性能特征的C、C++和许多其他语言构建库。这就是使得像numpy、TensorFlow和PyTorch以及生态系统中大量其他库成为可能的原因。

然而,虽然这种方法是构建高性能Python库的有效方式,但它也带来了一些代价:构建这些混合库非常复杂,需要对cpython内部有低级别理解,并需要掌握C/C++/…编程知识(从根本上破坏了最初使用Python的目标之一),使得难以发展大型框架,并且(在ML情况下)将世界推向比“急切模式”系统更差基础可用性的“图形化”编程模型。TensorFlow就是一个例子,但PyTorch 2中大部分工作都集中在发现图形以启用更积极地编译方法。

除了两个世界问题本质上涉及到系统复杂性外,在生态系统中所有其他事物都变得更加复杂。调试器通常无法跨越Python和C代码进行步进调试,而那些可以的调试器也没有被广泛接受。对于软件包生态系统来说,处理C/C++代码而不是单个世界是一种痛苦。像PyTorch这样具有重要C++投入的项目正在有意识地尝试将其代码库中更多的内容移植到Python中,因为他们知道这样做会提高可用性。

三个世界的问题以及N个时间的问题

两个世界的问题在Python生态系统中普遍存在,但对于机器学习框架的开发人员来说情况更加糟糕。人工智能得到了广泛的加速,而这些加速器使用定制编程语言如CUDA。虽然CUDA是C++开发的,但它有自己特殊的问题和限制,并且没有像调试器或分析工具一样一致性强大的工具。此外,它实际上被锁定在单个硬件制造商!

人工智能领域在硬件方面拥有令人难以置信数量的创新成果,因此复杂性正在失控地螺旋上升。现在已经有许多尝试为加速器构建受限编程系统(OpenCL、Sycl、OneAPI等)。这种复杂度爆炸趋势还在继续增长,并且这些系统都没有解决困扰行业并使其受损最严重的基本工具和生态系统碎片化问题。

移动端和服务器部署

Python 生态系统面临的另一个挑战是部署。这包括希望仔细控制依赖项的人,一些人更喜欢能够部署密封编译的a.out文件,多线程和性能也非常重要。我们希望看到 Python 生态系统在这些领域迈出步伐。

相关工作:改进Python的其他方法

有许多方法可以改进Python,包括让Python变得更快和替换GIL的工作,看起来像Python但是它们是其子集的语言以及与Python集成但不是第一类语言的嵌入式DSL。虽然我们无法列出所有努力,但我们可以谈论这些领域中的一些挑战以及为什么它们不适合Modular使用。

改进CPython和JIT编译Python

最近,人们在改善CPython性能和其他实现问题方面投入了大量精力,这对社区来说产生了巨大的成果。这项工作非常棒,因为它逐步改进了当前的CPython实现。Python 3.11通过内部改进提供了比Python 3.10快10-60%的改进,并且Python 3.12旨在通过跟踪优化器更进一步地提高性能。许多其他项目正在尝试驯服GIL,并且像PyPy(以及许多其他项目)使用JIT编译和跟踪方法加速Python。

这些都是伟大的努力,但对于将统一语言放到加速器上并不有帮助。如今,许多加速器仅支持非常有限的动态特性或者以可怕的性能支持动态特性。此外,系统程序员不仅寻求“性能”,他们通常还想要很多关于计算发生方式“可预测和控制”的内容。

虽然我们欣赏这些方法,并认为它们对社区具有价值和吸引力,但遗憾的是它们不能满足我们的需求。我们希望消除在Python库中使用C或C++所需,在某些情况下完全不接受动态特性,因此这些方法并没有帮助。

Python子集和其他类似Python的语言

有许多尝试构建“可部署”Python的方法,其中一个例子是PyTorch项目中的TorchScript。这些方法很有用,因为它们通常提供低依赖性的部署解决方案,并且有时具有高性能。由于它们使用类似Python语法,所以比起新颖的语言更容易学习。

另一方面,这些语言并没有得到广泛采用-因为它们是子集,通常不与Python生态系统互操作,在工具(例如调试器)方面也不太好,并且经常单方面更改Python中不便利的行为,从而破坏兼容性并使生态系统分裂。例如,其中许多会将简单整数的行为更改为包装而非生成与Python兼容的数学结果。

这些方法存在挑战之处在于他们试图解决 Python 的弱点, 但却不能像 Python 强项那样出色地完成任务. 最好情况下, 这些可以提供 C 和 C++ 的新选择 - 但如果不能解决 Python 的动态使用场景,则无法解决“两个世界问题”。此方法导致了碎片化和不兼容性, 并使迁移变得困难甚至不可能 - 回想一下从 Python 2 到 Python 3 的迁移是多么具有挑战性。

Python中的嵌入式DSL

另一种常见的方法是在Python中构建嵌入式DSL,通常使用Python装饰器安装。有许多这样的例子,例如TensorFlow中的@tf.function装饰器、OpenAI Triton编程模型中的@triton.jit等。这些系统的一个主要优点是它们与所有Python生态系统工具兼容,并且可以本地集成到Python逻辑中,允许嵌入式小语言与动态用例下Python强大功能共存。

不幸的是,这些系统提供的嵌入式小语言经常具有令人惊讶的限制,在调试器和其他工作流工具方面集成效果不佳,并且不支持我们为统一异构计算并编写大规模内核和系统所寻求的本地语言集成水平。我们希望通过简化事物并使其更加一致来推进整个系统可用性。嵌入式DSL是快速启动演示程序的便捷方式,但我们愿意付出额外努力和工作来为我们自己设定目标。

一览Mojo编程风格

您可以像使用Python一样从终端运行Mojo程序。因此,如果您有一个名为hello.mojo(或hello.(火)——是的,文件扩展名可以是这个表情符号!)的文件,请键入mojo hello.mojo:
这个(火)就像:

^_^因为这个表情打不出来。

1
2
3
4
5
6
7
8
9
10
11
$ cat hello.(火)
def main():
print("hello world")
for x in range(9, 0, -3):
print(x)
$ mojo hello.(火)
hello world
9
6
3
$

你可以使用表情符号或.mojo后缀。

如果您对深入了解 Mojo 的内部实现细节感兴趣,可以查看标准库中的类型、笔记本中的示例代码、博客和其他样例代码。

考虑到我们的兼容性目标以及Python在高级应用和动态API方面的优势,我们不必花费太多时间来解释语言中这些部分是如何工作的。另一方面,Python对系统编程的支持主要委托给C语言,并且我们希望提供一个在该领域表现出色的单一系统。因此,本节将详细介绍每个主要组件和功能,并说明如何使用它们并附有示例。

letvar声明

在Mojo中的def内部,您可以为一个名称分配一个值,并且它会隐式地创建一个函数作用域变量,就像Python一样。这提供了一种非常动态和低仪式感的编写代码的方式,但由于以下两个原因而具有挑战性:

  1. 系统程序员经常希望声明某个值是不可变的,以实现类型安全性和性能。
  2. 他们可能希望在赋值时如果错误拼写了变量名,则会出错。

为支持此功能,Mojo提供了作用域运行时值声明:let是不可变的,var是可变的。这些值使用词法作用域并支持名称遮蔽:

1
2
3
4
5
6
7
def your_function(a, b):
let c = a
c = b # error: c is immutable

if c != b:
var c = b
stuff()

letvar声明支持类型说明符以及模式,还支持延迟初始化:

1
2
3
4
5
6
7
8
9
10
def your_function():
let x: Int8 = 42
let y: Int64 = 17

let z: Int8
if x != 0:
z = 1
else:
z = foo()
use(z)

Note that let and var are completely opt-in when in def declarations. You can still use implicitly declared values as with Python, and they get function scope as usual.

请注意,在def声明中,letvar是完全可选的。您仍然可以像Python一样使用隐式声明的值,并且它们会像往常一样获得函数作用域。

struct类型(译者:这是Rust的概念)

Mojo基于MLIR和LLVM,这两个编译器和代码生成系统在许多编程语言中都得到了广泛应用。这使我们能够更好地控制数据组织、直接访问数据字段以及其他提高性能的方式。现代系统编程语言的一个重要特征是,在不损失性能的情况下,在复杂低级操作之上构建高级别且安全的抽象。在Mojo中,这由struct类型提供。

在Mojo中,struct类似于Python class:它们都支持方法、字段、运算符重载、元编程装饰器等功能。它们之间的区别如下:

  1. Python类是动态的:它们允许动态分派、monkey-patching(或“swizzling”)以及在运行时动态绑定实例属性。

  2. Mojo structs是静态的:它们在编译时绑定(您不能在运行时添加方法)。Structs允许您通过牺牲灵活性来换取性能,并且易于使用而又安全。

以下是一个简单定义struct的示例:

Here’s a simple definition of a struct:

1
2
3
4
5
6
7
8
9
10
struct MyPair:
var first: Int
var second: Int
def __init__(self&, first: Int, second: Int):
self.first = first
self.second = second
def __lt__(self, rhs: MyPair) -> Bool:
return self.first < rhs.first or
(self.first == rhs.first and
self.second < rhs.second)

从语法上讲,与 Python 类相比,结构体中的所有实例属性都必须使用 var let 声明显式声明。

好了,详细的Mojo语法手册可以看这里






关注我的微信公众号,可收到实时更新通知

公众号:土猛的员外


我们的创业项目已经上线!!!

TorchV AI,帮助企业快速进入AI时代!

具体详情,请点击官网咨询


最新内容,关注“土猛的员外”公众号