# go-llama.cpp: Go bindings that keep the heavy work in C and C++

> go-llama.cpp is a set of Go bindings for LLama.cpp that push most of the computation into the C and C++ layer, build a static library through a Makefile, and support CPU, OpenBLAS, CUDA, ROCm, OpenCL and Metal back ends. It suits Go developers who want local inference without a Python service in the middle, and it does not suit teams that want a versioned, released API surface.

**go-skynet/go-llama.cpp** — LLama.cpp golang bindings

- Repository: https://github.com/go-skynet/go-llama.cpp
- Stars: 944 · Forks: 124
- Language: C++
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/go-skynet-go-llama-cpp

## The bindings are deliberately thin so the work stays in C

go-llama.cpp is described as Go bindings for LLama.cpp, and the design decision that defines the project is stated up front: the bindings are high level, so most of the work is kept in the C and C++ code. The stated reasons are to avoid extra computational cost, to be more performant, and to ease maintenance, while keeping usage as simple as possible.

That framing tells you what this library is not. It is not a reimplementation of inference in Go, and it is not a wrapper that copies tensors into Go memory for you to work with. The Go side calls into the native library, and the native library does the arithmetic.

The documentation backs the approach with two write-ups on the cost and complexity of cgo, including a GopherCon 2018 piece on cgo performance and a Cockroach Labs article. That is an unusual amount of care spent on arguing a design choice, and it tells you the maintainers consider the cgo boundary a deliberate, reviewed decision rather than an accident. It also tells you what to expect from a Go program that embeds it: a cgo-linked binary, a C toolchain requirement, and a build that is not the usual pure Go pipeline.

The intended user is a Go developer who needs local inference inside an existing service or desktop program, and who does not want to keep a separate Python inference process in the deployment.

## Submodules carry the upstream llama.cpp tree, and the Go surface sits on top

The repository tracks LLama.cpp through git submodules, which is the first structural fact to understand before touching the build. The top level shows a llama.cpp directory, a patches directory, binding.cpp and binding.h, and the Go files llama.go, llama_cublas.go, llama_openblas.go and options.go, plus examples/ and a Makefile.

The split between llama.go and the accelerator-specific files is how the back ends are organised. Each back end gets its own Go file, and each one is compiled in only when you ask for it, which is why the build instructions pass a build tag along with the Go run command. The options.go file holds the configuration surface, which is where the model path, thread count and layer count are set.

The go.mod file is short and unremarkable. The module path is github.com/go-skynet/go-llama.cpp and it declares go 1.21. The only direct requirements are the test frameworks ginkgo v2.13.0 and gomega v1.28.0, which tells you the library has few runtime dependencies of its own. Everything that does the actual work comes from the vendored C and C++ tree, not from the Go module graph. That is a good sign for dependency hygiene, and it is also a reminder that a version bump in Go dependencies will not change your inference behaviour.

## Building means cloning with submodules and making a static library

The documented build sequence is three steps. The clone must include submodules, because the upstream LLama.cpp source is not vendored into the tree directly.

```bash
git clone --recurse-submodules https://github.com/go-skynet/go-llama.cpp
```

Then the binding is compiled into a static library that the Go linker will find later.

```bash
cd go-llama.cpp
make libbinding.a
```

The Makefile does the compiler housekeeping for you. It sets the include path to the llama.cpp tree and the current directory, compiles with -O3 and -DNDEBUG at C11 and C++11, and turns on a long list of warnings including -Wall, -Wextra, -Wpedantic and -Wcast-qual. It also probes the compiler versions, and on macOS it checks whether the machine reports itself as x86_64 while actually being ARM64, warning that not fixing that can lead to bad performance.

The first real use is the bundled example, which needs the library and the C headers on the compiler search paths.

```bash
LIBRARY_PATH=$PWD C_INCLUDE_PATH=$PWD go run ./examples -m "/model/path/here" -t 14
```

The model path is yours to fill in, and -t 14 sets the thread count. The same environment variables, LIBRARY_PATH and C_INCLUDE_PATH, appear in every accelerator recipe, because cgo needs them in all cases.

## Five back ends, one BUILD_TYPE variable

The acceleration section is where this library earns its keep, and the pattern is uniform. You set BUILD_TYPE when compiling the library, then pass matching cgo linker flags and a build tag when running the Go program.

For OpenBLAS on the CPU path, the library is built with the openblas type and the program links against it.

```bash
BUILD_TYPE=openblas make libbinding.a
CGO_LDFLAGS="-lopenblas" LIBRARY_PATH=$PWD C_INCLUDE_PATH=$PWD go run -tags openblas ./examples -m "/model/path/here" -t 14
```

CUDA uses cublas, ROCm uses hipblas, and OpenCL uses clblas with a CLBLAS_DIR pointing at the installation. The ROCm recipe is the most involved, since it overrides the compilers with the ROCm LLVM toolchain and passes a long set of hip-link and BLAS flags, plus an example invocation that offloads 64 layers and uses 32 threads.

Apple Silicon gets its own path through Metal, and it needs a file copied into the working directory after the build.

