Blackwell ready, pure Zig (0.16.0+) bindings to the NVIDIA CUDA Driver API
Dynamic loading of libcuda.so, clean high-level wrappers, and graceful stubs for non-CUDA environments.
No static linking, no CUDA toolkit required at runtime.
Tested on Blackwell (sm_120) — ready for low-level GPU programming, kernel launching, and basic BLAS in Zig.
git clone https://github.com/Aiurion/zigcuda.git &&cd zigcuda
zig build runExample output:
=== ZigCUDA CLI Diagnostic Tool ===
INFO: cuInit succeeded
✓ CUDA Driver Initialized
✓ Device Count: 1
[GPU 0] NVIDIA RTX PRO 6000 Blackwell Workstation Edition
├─ Compute: 12.0
├─ SMs: 120
└─ VRAM: 95.59 GB
- Dynamic Driver Loading – Works on Linux native and WSL2, multiple symbol resolution paths
- Clean Zig API – Raw Driver API access plus low-level ergonomic wrappers for memory, params, modules, and launch
- Graceful Stubs – Compiles and runs basic checks without a GPU
- Zero External Dependencies – Only needs NVIDIA driver at runtime
- Test Coverage – 113 passing tests across core, bindings, ergonomics, and integrations
- Easy Library Usage – Single
@import("zigcuda")with init/deinit pattern
| Component | Status | Notes |
|---|---|---|
| Driver Loading | Complete | Dynamic + extensive fallbacks |
| Core API (memory, streams, contexts) | Complete | Full wrappers, async support |
| Kernel Launch | Complete | cuLaunchKernel + legacy fallback |
| cuBLAS Integration | Partial | Basic handle + common ops working |
.dependencies= .{
.zigcuda= .{
.url="git+https://github.com/Aiurion/zigcuda.git#v0.0.2",
// Run `zig build` once to fill in hash
},
},constzigcuda_dep=b.dependency("zigcuda", .{
.target=target,
.optimize=optimize,
});
exe.root_module.addImport("zigcuda", zigcuda_dep.module("zigcuda"));
exe.root_module.linkSystemLibrary("c", .{});Use the low-level ergonomic API exported from zigcuda directly for normal application code. The raw Driver API wrappers remain available under zigcuda.bindings.* when you need an exact CUDA escape hatch.
conststd=@import("std");
constzigcuda=@import("zigcuda");
pubfnrunKernel(allocator: std.mem.Allocator, input: []constf16, output: []f16) !void {
varinput_dev=tryzigcuda.DeviceBuffer.alloc(std.mem.sliceAsBytes(input).len);
deferinput_dev.deinit();
varoutput_dev=tryzigcuda.DeviceBuffer.alloc(std.mem.sliceAsBytes(output).len);
deferoutput_dev.deinit();
tryinput_dev.copyFromTyped(f16, input);
varmodule=tryzigcuda.Module.loadFirst(allocator, &.{
"build/kernels/lm_head_q6k_mmq.cubin",
"kernels/lm_head_q6k_mmq.cubin",
});
defermodule.deinit();
constkernel=trymodule.kernel("lm_head_mmq_q6k_kernel");
varparams=zigcuda.Params.init();
tryparams.devicePtr(output_dev.ptr);
tryparams.devicePtr(input_dev.ptr);
tryparams.value(i32, @intCast(input.len));
trykernel.launch(.{
.grid= .{ .x=@intCast((input.len+255) /256) },
.block= .{ .x=256 },
.sync_after=true,
}, params.slice());
tryoutput_dev.copyToTyped(f16, output);
}Defaults keep common CUDA launch boilerplate out of the call site: grid.z = 1, block.y = 1, block.z = 1, shared_mem_bytes = 0, stream = null, and sync_after = false.
Device enumeration:
conststd=@import("std");
constzigcuda=@import("zigcuda");
pubfnmain() !void {
varctx=tryzigcuda.init();
deferctx.deinit();
constdevice_count=ctx.getDeviceCount();
std.debug.print("Found {d} CUDA device(s)\n", .{device_count});
for (0..@min(device_count, 3)) |i| {
constprops=tryctx.getDeviceProperties(@intCast(i));
constname=std.mem.sliceTo(props.name[0..], 0);
std.debug.print("Device {d}: {s}\n", .{
i,
name,
});
}
}Ergonomic kernel launch:
conststd=@import("std");
constzigcuda=@import("zigcuda");
constcuda=zigcuda.bindings;
pubfnmain() !void {
tryzigcuda.loadCuda();
tryzigcuda.initCuda(0);
constdevice=tryzigcuda.getDevice(0);
constctx=trycuda.createContext(0, device);
defercuda.destroyContext(ctx) catch {};
constn: u32=1024;
constbytes=n*@sizeOf(f32);
varinput=tryzigcuda.DeviceBuffer.alloc(bytes);
deferinput.deinit();
varoutput=tryzigcuda.DeviceBuffer.alloc(bytes);
deferoutput.deinit();
varmodule=tryzigcuda.Module.loadFirst(std.heap.page_allocator, &.{
"build/kernels/vector_add.cubin",
"examples/kernels/vector_add.ptx",
});
defermodule.deinit();
constkernel=trymodule.kernel("vector_add");
varparams=zigcuda.Params.init();
tryparams.devicePtr(input.ptr);
tryparams.devicePtr(output.ptr);
tryparams.value(u32, n);
trykernel.launch(.{
.grid=zigcuda.Dim3.init((n+255) /256),
.block= .{ .x=256 },
.sync_after=true,
}, params.slice());
}This IS:
- A solid CUDA Driver API wrapper for Zig
- Ready for writing and launching kernels, memory management, streams/events
- Usable today for low-level GPU work and experimentation
This is NOT:
- A full ML framework
- Complete high-level tensor ops
- Optimized inference engine
- v0.0.x – Core polish and further validation
zig build test# Run full suite
zig build run # Diagnostic toolSupported Platforms:
- Linux (x86_64) – Fully tested
- WSL2 – Working with dual-context handling
Open issues for bugs & in-scope features.
MIT (see LICENSE file)
ZigCUDA gives you real CUDA access in pure Zig with minimal overhead. The foundation is ready – start building GPU code today.
zigCUDA is an independent open-source project and is not affiliated with or endorsed by NVIDIA Corporation. CUDA is a trademark and/or registered trademark of NVIDIA Corporation in the U.S. and other countries.