- cuda-oxide là một trình biên dịch thử nghiệm cho phép viết kernel GPU SIMT bằng Rust thành ngữ, gần với mức an toàn, và biên dịch trực tiếp mã Rust tiêu chuẩn sang PTX
- Chỉ dùng Rust mà không cần DSL hay binding ngôn ngữ ngoài, nhưng giả định người dùng hiểu về ownership, trait, generic, và các phần về async cũng cần kiến thức về
.await
- v0.1.0 là bản phát hành alpha ban đầu, nên cần dự kiến sẽ có lỗi, tính năng chưa hoàn thiện và các thay đổi API gây phá vỡ tương thích
- Ví dụ được chạy bằng
cargo oxide run vecadd, trong đó hàm #[kernel] bên trong #[cuda_module] thực hiện phép cộng vector bằng thread::index_1d()
#[cuda_module] sẽ nhúng artifact thiết bị vào binary host, đồng thời tạo loader có định kiểu và các phương thức chạy theo từng kernel
Cách dùng và mã được sinh ra
-
Bắt đầu nhanh
- Sau khi đáp ứng các điều kiện cài đặt, có thể build và chạy ví dụ bằng
cargo oxide run vecadd
- Hướng dẫn cài đặt có tại prerequisites
- Ví dụ định nghĩa hàm
#[kernel] là vecadd bên trong module #[cuda_module], lấy chỉ số bằng thread::index_1d() rồi ghi a[i] + b[i] vào DisjointSlice<f32>
- Ở phía host, dùng
CudaContext::new(0), stream mặc định, kernels::load(&ctx), rồi chạy kernel với DeviceBuffer::from_host, DeviceBuffer::<f32>::zeroed, LaunchConfig::for_num_elems(1024)
- Kết quả thực thi được lấy bằng
c.to_host_vec(&stream) và kiểm tra result[0] == 3.0
-
Cách #[cuda_module] hoạt động
#[cuda_module] nhúng artifact thiết bị đã được tạo vào binary host
- Nó tạo ra hàm
kernels::load có định kiểu và các phương thức thực thi theo từng kernel
- Khi cần tải artifact sidecar cụ thể hoặc tự tạo mã thực thi tùy biến, vẫn có thể tiếp tục dùng API mức thấp
load_kernel_module và cuda_launch!
Điều kiện và định hướng
- cuda-oxide hướng tới việc viết kernel GPU bằng hệ thống kiểu và mô hình ownership của Rust, đồng thời đặt an toàn làm mục tiêu hàng đầu
- Vì GPU có nhiều chi tiết tinh vi, cần đọc the safety model
- Đây không phải DSL mà là một backend sinh mã
rustc tùy biến, biên dịch Rust thuần sang PTX
- Hỗ trợ thực thi bất đồng bộ bằng cách cấu thành công việc GPU thành đồ thị
DeviceOperation được thực thi trì hoãn, lên lịch trên pool stream, rồi chờ kết quả bằng .await
- Tài liệu giả định người đọc đã quen với ownership, trait, generic của Rust; các chương về lập trình GPU async phía sau còn cần kiến thức về
async/.await và runtime như tokio
- Tài liệu tham khảo gồm The Rust Programming Language, Rust by Example, Async Book
- Bản phát hành v0.1.0 đang ở giai đoạn alpha ban đầu, nên cần dự kiến có lỗi, tính năng chưa hoàn thiện và thay đổi API gây phá vỡ tương thích
1 bình luận
Ý kiến trên Hacker News
Điều tôi đặc biệt tò mò là thời gian build sẽ so sánh ra sao. Hầu hết các crate Rust CUDA đều phụ thuộc vào CMake hoặc gọi
nvcc, nên việc biên dịch có thể chậm đến mức rất khó chịuTình cờ tuần trước khi profiling thời gian build, tôi thấy các công cụ như sccache có thể giảm đáng kể thời gian build lại nhờ cache đầu ra, nhưng chi phí của các lệnh gọi
nvcctùy chỉnh vẫn còn đó. Ví dụ, candle của Hugging Face cũng gọi lệnhnvcctùy chỉnh trong lúc biên dịch kernel: https://arpadvoros.com/posts/2026/05/05/speeding-up-rust-whi...Còn chuyện đa số crate Rust CUDA gọi CMake hay
nvccnên compile chậm thì cá nhân tôi chưa gặp quá nhiều. Nếu nhìn vào cratecuda_setupđược tạo để xử lý script build, nó chỉ là mộtbuild.rsđơn giản nên chỉ biên dịch lại khi file thay đổi, và thời gian compile của nó rất nhỏ nếu so với phần mã CPU của RustNếu đúng vậy thì quá tuyệt, nhưng cá nhân tôi đoán nó có lẽ gần với một công cụ bổ sung hơn. Tôi cũng muốn biết điểm khác biệt của cuda-oxide là gì, ngoài việc nó được NVIDIA kiểm soát hoàn toàn
Tile IR ở mức trừu tượng cao hơn một chút nên nhắm mục tiêu sẽ dễ hơn nhiều, chỉ phải đánh đổi ở những chỗ như fusion phần epilogue
[1] https://docs.nvidia.com/cuda/tile-ir/
[2] https://developer.nvidia.com/cuda/tile
Tôi nghĩ việc viết GPU kernel về bản chất là không an toàn. Do cách phần cứng hoạt động và vì luôn phải tối ưu đến cực hạn nên rất khó tạo ra một ngôn ngữ an toàn cho việc này
cudaFreethủ côngThứ hai, thay vì đối số
void*của C++ chỉ là một mảng con trỏ và chỉ kiểm tra số lượng, ở đâycuda_launch!ép buộc kiểu đối số kernelThứ ba là vấn đề aliasing khi ghi có thể thay đổi được. Trong C++, mã mà từ hai hay nhiều thread cùng ghi vào
out[i]với cùngivẫn compile được, nhưngDisjointSlicevàThreadIndexkhông có constructor công khai, và chỉ cho phép dùng các APIindex_1d,index_2d,index_2d_runtimehttps://github.com/NVlabs/cuda-oxide/blob/2a03dfd9d5f3ecba52...Thứ tư, trong C++ bạn có thể
cuda memcpystd::stringhay thực ra gần như bất kỳ POD nào để làm hỏng trạng thái, nhưng ở đây chỉ nhậnDisjointSlice, scalar và closure https://nvlabs.github.io/cuda-oxide/gpu-programming/memory-a...Chi tiết có trong https://nvlabs.github.io/cuda-oxide/gpu-safety/the-safety-mo... và https://nvlabs.github.io/cuda-oxide/gpu-programming/memory-a.... Dĩ nhiên nó không bắt được mọi thứ, nhưng có vẻ cung cấp nhiều rào chắn an toàn hơn hẳn so với các file
.curaw thuần túyLiệu đây có phải là ngôn ngữ thuận tiện cho lập trình GPU hay không thì còn phải xem, nhưng sẽ không ngạc nhiên nếu có thể tạo ra một API kiểu DSL đủ ổn để viết mã an toàn mà vẫn khai thác được mọi điểm kỳ quặc riêng của GPU. CUDA rốt cuộc có lẽ cũng là như vậy
Với những tác vụ an toàn nhưng song song mà khó nhét gọn vào mô hình
Send/Synccủa Rust, sẽ cần một chút sự thô rápSẽ tốt nếu có thể tận dụng mô hình bộ nhớ hay mô hình ownership với mức ma sát thấp. Nhưng nếu vì thế mà trải nghiệm sử dụng trở nên quá bất tiện, thì tôi không muốn kiểu đó
Đường cơ sở ở đây theo tôi là cách Cudarc làm hiện nay. Quản lý bộ nhớ không can thiệp quá nhiều, chỉ là cú pháp mệnh lệnh bọc quanh FFI và vài dòng script build gọi
nvcckhi kernel thay đổiNói thêm là tôi khá thích Slang
[0]: https://shader-slang.org/
Ví dụ như descriptor set, thanh ghi tài nguyên, hay các giới hạn dispatch
Các ngôn ngữ shader cũng thân thiện hơn với người dùng về mặt tính năng. Hơn nữa, NVIDIA đã dùng Slang trong production rồi, và những người đó sẽ không viết lại pipeline shader của họ bằng Rust đâu
Thứ duy nhất tôi tìm được là nội dung dưới đây
https://www.adacore.com/case-studies/nvidia-adoption-of-spar...
https://www.youtube.com/watch?v=2YoPoNx3L5E
Dù vậy tôi vẫn cố bỏ qua và đọc tài liệu, rồi đúng lúc bắt đầu thấy hứng thú vì có IR tùy chỉnh thì lại gặp kiểu câu như “triển khai MLIR là C++ kèm TableGen, build system bắt bạn phải compile cả LLVM và những phiên debug khiến bạn nghi ngờ lựa chọn sự nghiệp của mình”, từ đó tôi khó mà tiếp tục xem ngành này một cách nghiêm túc
Điều này trông đúng kiểu dogfooding mà ta có thể kỳ vọng ở một công ty thổi phồng AI
cuda-oxide làm cho trường hợp phổ biến là “mỗi thread ghi một phần tử” trở nên an toàn về mặt cấu trúc, còn các trường hợp hiếm hơn như shared memory, warp shuffle hay hardware intrinsic thì yêu cầu
unsafevới các hợp đồng được ghi rõ trong tài liệu, và các tính năng tuyến đầu như TMA, tensor core hay giao tiếp cấp cluster thì để hoàn toàn thủ công cho tương xứng với độ phức tạp của phần cứngNhưng điều này lại không quá đậm chất Rust. Trong Rust, nếu các trừu tượng hiện có không hợp với bài toán thì người ta sẽ tạo các trừu tượng an toàn mới. Rust for Linux là ví dụ như vậy
Nếu không an toàn thì tôi tự hỏi lý do dùng Rust là gì. Việc cung cấp API
unsafecho những người cần vắt kiệt hiệu năng cuối cùng thì ổn, nhưng nó không nên là mặc địnhNó khiến tôi liên tưởng đến các thư viện không gian người dùng cho các API như
io_uringhay Vulkan. Thiết kế API an toàn cho những thứ đó khá khó, và thực tế đã có những nỗ lực không soundRào cản serialize/byte giữa hai bên cũng vậy
Ví dụ, tôi muốn biết liệu kiểm tra biên mảng có thể làm phát sinh thêm việc dùng thanh ghi, từ đó làm giảm mức độ đồng thời của kernel hay không