```bash
BUILD_TYPE=metal make libbinding.a
CGO_LDFLAGS="-framework Foundation -framework Metal -framework MetalKit -framework MetalPerformanceShaders" LIBRARY_PATH=$PWD C_INCLUDE_PATH=$PWD go build ./examples/main.go
cp build/bin/ggml-metal.metal .
./main -m "/model/path/here" -t 1 -ngl 1
```

The Metal route is the one place where a generated shader file has to be present next to the binary. If you forget the copy step, the binary builds cleanly and then fails at runtime, which is an annoying way to discover it.

## gguf only: the ggml format stopped working after pull request 180

There is a compatibility notice that deserves to be read before anything else. Since pull request 180 was merged, go-llama.cpp is no longer compatible with the ggml format and works only with the new gguf file format, and the documentation points at the corresponding upstream change in llama.cpp.

If you have an existing model in the older format, the documentation gives you a path: use the pre-gguf tag of this repository, released as a tag rather than a GitHub release. The practical consequence is that model files have a version floor. A gguf file is what current code expects, and a ggml file needs a different, pinned version of the bindings, which in turn means a different copy of the upstream C tree, since that tree is carried in a submodule and changes with the tag.

This is the single biggest source of friction in adopting the library, and it is a file format issue rather than a code issue. Older converted models and legacy tooling that emit ggml simply will not load, and the error you get will point at the loader rather than at the format change, so budget time for that diagnosis.

Another limitation is worth naming plainly. The Makefile contains a TODO to support Windows, and the OS specific blocks cover Linux, Darwin, FreeBSD, NetBSD, OpenBSD and Haiku. Windows is not among them, so this is a Unix-flavoured build in practice,.

## Versioning by tag only, and an MIT licence over C and C++ code

The repository has no GitHub releases. There is no release page to check, no changelog entries and no versioned binary to download. The only versioning available is the git tag set, and the documentation points you at a tag when you need an older behaviour, such as pre-gguf. For a library you vendor into a product, that is workable but unglamorous, and it means your upgrade process is a git operation rather than a package manager operation.

Activity, on the other hand, looks current. The last push was on 2026-10-10 and the repository is not archived. The presence of renovate.json suggests dependency updates are automated, and the patches directory is where changes to the upstream tree are kept, which is how the maintainers carry fixes across submodule bumps.

The licence field on the repository says MIT, and the project states the same in its own licence section. MIT is permissive and imposes no obligation to publish your own code, which matters for a binding that ships compiled C and C++ from an upstream project into your binary. Reading the LICENSE file for the exact obligations, and checking the upstream llama.cpp terms as well, is the sensible step before shipping. None of this is legal advice.

The two write-ups the documentation cites, on cgo performance and on the cost of cgo, are worth reading before you commit. They explain the boundary you are inheriting, and they explain why the build is the way it is.

## Conclusion

Adopt go-llama.cpp when your Go application needs local model inference and you are willing to build a C and C++ static library and manage CUDA, ROCm, OpenCL or Metal toolchains yourself. Do not choose it if you want a prebuilt binary with published releases, since the repository has no GitHub releases and its tags are the only versioning available, or if your platform is Windows, because the Makefile carries an explicit TODO for Windows support. First verify that your model file uses the gguf format, that the submodule clone succeeded, and that the build type you pass to make matches the accelerator you actually have on the machine.

## FAQ

### How do I build go-llama.cpp and run the example?

Clone with git clone --recurse-submodules https://github.com/go-skynet/go-llama.cpp so the upstream llama.cpp tree is present, then run make libbinding.a. To run the example, set LIBRARY_PATH and C_INCLUDE_PATH to the repository directory and use go run ./examples -m with your model path and a thread count.

### Which model file format does go-llama.cpp support?

Only the gguf format. Since pull request 180 was merged the bindings are no longer compatible with the ggml format, and the documentation points to the pre-gguf tag if you need the old format.

### How do I enable GPU acceleration in go-llama.cpp?

Set BUILD_TYPE when building the library and pass matching cgo linker flags and a build tag when running. The documented values are openblas, cublas, hipblas for ROCm, clblas for OpenCL and metal for Apple Silicon.

### Does go-llama.cpp work on Windows?

The Makefile lists OS specific handling for Linux, Darwin, FreeBSD, NetBSD, OpenBSD and Haiku, and it carries an explicit TODO to support Windows. The documented build recipe is therefore a Unix one.

### Is there a higher level Go API for go-llama.cpp?

The documentation points to a separate project, llama-cli, for a high level OpenAI compatible API. go-llama.cpp itself is the low level binding layer that keeps computation in C and C++.

## Sources

- [go-skynet/go-llama.cpp on GitHub](https://github.com/go-skynet/go-llama.cpp)
- [Issues](https://github.com/go-skynet/go-llama.cpp/issues)
- [License: MIT](https://github.com/go-skynet/go-llama.cpp/blob/master/LICENSE)
- [README](https://github.com/go-skynet/go-llama.cpp/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/go-skynet-go-llama-cpp
