Skip to content

NVRTC Runtime Compilation ​

NVRTC (NVIDIA Runtime Compilation) lets you compile CUDA C/C++ source code to PTX at application runtime — no nvcc binary required at compile time.

Creating a Program ​

zig
const cuda = @import("cuda");

const kernel_src =
    \\extern "C" __global__ void saxpy(float a, float* x, float* y, int n) {
    \\    int i = blockIdx.x * blockDim.x + threadIdx.x;
    \\    if (i < n) y[i] = a * x[i] + y[i];
    \\}
;

var prog = try cuda.nvrtc.Program.create(kernel_src, "saxpy.cu");
defer prog.deinit();

Compiling ​

zig
// Minimal: compile with no extra flags
try prog.compile(&.{});

// With architecture targeting
try prog.compile(&.{
    "--gpu-architecture=compute_80",
    "--std=c++17",
    "--extra-device-vectorization",
});

If compilation fails, the error log is captured and returned:

zig
prog.compile(&.{}) catch |err| {
    const log = try prog.getLog(allocator);
    defer allocator.free(log);
    std.debug.print("NVRTC error:\n{s}\n", .{log});
    return err;
};

Extracting PTX ​

zig
const ptx = try prog.getPtx(allocator);
defer allocator.free(ptx);

Loading and Launching ​

zig
var module = try cuda.Module.load(ptx);
defer module.deinit();

var kernel = try module.getFunction("saxpy");

const n: i32 = 65536;
const a: f32 = 2.0;
var d_x = try cuda.DeviceBuffer(f32).init(allocator, @intCast(n));
var d_y = try cuda.DeviceBuffer(f32).init(allocator, @intCast(n));
defer d_x.deinit();
defer d_y.deinit();

const threads: u32 = 256;
const blocks: u32 = (@as(u32, @intCast(n)) + threads - 1) / threads;

try kernel.launch(
    .{ .x = blocks, .y = 1, .z = 1 },
    .{ .x = threads, .y = 1, .z = 1 },
    0, stream,
    .{ &a, d_x.devicePtr(), d_y.devicePtr(), &n },
);

PTX Caching ​

For production use, cache the PTX string to disk to avoid recompilation on every run:

zig
const cache_path = "cache/saxpy.ptx";
const ptx = blk: {
    // Try loading from cache
    const f = std.fs.cwd().openFile(cache_path, .{}) catch |e| {
        if (e != error.FileNotFound) return e;
        // Compile fresh
        var p = try cuda.nvrtc.Program.create(kernel_src, "saxpy.cu");
        defer p.deinit();
        try p.compile(&.{});
        const fresh_ptx = try p.getPtx(allocator);
        try std.fs.cwd().makePath("cache");
        try std.fs.cwd().writeFile(cache_path, fresh_ptx);
        break :blk fresh_ptx;
    };
    defer f.close();
    break :blk try f.readToEndAlloc(allocator, 4 * 1024 * 1024);
};
defer allocator.free(ptx);

NVRTC API Functions ​

FunctionDescription
Program.create(src, name)Create a program from a source string
Program.compile(options)Compile to PTX
Program.getPtx(allocator)Return the compiled PTX
Program.getLog(allocator)Return the compilation log
Program.deinit()Free NVRTC program handle

Released under the MIT License.