cuda-oxide 把 Rust 變成 PTX 核心
我拆 cuda-oxide 的 Rust 轉 PTX 做法,最後給你一份可直接改的 GPU Rust 模板。

我拆 cuda-oxide 的 Rust 轉 PTX 做法,最後給你一份可直接改的 GPU Rust 模板。
我用 GPU 工具鏈很久了,真的很少看到哪個方案會讓我一開始就放心。通常都是這樣:主程式是 Rust,device 端又跑去另一套語言,launch 還要再包一層,最後你花半天只是在追一個「CPU 上能編、進 kernel 就壞掉」的型別問題。最煩的是,程式明明是你寫的,到了 GPU 那一段卻像借來的。cuda-oxide 讓我停下來看,不是因為它很華麗,而是因為它想把這條討厭的縫補起來。我先懷疑,反而覺得它比較像真的東西。
我會注意到它,主要是因為它不是再丟一個「Rust 包裝 CUDA」的小把戲。它想做的是把標準 Rust 直接編到 PTX,host 跟 device 還放在同一個 workspace 裡,整條路徑都走 Rust-native。這種東西我通常第一反應是:你先別急著吹,先把邊界講清楚。好消息是,這個專案自己也沒裝成熟,README 直接說它還在 alpha,API 會變。老實講,我比較喜歡這種誠實。
觸發我整理這份拆解的來源,就是 NVlabs/cuda-oxide 的 GitHub 倉庫,還有它正在寫的 cuda-oxide book。我下面拆的不是新聞,是我把它的 README、目錄結構、工具鏈說明,整理成開發者真的能拿去試的版本。
我先在意的不是語法,是那條討厭的分裂線
訂閱 AI 趨勢週報
每週精選模型發布、工具應用與深度分析,直送信箱。不定期,不騷擾。
不會寄垃圾信,隨時可取消。
single-source compilation:host 和 device code 放在同一個檔案,透過一次 cargo oxide build 一起處理
翻譯一下就是:它不想讓你一邊寫主程式、一邊再養一個 GPU 子世界。你不用把 host crate、device crate、wrapper crate 三個東西拆開來互相餵資料。cuda-oxide 想把這些東西收回同一個 Rust workspace,然後把 device 端編成 PTX。這件事看起來很小,但其實很狠,因為它直接砍掉我最常踩到的那條線。

我以前做 GPU 專案最常卡住的地方,不是 kernel 算法,而是「語言邊界」本身。型別在 CPU 端很漂亮,一跨過去就變另一種生物;錯誤訊息還會假裝自己很有幫助,結果只是把你丟回 FFI、build script、macro 地獄。那種感覺很像你明明在寫同一個功能,卻得同時維護兩個思考模型。cuda-oxide 這個方向對我來說有吸引力,因為它不是在包裝那條分裂線,它是想把線直接拔掉。
實操上我會怎麼看?我不會先問「能不能取代 CUDA」。那太大了,也太容易自我感動。我會先問三件事:第一,host 跟 kernel 能不能在同一個模組裡協作;第二,資料型別跨過去會不會還是你熟悉的 Rust;第三,專案裡是不是有一堆膠水在偷偷吃掉時間。如果答案是後兩個都不漂亮,那再快的 kernel 也救不了整個 repo。
- 先找「一個 workspace、兩個世界」能不能真的成立。
- 先看型別邊界,不要先看效能數字。
- 如果你團隊最痛的是維護成本,這種整合型工具才有意義。
#[kernel] 不是噱頭,重點是編譯器真的看得懂
rustc codegen backend:把 #[kernel] 函式編成 CUDA PTX也就是說,kernel 不是某種外掛 DSL,也不是「看起來像 Rust 的假語法」。它還是 Rust 函式,只是最後會被編譯器往下轉成 GPU 能跑的 PTX。這差很多。因為只要你真的碰過 GPU 工具鏈,就知道最討厭的不是寫 kernel,而是你得同時記住一堆特殊簽名、特殊 launcher、特殊限制,最後整段程式像在跟編譯器談條件交換。
我喜歡 cuda-oxide 的地方,是它把「語法好看」跟「編譯器理解」分開處理。很多工具都很愛做表面功夫:macro 寫得很順、API 看起來很親切,結果一碰到泛型、closure capture、第二個 kernel 變體,整個外殼就裂掉。cuda-oxide 不是那種路線。它的重點是編譯器後端要真的知道怎麼把 Rust 降到 PTX,而不是只靠一些魔術字串撐場面。
我之前踩過一個很典型的坑:demo 階段很美,因為只有單一 kernel、固定參數、固定資料流。等你開始加泛型、加 closure、加不同輸入型別,原本說得很漂亮的 macro API 就開始露出破綻。最後你還是得回頭手寫 launch plumbing。這也是為什麼我會把 cuda-oxide 的重點放在「編譯器契約」而不是「macro 手感」。如果你搞不清楚 lowering 規則,後面只會更痛。
實操寫法很簡單:先讀它的 kernel 模型,不要先研究花俏功能。你要先知道一個 Rust 函式怎麼變成 PTX、參數怎麼傳、哪些普通 Rust 能保留、哪些不行。能用一句話講清楚這條路徑,再談要不要押專案。講不清楚,就先別碰大規模遷移。
- 把 kernel 當成「會被編譯器理解的 Rust」,不是外來語法。
- 先測泛型、closure、捕獲變數,再談導入。
- 真正的契約在 compiler backend,不在表層 macro。
host runtime 才是大多數 GPU 專案真正卡住的地方
host-side runtime:處理記憶體管理、pinned host transfer、kernel launching
翻譯一下就是:cuda-oxide 不只想編 kernel,它也想把 host 端那些煩到不行的事一起接走。像是配置 device memory、搬資料、pin host buffer、啟動 kernel,這些東西平常都不會出現在宣傳頁,但真正把專案磨爛的,常常就是這些地方。

