cugo: pure-Go CUDA Driver API bindings (no cgo)

cugo exposes the NVIDIA CUDA Driver API directly to Go without cgo by dynamically loading nvcuda.dll at runtime via golang.org/x/sys/windows.
While existing Go CUDA bindings require a C compiler, CUDA headers, and the CUDA Toolkit installed at build time, cugo requires no C compiler to build or ship a Go program that uses the GPU โ only the standard NVIDIA display driver needs to be present on the target host at runtime. Cross-compiling GPU-accelerated Go binaries is completely frictionless (CGO_ENABLED=0).
Key Features
- ๐ Zero Build Dependencies: No MSVC, GCC, Clang, or CUDA Toolkit required for downstream users or consumers.
- ๐ฆ PTX & CUBIN Embedding: Embed GPU kernels directly into Go binaries using Go 1.16+
//go:embed.
- โก Kernel Launch Reflection: Pass arbitrary Go structs by-value or by-pointer, plus primitives (
bool, int8-int64, float32/float64, uintptr) with automatic ABI marshaling.
- ๐ Pinned Host Memory: Page-locked allocations (
AllocHost) with zero-copy direct GPU access and high-speed PCIe DMA transfers.
- ๐ง Unified Memory: Coherent CPU/GPU virtual memory (
AllocManaged) with automatic migration and explicit prefetching (PrefetchToDevice, PrefetchToCPU).
- ๐ CUDA Graphs: Capture entire execution DAGs (
BeginCapture, EndCapture, Instantiate, Launch) to execute complex pipelines with sub-microsecond launch latency.
- ๐ Stream-Ordered Allocator: Modern CUDA 11.2+ memory pools (
AllocAsync, FreeAsync, TrimTo) with zero-synchronization GPU memory recycling.
- ๐ ๏ธ Dynamic JIT Linker: In-process runtime compilation and linking of PTX strings directly into native device CUBIN binaries (
CreateLinker, AddPTX, Complete).
- ๐ 2D Pitched Memory & Arrays: Hardware-aligned stride allocations (
AllocPitch), 2D rectangular transfers (Copy2D), and CUDA hardware arrays (CreateArray2D).
- ๐ Multi-GPU P2P: Direct NVLink / PCIe peer-to-peer copies (
CanAccessPeer, EnablePeerAccess, CopyPeer).
- ๐งฎ Occupancy Auto-Tuning: Built-in occupancy calculators (
MaxActiveBlocksPerMultiprocessor, SuggestBlockSize).
Feature Comparison
| Capability |
cugo (v1.0.0) |
gorgonia/cu |
| CGO Required |
No (CGO_ENABLED=0 friendly) |
Yes |
| Build-Time C Toolchain |
None |
MSVC / GCC / Clang required |
| CUDA Toolkit at Build Time |
None (library consumers) |
Required (cuda.h, import libs) |
| Cross-Compilation |
Seamless from any OS/architecture |
Requires cross-compilation toolchain |
| Driver Dependency |
Runtime nvcuda.dll |
Runtime + link-time driver libraries |
| Kernel Param Reflection |
Yes (Go structs & primitives) |
Manual packing |
| CUDA Graphs API |
Yes (Capture & Replay) |
No |
| Stream-Ordered MemPool |
Yes (cuMemAllocAsync) |
No |
| Runtime JIT Linker |
Yes (cuLinkCreate) |
No |
| Platform Support |
Windows (amd64, v1.0) |
Windows, Linux |
Verified and measured directly on physical hardware (NVIDIA GeForce RTX 4060 Laptop GPU, Go 1.26 windows/amd64). See bench/README.md for full unedited benchmark measurements.
Installation
go get github.com/DanielcoderX/cugo
Requires Go 1.22+ and NVIDIA display drivers installed. No CUDA Toolkit or C compiler required.
Quickstart Examples
1. Vector Addition Kernel
package main
import (
"fmt"
"log"
"unsafe"
"github.com/DanielcoderX/cugo/driver"
"github.com/DanielcoderX/cugo/kernels/vecadd"
)
func main() {
if err := driver.Init(); err != nil {
log.Fatal(err)
}
devices, _ := driver.Devices()
ctx, err := devices[0].CreateContext()
if err != nil {
log.Fatal(err)
}
defer ctx.Destroy()
mod, err := ctx.LoadModuleData(vecadd.PTX)
if err != nil {
log.Fatal(err)
}
defer mod.Unload()
fn, err := mod.Function("vecAdd")
if err != nil {
log.Fatal(err)
}
const n = 100000
const byteSize = n * 4
hA, hB, hC := make([]float32, n), make([]float32, n), make([]float32, n)
for i := range hA {
hA[i] = float32(i)
hB[i] = float32(i) * 2
}
dA, _ := ctx.Alloc(byteSize); defer ctx.Free(dA)
dB, _ := ctx.Alloc(byteSize); defer ctx.Free(dB)
dC, _ := ctx.Alloc(byteSize); defer ctx.Free(dC)
_ = ctx.CopyHtoD(dA, unsafe.Slice((*byte)(unsafe.Pointer(&hA[0])), byteSize))
_ = ctx.CopyHtoD(dB, unsafe.Slice((*byte)(unsafe.Pointer(&hB[0])), byteSize))
cfg := driver.LaunchConfig{
GridDimX: (n + 255) / 256,
BlockDimX: 256,
}
_ = fn.Launch(cfg, dA, dB, dC, int32(n))
_ = ctx.CopyDtoH(unsafe.Slice((*byte)(unsafe.Pointer(&hC[0])), byteSize), dC)
fmt.Printf("GPU Result[42] = %.1f (expected %.1f)\n", hC[42], hA[42]+hB[42])
}
2. Unified Memory (Zero-Memcpy)
// Allocate shared virtual memory directly accessible by CPU and GPU
mem, _ := ctx.AllocManaged(size)
defer mem.Free()
// Access directly on host CPU
slice := mem.Bytes()
slice[0] = 42
// Launch kernel directly using mem โ hardware migrates pages on demand
_ = fn.Launch(cfg, mem, int32(n))
3. CUDA Graphs (Capture & Replay)
stream, _ := ctx.CreateStream()
defer stream.Destroy()
// 1. Capture stream operations into a graph
_ = stream.BeginCapture()
_ = fn.Launch(cfg, dA, dB, dC, int32(n))
graph, _ := stream.EndCapture()
defer graph.Destroy()
// 2. Instantiate and launch repeatedly with sub-microsecond CPU overhead
exec, _ := graph.Instantiate()
defer exec.Destroy()
_ = exec.Launch(stream)
_ = stream.Synchronize()
4. Dynamic JIT Linker
linker, _ := ctx.CreateLinker()
defer linker.Destroy()
// Feed PTX code directly generated at runtime
_ = linker.AddPTX(ptxBytes, "my_kernel.ptx")
// Compile and link directly to hardware CUBIN
cubin, _ := linker.Complete()
// Load and execute immediately
mod, _ := ctx.LoadModuleData(cubin)
defer mod.Unload()
Included Examples & Benchmarks
# Enumerate GPU models, compute capabilities, and hardware attributes
go run ./examples/device-info
# Basic vector addition end-to-end kernel launch
go run ./examples/vecadd
# Async streams, event recording, and transfer overlap
go run ./examples/async-copy
# 16x16 shared-memory tiled GEMM
go run ./examples/gemm
Architecture & Design Documents
License
Apache License 2.0. See LICENSE for details.