From 9886acbf663cbc3ab30b088b251def5f1bbd173e Mon Sep 17 00:00:00 2001 From: Brandon Ros Date: Mon, 14 Sep 2026 17:52:29 -0400 Subject: [PATCH 1/2] Add GPU-independent PTX exporter with vector and SHA-256 kernels --- .github/workflows/ptx_export.yml | 31 ++++++++++++++++++++++++++ Cargo.lock | 15 +++++++++++++ Cargo.toml | 2 ++ examples/ptx_export/Cargo.toml | 12 ++++++++++ examples/ptx_export/README.md | 16 +++++++++++++ examples/ptx_export/kernels/Cargo.toml | 12 ++++++++++ examples/ptx_export/kernels/src/lib.rs | 29 ++++++++++++++++++++++++ examples/ptx_export/src/main.rs | 21 +++++++++++++++++ 8 files changed, 138 insertions(+) create mode 100644 .github/workflows/ptx_export.yml create mode 100644 examples/ptx_export/Cargo.toml create mode 100644 examples/ptx_export/README.md create mode 100644 examples/ptx_export/kernels/Cargo.toml create mode 100644 examples/ptx_export/kernels/src/lib.rs create mode 100644 examples/ptx_export/src/main.rs diff --git a/.github/workflows/ptx_export.yml b/.github/workflows/ptx_export.yml new file mode 100644 index 00000000..8f01333a --- /dev/null +++ b/.github/workflows/ptx_export.yml @@ -0,0 +1,31 @@ +name: Export portable PTX + +on: + pull_request: + branches: [experiment/cuda13.3-llvm21, "stack/**"] + workflow_dispatch: + push: + branches: [poc/portable-ptx-export] + +permissions: + contents: read + +jobs: + export: + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: DeterminateSystems/nix-installer-action@main + - name: Compile Rust kernels without a GPU + run: nix develop .#v21 --command cargo run -p ptx_export --features llvm21 -- artifacts/ptx + - name: Record provenance + run: | + git rev-parse HEAD > artifacts/ptx/source-commit.txt + nix develop .#v21 --command rustc -Vv > artifacts/ptx/rustc-version.txt + sha256sum artifacts/ptx/rust_kernels.ptx > artifacts/ptx/SHA256SUMS + - uses: actions/upload-artifact@v4 + with: + name: rust-ptx + path: artifacts/ptx/ + if-no-files-found: error diff --git a/Cargo.lock b/Cargo.lock index 1afa046a..5e53c7b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2584,6 +2584,14 @@ dependencies = [ "strum 0.26.3", ] +[[package]] +name = "ptx-export-kernels" +version = "0.1.0" +dependencies = [ + "cuda_std", + "sha2", +] + [[package]] name = "ptx_compiler" version = "0.1.1" @@ -2591,6 +2599,13 @@ dependencies = [ "cust_raw", ] +[[package]] +name = "ptx_export" +version = "0.1.0" +dependencies = [ + "cuda_builder", +] + [[package]] name = "pxfm" version = "0.1.26" diff --git a/Cargo.toml b/Cargo.toml index 108b31a6..77ceaa42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,8 @@ members = [ "examples/sha2_crates_io/kernels", "examples/vecadd", "examples/vecadd/kernels", + "examples/ptx_export", + "examples/ptx_export/kernels", "samples/introduction/async_api", "samples/introduction/async_api/kernels", diff --git a/examples/ptx_export/Cargo.toml b/examples/ptx_export/Cargo.toml new file mode 100644 index 00000000..9c2b34cf --- /dev/null +++ b/examples/ptx_export/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "ptx_export" +version = "0.1.0" +edition = "2024" +publish = false + +[features] +default = [] +llvm21 = ["cuda_builder/llvm21"] + +[dependencies] +cuda_builder = { workspace = true } diff --git a/examples/ptx_export/README.md b/examples/ptx_export/README.md new file mode 100644 index 00000000..e227dde7 --- /dev/null +++ b/examples/ptx_export/README.md @@ -0,0 +1,16 @@ +# Export PTX without a GPU host application + +Compile vector-addition and SHA-256 kernels without linking a CUDA host +application or launching a GPU. Compilation requires the Linux Rust-CUDA +toolchain, CUDA toolkit, and NVVM libraries. + +```sh +nix develop .#v21 --command cargo run -p ptx_export --features llvm21 -- artifacts/ptx +``` + +The output is `rust_kernels.ptx`; `final-module.ll` records the NVVM input. +The builder selects `compute_100` with LLVM 21 and `compute_75` with LLVM 7. +The kernels accept explicit pointer/count arguments. SHA-256 reads and writes +32 bytes per work item. Input and output buffers must not overlap. + +Export success does not establish another PTX consumer's numerical correctness. diff --git a/examples/ptx_export/kernels/Cargo.toml b/examples/ptx_export/kernels/Cargo.toml new file mode 100644 index 00000000..8495a999 --- /dev/null +++ b/examples/ptx_export/kernels/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "ptx-export-kernels" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +cuda_std = { path = "../../../crates/cuda_std" } +sha2 = { version = "0.10", default-features = false } + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/examples/ptx_export/kernels/src/lib.rs b/examples/ptx_export/kernels/src/lib.rs new file mode 100644 index 00000000..9f02d2b3 --- /dev/null +++ b/examples/ptx_export/kernels/src/lib.rs @@ -0,0 +1,29 @@ +use cuda_std::prelude::*; +use sha2::{Digest, Sha256}; + +/// Add `count` floats. All pointers address buffers of at least `count` elements. +/// +/// # Safety +/// Inputs must be readable and output writable, with no overlapping buffers. +#[kernel] +pub unsafe fn rust_vecadd(a: *const f32, b: *const f32, out: *mut f32, count: u32) { + let i = thread::index_1d(); + if i < count { + unsafe { *out.add(i as usize) = *a.add(i as usize) + *b.add(i as usize) }; + } +} + +/// Hash `count` independent 32-byte messages into 32-byte digests. +/// +/// # Safety +/// Input/output each address `count * 32` bytes and must not overlap. +#[kernel] +pub unsafe fn rust_sha256_32(input: *const u8, out: *mut u8, count: u32) { + let i = thread::index_1d(); + if i < count { + let offset = i as usize * 32; + let message = unsafe { core::slice::from_raw_parts(input.add(offset), 32) }; + let digest = Sha256::digest(message); + unsafe { core::ptr::copy_nonoverlapping(digest.as_ptr(), out.add(offset), 32) }; + } +} diff --git a/examples/ptx_export/src/main.rs b/examples/ptx_export/src/main.rs new file mode 100644 index 00000000..b8b82079 --- /dev/null +++ b/examples/ptx_export/src/main.rs @@ -0,0 +1,21 @@ +use cuda_builder::CudaBuilder; +use std::{env, fs, path::PathBuf}; + +fn main() -> Result<(), Box> { + let output = PathBuf::from( + env::args_os() + .nth(1) + .unwrap_or_else(|| "artifacts/ptx".into()), + ); + fs::create_dir_all(&output)?; + let output = output.canonicalize()?; + let kernels = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("kernels"); + let ptx = CudaBuilder::new(kernels) + .copy_to(output.join("rust_kernels.ptx")) + .final_module_path(output.join("final-module.ll")) + .emit_llvm_ir(true) + .build() + .map_err(|error| std::io::Error::other(format!("PTX compilation failed: {error:?}")))?; + println!("Exported {}", ptx.display()); + Ok(()) +} From 40675981a60bfbdbea41fe8fbc79ea9f0f875b2a Mon Sep 17 00:00:00 2001 From: Brandon Ros Date: Mon, 14 Sep 2026 21:10:32 -0400 Subject: [PATCH 2/2] Run PTX export checks for the native stack trunk --- .github/workflows/ptx_export.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ptx_export.yml b/.github/workflows/ptx_export.yml index 8f01333a..d2363406 100644 --- a/.github/workflows/ptx_export.yml +++ b/.github/workflows/ptx_export.yml @@ -2,7 +2,7 @@ name: Export portable PTX on: pull_request: - branches: [experiment/cuda13.3-llvm21, "stack/**"] + branches: [main, experiment/cuda13.3-llvm21, "stack/**"] workflow_dispatch: push: branches: [poc/portable-ptx-export]