一家名为 VectorWare 的初创公司,居然把 Rust 的 async/await 成功搬到了 GPU 上运行。
你没听错,就是那个我们在后端服务里用得飞起的异步编程模型,现在直接在显卡的核心里跑起来了。
而且,不仅仅是“能用”,他们甚至展示了如何利用现有的 Rust 生态库,在 GPU 上调度并发任务。
有点离谱
GPU 编程的旧梦魇
先得聊聊现状。
传统的 GPU 编程是个什么逻辑?
一般就是就是简单粗暴的数据并行:你写一段代码,成千上万个线程同时跑,大家对着不同的数据做同样的操作。
这在图形渲染、矩阵乘法这些场景下,简直是神器。
但随着程序越来越复杂,这种“大锅饭”模式不够用了,于是出现了“Warp specialization”
简单理解就是,让 GPU 的不同部分干不同的事儿:一组线程负责加载数据,另一组负责计算,还有一组负责存结果。
听起来很美对吧?
但这玩意儿有个巨大的坑:没有语言和运行时的支持。
开发者得像在手写汇编一样,手动管理并发、手动同步。这跟以前在 CPU 上写多线程代码一样,稍不留神就是死锁或者数据竞争,调试起来能让人怀疑人生。
现有的解药都不够劲
当然,也不是没人想解决这个问题。
Google 的 JAX 把程序当成计算图,OpenAI 的 Triton 用块来组织计算,NVIDIA 最近也推了 CUDA Tile。它们都在试图把开发者从繁琐的手动管理中解放出来。
但这几家都有个共同的毛病:你得学新东西。
你得用特定的 DSL(领域特定语言),得适应全新的编程范式。这就导致了一个尴尬的局面——代码复用极其困难。你现有的 CPU 库基本没法直接用;现有的 GPU 库呢,它们还是老一套手动管理,跟这些新框架格格不入。
这就好比你想换辆更快的跑车,结果发现所有操作都变了,还得重新考驾照。
Rust 的降维打击
VectorWare 干的事儿,就是直接绕过了这些新造的轮子。
他们发现,Rust 的 Future trait 和 async/await,简直就是为这种场景量身定做的。
为什么这么说?
一个 Future 代表一个可能还没完成的计算。它不关心自己是在 CPU 线程上跑,还是在 GPU 的 Warp 上跑。它的核心操作就是 poll(轮询),返回“完成”或者“待定”。
这种极度的抽象,让同一个异步代码可以在不同的环境里被驱动。
更绝的是,Rust 的所有权模型把数据限制写得清清楚楚:
谁拥有数据,谁能借用,能不能在并发单元间传递,编译器都给你安排得明明白白。这比手动管理并发安全了不知道多少倍。
说白了,以前那些复杂的 Warp specialization,本质上就是手写的状态机。而在 Rust 的 async/await中,编译器自动帮你生成状态机。
既然都是状态机,为什么不能在 GPU 上跑?
VectorWare 证明了一点:完全没问题。
看看这代码,简直像做梦
光说不练假把式。VectorWare 贴出的代码,看起来就像是在写普通的 Rust 后端代码。
// Simple async functions that we will call from the GPU kernel below.
async fn async_double(x: i32) -> i32 {
x * 2
}
async fn async_add_then_double(a: i32, b: i32) -> i32 {
let sum = a + b;
async_double(sum).await
}
// ... more code ...
#[unsafe(no_mangle)]
pub unsafe extern "ptx-kernel" fn demo_async(
val: i32,
flag: u8,
) {
// Basic async functions with a single await execute correctly on the device.
let doubled = block_on(async_double(val));
// Chaining multiple async calls works as expected.
let chained = block_on(async_add_then_double(val, doubled));
// ... more code ...
}
你看,链式调用、条件判断、多步骤工作流,甚至第三方库的组合子,全都能在 GPU Kernel 里直接用。他们甚至把嵌入式领域常用的 Embassy 执行器移植到了 GPU 上,实现了多任务的并发调度。
这意味着什么?意味着你不需要为了 GPU 重新发明一套生态,你现有的 Rust 技能栈,很大一部分可以直接平移过去。
这相当于把 CPU 的开发体验“复制粘贴”到了显卡上。
不过坑也不少
但这事儿真就这么完美了吗?
老实讲,并没有。
评论区里的老鸟们一眼就看出了问题。有人直接指出,这反转了并发的心理模型。CPU 的异步是为了等 I/O,GPU 的异步是为了管理并行工作。直觉不能直接照搬。
技术上的硬伤也不少。
第一,协作式调度的陷阱。
Future 是协作式的,如果一个任务死活不让出 CPU(这里是 GPU),其他任务就得饿死。这在没有中断机制的 GPU 上尤其危险。
第二,轮询的代价。
GPU 没有中断,执行器得不停地轮询看任务能不能推进。这就像你为了等个快递,每秒钟都去门口看一眼,效率肯定不如有人敲门通知你。虽然可以用 nanosleep 之类的 API 缓解,但终究不如硬件级调度来得高效。
第三,寄存器压力。
维护这些 Future 的状态机需要额外的寄存器。GPU 的寄存器可是寸土寸金,这可能会直接降低占用率,进而影响最终性能。
还有个灵魂拷问:性能到底怎么样?
相比于 Triton 那种在编译期就把一切算计好的 AOT(提前编译)模式,Rust 这种运行时调度的模式,会不会带来无法接受的 overhead?毕竟在 AI 训练这种场景下,每一滴性能都要榨干。
方向对了
尽管还有这么多问号,我个人觉得,VectorWare 的这次尝试依然意义重大。
它证明了我们不需要为了高性能而牺牲开发体验,也不需要为了新硬件而抛弃现有的软件遗产。NVIDIA 也在搞类似的 stdexec(针对 C++),但 Rust 的优势在于,这些东西现在已经存在了,而且生态已经相当成熟。
也许现在的实现还不够完美,性能可能依旧打不过手写的 CUDA C++。但它指了一条路:用更高级、更安全的抽象,去驯服越来越复杂的硬件。
如果以后写 GPU 程序真的能像写 Web 服务一样爽,那这场革命,哪怕慢一点,也值得等。
参考链接:
https://www.vectorware.com/blog/async-await-on-gpu/