# 克里斯·拉特纳：编程与 AI 的未来

> LLVM 与 Swift 的创造者谈编程语言的演进、编译器技术，以及 AI 将如何改变写代码的方式。

- 原标题：#381 – Chris Lattner: Future of Programming and AI
- 期数：第 381 期
- 发布日期：2023-06-02
- 嘉宾 / 主题：克里斯·拉特纳
- 网页版：https://lexfridman-cn.pages.dev/ep/chris-lattner-3/
- 官方页面：https://lexfridman.com/chris-lattner-3/
- 精读字数：约 5001 字（阅读约 11 分钟）

> 本文为 lex.qiaomu.ai 基于原始访谈的 AI 改写中文精读，非官方翻译；引用请注明出处并链接网页版。

Mojo 官方给出的性能数字是：某些场景下比 Python 快 35000 倍。

说这个数字的人是克里斯·拉特纳（Chris Lattner）。他造过 LLVM 和 Clang，发明了 Swift，在 Google 参与过 TensorFlow 和 TPU，在特斯拉当过 Autopilot 软件副总裁。现在他联合创办了 Modular，一家做 AI 基础设施的公司，Mojo 是这家公司的编程语言，一门 Python 的超集。

这是他第三次上 Lex Fridman 的播客。两年不见，他手里多了一门新语言。

## 一门语言的文件扩展名，可以是一个火苗

Mojo 还有个设计，比 35000 倍更能说明这个人的审美：源文件的后缀名可以是一个 emoji，比如 🔥。

他半开玩笑地说，这大概是这门语言最重要的决定。理由倒不全是玩笑：文件扩展名本质上是元数据，既然是元数据，为什么要让它占掉屏幕上一块地方，还要和一堆 txt、pdf 挤在一起，长得灰扑扑的。想酷一点，就得在视觉上跳出来。

实际用起来有一点小麻烦。Git 不认这个，它把火苗当成不可打印字符，在命令行里吐出一串奇怪的十六进制。**GitHub、VS Code、Windows 资源管理器都没问题**，因为它们早就为国际化路径做过准备，emoji 只是再往前迈一步。

拉特纳对这事的态度是：Git 可以被修好。他把整个反应看成一条光谱，一端是“哦这个挺酷”，另一端是“你在杀我的孩子”。他判断世界会走到前面那一端，而且不必赌上全部家当，两条路可以同时铺。

这个细节值得多看一眼。一个人愿意在文件扩展名这种毫秒级的体验上花心思，说明他做语言不是在做规范，是在做产品。

## 两头都在动，中间就成了一团乱麻

要理解 Mojo 为什么存在，得先看 Modular 想解决的问题。

拉特纳的观察是，TensorFlow 和 PyTorch 这些框架，设计于七八年前。那时候大语言模型还不存在。今天人们拿它们做的事情，和它们被造出来时的目标已经差了一大截。这是软件这一头的变化。

硬件那一头的变化更剧烈。新芯片、新加速器、各种 ASIC，几乎每家像样的公司都在发布自己的芯片，TPU、GPU、NPU、IPU，各种 P 打头的处理器。拉特纳自己在 Google 做过 TPU，那是人类部署过最大规模的 AI 计算系统之一。

> 一边是移动的系统，另一边也是移动的系统，中间就变成了一团巨大的乱麻。（克里斯·拉特纳）

乱麻的代价落在使用者身上。搞软件的人面对一堆自己用不上的硬件，理论上能编程和实际上能把全世界聪明人的创造力释放出来，是两件完全不同的事。

Modular 想做的就是中间那层软件栈，让研究者把想法送进生产环境的路径短一点。Mojo 是这层栈里最关键的一块砖。

## Python 到底好在哪

拉特纳在节目里反问 Lex：你喜欢 Python 什么？

Lex 的回答很感性：直观，写起来像写英文，读别人的代码也比读 C++ 快，同样的意思用更少的字。还有生态，想干什么都有现成的包。

拉特纳接着往下挖了一层。他认为机器学习确实帮了 Python 大忙，TensorFlow 和 PyTorch 拥抱 Python，直接把 Python 推到了今天的位置。但真正让它长成这样的，是它扮演的角色。

> Python 是那个通用连接器。它能把很多不同的系统拼在一起，让你组合出更大的东西，而不必先搞懂每个零件内部怎么转。（克里斯·拉特纳）

这个定位挺准确。Python 更像一块乐高底板，形状各异的积木都能插上去，虽然每块积木本身不是它造的。生态和易用性互相喂养，滚成一个正向循环，一旦滚起来就很难被替代。

那问题在哪？

慢。

## 35000 倍这个数字，一层层拆开

这个倍数听着像营销话术，其实拆开看，每一层都有具体的技术来源，加起来还真能到这个量级。