我看它的 crate 切法,像是 cuda-core 跟 cuda-async,我會把它理解成:前者偏向核心 runtime primitive,後者偏向非同步裝置操作。這個分法有價值,因為它把「我能不能 launch 一個 kernel」跟「我能不能把整條 pipeline 接起來」分開了。很多 GPU 工具卡死就是卡在這裡,demo 可以跑,但只要資料流一多,host orchestration 就變成一坨 callback 和同步點。
我以前做過一條 GPU 資料管線,kernel 本身其實沒什麼問題,真的讓人想翻桌的是 host 端。每次傳輸、每次同步、每次 stream 管理都要手工接線,結果整個專案像在拼樂高,但每塊都是自訂規格。這種時候,如果 runtime 層能用 Rust 型別把流程講清楚,價值其實比 kernel 語法還高。因為你不是在追求「能跑」,你是在追求「能維護」。
實操上,我會直接拿 host API 當審核重點:能不能不碰 raw FFI 就完成配置、搬運、啟動、取回結果?能不能讓你看得出資料何時在 host,何時在 device?如果答案是否定的,那它只是把複雜性包得比較好看,不是真的少。README 裡提到的 CudaContext、DeviceBuffer、LaunchConfig、typed module loader,我覺得方向是對的,因為 host 端應該像控制平面,不是第二套語言。
- 先檢查 host API 是否還要你理解太多 CUDA 細節。
- 先看資料搬運流程,再看 kernel launch。
- runtime 要像控制平面,不要像另一個 FFI 黑盒。
非同步 GPU 工作如果不順,整個抽象就只是換皮
stream 消失,{kernel}_async回傳 lazyDeviceOperation,最後靠.sync()或.await觸發
也就是說,它不是只想把 PTX 編出來而已,它想讓 GPU 工作變成可以在 Rust 裡組合的延遲運算。這點我很在意。因為同步 demo 很容易寫得漂亮,真正麻煩的是你要同時處理傳輸、launch、後處理,還要把吞吐量撐起來。這時候如果 API 設計不好,你的 stream 會慢慢變成隱性架構,最後每個模組都知道 stream,大家都知道要 sync,但沒人知道為什麼。
cuda-oxide 在 async 路線上,明講 cuda-async 是核心。這種設計我會給過,因為它至少知道複雜性應該被集中處理,而不是散落到每個 kernel 呼叫點。DeviceOperation 這種 lazy 物件也很有意思,因為它把 GPU 工作描述成「可組合的待執行步驟」,不是一串硬塞進 stream 的命令。
我以前看過太多 GPU 專案,最後最貴的不是算力,是同步點管理。你本來只是想把兩個 kernel 串起來,結果一個 sync 不小心插在中間,整個 pipeline 直接失去重疊效果。更慘的是,程式看起來還是對的,只是慢得很誠實。這也是為什麼我會先測 async,而不是等到最後才碰。因為如果 async 抽象不順,後面所有高階功能都只是更漂亮的包裝紙。
實操寫法:先做最小 pipeline,upload、launch、download、驗證,一次跑完。然後看 {kernel}_async 能不能自然串接,不要把你逼成 state machine。若它能讓你把多步驟工作寫成可讀的鏈式流程,那才算真的有用。若不行,那就只是 stream 管理換了名字。
- 先測 async 串接,不要等到專案後期。
- 確認同步邊界清楚,不要藏在抽象裡。
- 看的是可組合性,不只是吞吐量。
編譯管線才是這專案真正的本體
Rust → Rust MIR → Pliron IR → LLVM IR → PTX
翻譯一下就是:這不是一個只賣語法糖的專案,它是在賣編譯器架構。這件事很重要,因為長期能不能維護,通常不是看你今天能不能跑,而是看你中間層怎麼設計。架構亂,後面每個 bug 都會變成尋寶遊戲。
README 還提到可以用 cargo oxide pipeline vecadd 看完整流程,這種透明度我很買單。因為如果你真的要把它放進專案,你一定會想知道 lowering 在哪裡發生、最佳化在哪裡做、device-specific lowering 又從哪裡開始。沒有這些資訊,出了問題只會一路往下猜,猜到最後人都沒了。
我也會特別注意工具鏈限制。專案把 pinned nightly Rust、CUDA Toolkit 12.x+、Clang、LLVM 21 以上這些要求直接講出來,這不算輕鬆,但算務實。GPU 編譯本來就很吃環境,裝得起來不代表能穩定重現。與其假裝你的系統預設都沒問題,不如老實說版本就是要對。這點我反而尊重。
實操上,如果你要試,先問團隊能不能接受 pinned nightly、CUDA 安裝、LLVM 版本對齊。如果你們連容器或 Nix shell 都不想碰,那你會很痛苦。相反地,如果你們本來就有固定 GPU build 環境,那這套可能比你現在東拼西湊的流程更乾淨。它還有 Nix 的 dev shell 方向,這對編譯器專案來說很實際,因為可重現性常常比速度更重要。
它講「安全」,但我會把那個詞前面加一個問號
safe(ish), idiomatic Rust
也就是說,它不是在保證 GPU 上一切都絕對安全。它比較像是在說:我盡量保留 Rust 的紀律,但 GPU 上還是有 shared memory、atomic、barrier、warp op 這些硬骨頭。那個 ish 很誠實,我反而喜歡。因為只要你真的寫過 GPU,就知道「完全安全」通常只是把鋒利的地方藏起來,不是把問題消掉。
README 提到的 device-side 抽象很多,像是 type-safe indexing、shared memory、scoped atomics、barriers、TMA、warp/cluster ops。這些東西每一個都能讓抽象翻車,所以它敢把硬體層的東西直接列出來,我覺得是好事。像 NVIDIA CUDA Programming Guide 裡那些 atomics 和 barriers,本來就不是可以假裝不存在的東西。
我待過的 GPU 抽象層很多都犯同一個毛病:把尖角包起來,然後假裝尖角不見了。結果只是把風險搬到別的地方。cuda-oxide 如果能做到的是「減少你出錯的方式」,而不是「讓你完全不可能出錯」,那就已經很有價值了。GPU 程式的安全感,本來就不是零風險,而是你能不能清楚知道哪裡會炸。
實操上,我會把它的安全敘事當成部分保護,不是絕對保證。先讀 SIMT、barrier、atomic 的章節,再拿同步相關的 kernel 做測試。凡是依賴同步的地方,都要補測試。這不是悲觀,這是 GPU 開發的基本禮貌。你要做的不是相信它永遠不錯,而是把錯的空間壓小。
它列出的例子像 vector add、generic kernels、device-side Ord::cmp lowering、GEMM、Blackwell tensor cores、async pipeline,這些都不是裝飾品。這些例子代表編譯器已經得在真實 GPU 模式上活下來,不然根本撐不起這些範圍。
我會怎麼把它放進自己的 repo
如果是我明天就要試 cuda-oxide,我會照這個順序:先做一個很無聊的 vector add 或 map kernel。先確認 host/device 共用 workspace 是真的順。再測一個帶 closure capture 的泛型 kernel。接著如果我的工作負載需要,再看 async 組合。最後才碰 tensor core、cluster、device FFI 這些比較兇的功能。
我不會一開始就挑 repo 裡最炫的例子。那種做法很容易騙自己,以為工具成熟了,其實只是某個 demo 剛好跑過。先從 boring 的東西開始,boring 能跑,後面才有資格談進階功能。這是我看編譯器專案的老習慣,沒這個習慣很容易被展示稿帶走。
我也會盯著平台支援。README 明講目前以 Linux 為主,還提到 Ubuntu 24.04。這很正常,但也代表我不會把 macOS 或 Windows 當起點。專案如果老實說它先支援哪裡,我反而比較安心。比起「到處都能跑」這種空話,我更想知道哪裡是它真的測過的地方。
另外一個我會查的是,自己的程式到底會不會太依賴現在這套 macro 和 backend 形狀。專案還在 active development,API 變動是可預期的。這沒什麼好裝,實驗期就該這樣。你要分清楚哪些層會 churn:macro、runtime API、還是 lowering internals。這決定你要不要把它放進正式路徑。
可抄的模板
# cuda-oxide 導入模板(可直接改成你自己的 repo 筆記)
## 1) 我想解的問題
我想用 Rust 寫 GPU kernel,並且把 host 與 device code 放在同一個 workspace。
## 2) 第一個驗證目標
先做一個最無聊的 kernel:
- vector add
- map over slice
- reduction
## 3) 導入前先回答的問題
- 我能不能把 kernel 寫成一般 Rust 函式,然後用 #[kernel] 標記?
- host 端能不能不用 raw CUDA FFI 就 launch?
- 泛型與 closure capture 能不能正常工作?
- DeviceBuffer 搬資料需不需要額外膠水?
- async GPU 工作能不能自然串起來,不用手工管 stream?
## 4) 最小 kernel 範本
use cuda_device::{cuda_module, kernel, thread, DisjointSlice};
use cuda_core::{CudaContext, DeviceBuffer, LaunchConfig};
#[cuda_module]
mod kernels {
use super::*;
#[kernel]
pub fn map T>(f: F, input: &[T], mut out: DisjointSlice) {
let idx = thread::index_1d();
let i = idx.get();
if let Some(out_elem) = out.get_mut(idx) {
*out_elem = f(input[i]);
}
}
}
fn main() {
let ctx = CudaContext::new(0).unwrap();
let stream = ctx.default_stream();
let data: Vec = (0..1024).map(|i| i as f32).collect();
let input = DeviceBuffer::from_host(&stream, &data).unwrap();
let mut output = DeviceBuffer::zeroed(&stream, 1024).unwrap();
let module = kernels::load(&ctx).unwrap();
let factor = 2.5f32;
module.map(
&stream,
LaunchConfig::for_num_elems(1024),
move |x: f32| x * factor,
&input,
&mut output,
).unwrap();
let result = output.to_host_vec(&stream).unwrap();
assert!((result[1] - 2.5).abs() < 1e-5);
}
## 5) 導入檢查清單
- [ ] Rust nightly 已固定版本
- [ ] CUDA toolkit 已安裝
- [ ] LLVM / llc 版本已確認
- [ ] clang / libclang 可用
- [ ] cargo oxide doctor 可通過
- [ ] 一個 kernel 可編成 PTX
- [ ] 一次 host launch 可成功
- [ ] 一條 async pipeline 可跑通
## 6) 上線規則
如果 compiler 變動太快,先把 cuda-oxide 放在 feature flag 後面,等 API 穩一點再擴大使用。
## 7) 需要寫進你 repo 的文件
- 支援的 toolchain 版本
- 一個可跑的 kernel 範例
- 一個 async 範例
- 已知限制
- 必要時回退到原生 CUDA 的路徑我最想從 cuda-oxide 抄走的,不是「Rust 可以跑 GPU」這句口號,而是它把 compiler、runtime、kernel model 都攤在開發者面前。這種工具才不容易變成 folklore。我要的是能檢查、能測、能慢慢信任的工具鏈,不是只有一份好看的 README。
如果你現在卡在 GPU 專案的語言分裂、host orchestration、或 async 流程管理,這套值得你花時間看。原始來源是 NVlabs/cuda-oxide 與它的 book;上面那份模板是我根據它的結構整理出的可抄版本,不是原文照搬。