**第一层，把解释器拿掉。**大家平时敲 *python3* 跑的那个叫 CPython，是个解释器。代码要先变成字节码，再被读、被解析、被解释执行，这中间多了一整层。改用编译器，光是消掉这一层，根据代码不同就是 2 倍到 5 倍、10 倍的提升。

**第二层，把数据从盒子里拿出来。**CPython 有个设计决定：一切皆对象，为了让所有对象的内存表示统一，每个对象都带一个头，后面跟载荷。这意味着你传递一个整数，传的其实是一个指向数据的指针。每次分配要记账，每次引用计数要加减，现代 CPU 最讨厌的就是这种追着指针满内存跑的行为，因为缓存会被打穿。把这层间接去掉，让数字直接躺在寄存器里，又是一个 10 倍。两层乘起来，量级已经很可观。

**第三层，用上向量。**现代 CPU 一条指令能同时处理 4 个、8 个、16 个甚至 32 个数据。Python 不暴露这个能力。Mojo 暴露了。

**第四层，用上多线程。**这里牵扯到 GIL。很多人抱怨 Python 的全局解释器锁让并行变得半残，拉特纳指出，GIL 和引用计数是死死绑在一起的，你没法只扔一个留一个。

**第五层，控制内存层次。**数据放在哪一级缓存、怎么布局，直接决定跑多快。这些在 Python 里根本没法表达。

这几层叠起来，就是那个吓人的数字。Mojo 给这些能力的语法还相当省事，比如你想让一段循环并行，写一句 parallelize 就行。

性能的意义不止是快。拉特纳给了两个理由，第二个更值得琢磨。

对企业和环境来说，性能等于成本和能耗。他开了个玩笑：**你可以说 Python 对环境不好，换到 Mojo 至少立刻好上十倍。**

对产品来说，性能等于体验。模型延迟降下来，产品团队就敢把模型做大；模型做大，用户感受到的东西就更好。这条因果链在机器学习行业里反复出现，很多时候模型能力的上限不取决于算法，取决于推理成本。

## 把解释器搬进编译器

Mojo 最有意思的设计在这里。

Python 的表达力很大一部分来自动态特性。你定义一个类，重载它的 *__add__*，加号就能用在这个类上。PyTorch 的自动微分、eager 模式，底下全是这类运行期的魔法。

问题是这些魔法在 GPU 上跑不了，就算能跑，性能也不划算。

Mojo 的做法是：既然 Python 内部本来就有一个解释器，那就让这个解释器在编译期运行。于是你得到编译期元编程。API 写法一点不变，那些动态特性被整体抬到编译期执行，生成的代码再去跑在加速器上。

他拿 Lisp 做类比。Lisp 最强大的地方在于，宏和普通代码是同一套东西，同一套语法、同一套概念。这让它简单、一致、可组合，你写的库在运行期和编译期都能用。

C++ 走的是反方向。模板系统跟运行期编程是两个平行宇宙，语法不同，概念不同，调试方式也不同。

> 它是意外变得强大的。它并不是被设计成图灵完备的，那是后来一路上被发现的事情。（克里斯·拉特纳）

他说自己写过足够多的 C++，有资格抱怨几句。这话不假，一个维护过 Clang 的人对 C++ 的感情大概最复杂。

他提到 Zig 也在这个方向上探索，把运行期能做的事挪到编译期。这条路这几年确实在聚拢共识。

技术上难在哪？Mojo 底下是一套全新的编译器设计，建在 MLIR 之上，还塞进了缓存、解释器、JIT。也就是说，编译器内部装了一个解释器，这个解释器现在还得把代码拿到目标机器上真跑一遍，看哪个版本最快，再缝回编译流程里继续。

编译过程本身变成了机器相关的搜索。这在传统编译器里是很少见的形态。

## 让人去记 TPU 的 tile size 是 128，本来就不合理

自动调优（auto-tuning）是 Mojo 另一个被低估的特性。

先看现实。硬件专家脑子里装着一堆魔数：缓存多大、寄存器多少个、向量多长最划算、GPU 有多少个 SM、warp size 是多少、TPU 的 tile 是 128。这些数字确实重要，写对了代码能快好几倍。

> 我的看法是，大多数普通程序员其实不想知道这些东西。（克里斯·拉特纳）

而且这些魔数还在变。A100、H100、Volta，算法是同一套，参数各不相同。再往远处看，你的代码半年前写在笔记本上，今天要搬到服务器上跑，明天可能换了张显卡。每次都要重调一遍，谁受得了。

自动调优的思路很朴素：既然外面有的是算力，何必让人去猜。你把算法结构写清楚，比如切成块、并行处理、按 128 或者别的粒度切，然后告诉编译器“这些方案你挑一个”，编译器在真实硬件上跑一遍，谁快用谁，结果缓存下来，下次直接用。

一步到位，写完就不用管了。换机器的时候它自己重新试。

这套机制解决的其实是个老问题：可移植性和极致性能长期矛盾。以前的做法是 *#ifdef* 一层套一层，代码越来越难看，测试的组合数爆炸。自动调优把这件事从“人手写分支”变成“机器做实验”，本质上是用算力换人的注意力。

## 缩进这件事，他愿意吵

中间有一段岔得很远，但挺有意思。

Lex 提到自己从 Emacs 换到 VS Code，顺带说了句考虑过 Vim 但没选，结果收到一堆 hate mail。拉特纳接话说，缩进就是比花括号好，而且他能给出理性论证。

他的论证有三条。Python 有几百万人用，占住了机器学习，这是事实层面的理由。花括号语言最后也要跑格式化工具，跑完的结果是缩进加花括号两套并存，既然要分组，为什么留两套。还有那些“只有一条语句时可以省略花括号”的设计空间，Python 式的缩进根本不需要面对。

Lex 想看数据：因为缩进写错导致的 bug，Python 里多还是 C++ 里多。两个人都觉得大概率不多，因为编辑器会替你缩进，你甚至不需要跟它较劲。

拉特纳承认这是宗教问题，但他乐于捅一下熊。捅完还是回到正题：

> 我们不想破坏 Python，也不想改它，或者打引号地“修复”它。我们爱 Python 本来的样子。我们的看法是，Python 只是还没写完。（克里斯·拉特纳）

这句话是理解 Mojo 定位的钥匙。它不打算取代 Python，它打算接着写。

## 类型是可选项，不是教条

Python 有类型，字符串、整数、字典，但全都活在运行期。所以你写函数不用标类型，代价是所有东西必须是同一种表示：指向带对象头的对象的指针。方法分发永远走同一条路径，不管接收者实际是什么。

Mojo 允许你渐进式地加类型。这个整数是 32 位还是 64 位，这个浮点是双精度还是单精度，编译器拿到这些信息就能做更好的优化，代码补全也更准，因为它知道这个对象上有哪些操作。

整套设计的重心落在“渐进”两个字上。你可以在任何地方加任意多的类型，不想加就不加，Python 那套动态语义照样完整保留。

拉特纳的立场是：类型不是对的东西，也不是错的东西，它是有用的东西。这个说法比两边阵营的吵架都更接近工程现实。

## Mojo 到底是什么

回到最基本的问题。它是编译型还是解释型？

答案是全都干。它可以是解释执行的，可以是 JIT 编译的，也可以是静态编译的。三种模式共存，因为不同场景的需求不一样：试验阶段要快，部署阶段要极致。

它是不是只服务 AI？AI first，是的，AI 的需求在推着它往前走。但目标是通用语言。判断标准他给的很简单：只要某个地方出现“上面一层 Python 做 API、下面一层 C 和 C++ 做计算”这种结构，那就是 Mojo 的机会。

这个模式在今天的软件世界里到处都是。科学计算、数据处理、金融建模、图像处理，全是这个形状。用一门语言从头写到尾，把两层合成一层，收益不只是省掉一门语言的学习成本，还有调试、部署、团队协作上的简化。

至于那个火苗后缀，拉特纳说得轻描淡写：不必赌上全部家当，两条路可以同时铺。这话听着像在讲文件扩展名，其实也是在讲 Mojo 和 Python 的关系。

这场对话发生的时候，Mojo 还没开放。但从他描述的技术路径看，方向是清楚的：**不要让使用者为了性能离开 Python，而是让 Python 长出自己的性能。**

至于能不能成，得看接下来几年有多少人愿意把生产代码搬过去。

- 去 mojolang.org 申请早期访问，找一段你熟悉的 Python 数值代码，原样跑一遍，再给热点函数加上类型标注跑一遍，自己看差距，别信二手数字。
- 翻一遍你手上的项目，找出“Python 调 C++ 扩展”的地方。这些地方就是最值得试点的位置，收益最直接，风险最可控。
- 如果暂时用不上 Mojo，也可以先借它的思路：把代码里那些手调的魔数（分块大小、向量长度、线程数）抽出来，写成可配置的参数，让程序自己在目标机器上试。哪怕用 Python 写脚本做这件事，也比凭经验拍脑袋强。
- 想理解编译期元编程，别从 C++ 模板入手，先看 Lisp 的宏，理解“代码即数据”之后再看 Mojo 的设计会顺很多。
- 把 GIL 和引用计数当成同一个问题看。这样你就能判断某个并发方案的宣传是真解决了问题，还是绕过去了。
- 缩进还是花括号，别在这上面花时间吵架了。真的，两个人都说这是宗教问题，信哪个都不影响你写出好代码。
