golem

module
v0.38.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 20, 2026 License: MIT

README

golem

Gemma, Qwen and Pocket TTS in a single static Go binary.
No Python. No cgo. Runs on your CPU, or on your GPU with -vulkan.

test Go Reference MIT


A golem is inert matter given a voice. That is what these engines do to a file of weights.

Golem runs Gemma 4, Qwen3, Qwen3.8 and Kyutai speech models locally, on the processor or on a Vulkan GPU that is roughly ten times faster. It is one Go binary of about twelve megabytes, and there is nothing else to install.

go install github.com/ThiraSoft/golem/cmd/golem-cli@latest

golem-cli -model gemma-4-E2B-it-QAT-Q4_0.gguf -p "Explain a mutex in one sentence."

Without a Go toolchain, take a static binary from the releases: Linux and macOS, amd64 and arm64. Windows does not build yet, because tensors/ maps a checkpoint through a call that platform does not have.

There is no configuration file to write. The engine reads general.architecture out of the GGUF and opens whichever implementation matches, which is why no command below names a model family.

Who this is for

If you ship Go and you need a model to run on the machine your software already runs on, this removes the part of that job that hurts. No Python runtime to install beside your binary. No four-gigabyte CUDA image. No cgo, so GOOS=linux GOARCH=amd64 go build still produces something you can scp to a box you do not control and run on a kernel you did not choose.

That covers on-premise deployments, edge and embedded boxes, air-gapped machines, CI, and any product where "please install Python 3.11 first" is not an acceptable sentence to put in front of a customer.

If you are running models on your own workstation and a package manager is fine, Ollama is friendlier and you should use it.

Why not just bind to llama.cpp

Because a cgo binding is no longer a static binary. It brings back the C toolchain, the cross-compilation problems, and a build that breaks differently on every distribution. That is the entire cost this project is paying its way out of, and the price of admission was a full set of kernels: AVX2 and NEON in Go assembly, Vulkan compute bound through purego at runtime rather than linked.

The bet is that it is worth it only if the result is actually fast. See the numbers.

What you get for it

  • One binary, four dependencies. purego loads Vulkan at runtime, and three packages read file formats the standard library does not (WebP, MP3, FLAC). CGO_ENABLED=0 go build ./... passes.
  • A library, not just a command. engine.Open hands back a forward pass, a vocabulary and a chat template. The commands in cmd/ are thin.
  • It is fast enough to be the real thing. Tuned AVX2 kernels sit at llama.cpp's level on CPU. The Vulkan backend does too, on the card it was measured on. See the numbers and the caveats that come with them.
  • Nothing is asserted, everything is checked. No layer here is considered correct until its intermediate activations match llama.cpp or PyTorch waypoint by waypoint. A model that answers fluently and wrongly is this project's named failure mode.
  • Every number in this file is a benchmark in this repository, run on the machine named next to it. Nothing is extrapolated, and where golem loses it says so.

What it does

Text Gemma 4 (E2B, 12B, 26B A4B), Qwen3, Qwen3.8 27B
Vision Gemma 4 and Qwen3.8, from a projector file
Audio in Gemma 4 hears WAV, MP3 and FLAC. Kyutai STT transcribes English and French
Audio out Kyutai Pocket TTS, 12 shipped models across 6 languages, plus voice cloning
Images Krea 2 from ComfyUI's fp8 files, with its LoRA, the same picture as ComfyUI for the same seed, and on a colour to key out
Serving OpenAI-compatible HTTP API, tool calls, continuous batching, JSON schemas and GBNF grammars
Weights GGUF, every K-quant llama.cpp writes, and golem's own .golem format

Quickstart

Every command below is go install github.com/ThiraSoft/golem/cmd/<name>@latest, or go build ./cmd/<name> from a clone.

Chat in the terminal:

golem-cli -model Qwen3-4B-Q4_0.gguf -p "Explain a mutex in one sentence." -stats
golem-cli -model Qwen3-4B-Q4_0.gguf -vulkan          # on the GPU

Serve an OpenAI-compatible API:

golem-server -model Qwen3-4B-Q4_0.gguf -addr 127.0.0.1:8080 -parallel 4

-parallel N cuts the context into N slots and holds N conversations at once. Whatever is waiting when a pass is built rides in that pass, so four clients wanting a token are one read of the weights instead of four. Details in cmd/golem-server/README.md.

Look at a picture:

golem-cli -model gemma-4-E2B-it-QAT-Q4_0.gguf \
    -mmproj mmproj-gemma-4-E2B-it-QAT-BF16.gguf \
    -image photo.png -p "What is in this picture?"

The same flags with -audio question.wav make Gemma listen. See gemma/README.md.

Speak, and transcribe:

pocket-tts -voice voice.safetensors -o hello.wav "Bonjour le monde."
pocket-tts -clone someone.wav -save-voice someone.safetensors   # 20s of audio is enough

golem-cli -stt ~/models/stt-1b-en_fr -listen    # microphone in, words out

Voice cloning needs no training and no Python. Transcription streams at twice real time on eight CPU cores, and about ×3 on the card. See stt/README.md and pockettts/README.md.

Ask for JSON and get JSON:

golem-cli -model Qwen3-4B-Q4_0.gguf -json-schema city.json -p "Give me the city of Lyon."

A model asked for JSON in prose usually obliges. A model drawing inside a grammar cannot do otherwise, because every token that would break the document is refused before the draw. -json, -json-schema and -grammar on the command line, response_format and grammar over the API. The engine is a port of llama.cpp's own, compared against it rule by rule. See grammar/README.md.

Or use it as a library:

m, err := engine.Open("Qwen3-4B-Q4_0.gguf", 4096)   // reads the architecture, opens the engine
defer m.Close()

ids := m.Vocab.Encode("Explain a mutex in one sentence.", true, true)
hidden := m.Forward.ForwardBatch(ids, 0)

logits := make([]float32, m.Vocabulary)
m.Forward.Logits(hidden[len(hidden)-1], logits)

engine.Open hands back one shape whichever family the file is: the forward pass, the vocabulary, the chat template the checkpoint carries, and what to sample with. The commands in cmd/ are thin wrappers over it, and pkg.go.dev has the rest.

Speed

On an RX 9070 XT against llama.cpp's Vulkan build (ba1df050f, b9603), same Q4_0 files, both sides warmed, measured on 2026-09-03 in one sitting. Tokens a second, higher is better:

golem gen llama gen golem pp512 llama pp512
Gemma 4 26B A4B 153.4 125.1 4434 4059
Gemma 4 12B 72.3 64.6 2901 2978
Qwen3 4B 180.5 172.0 5977 7117
Qwen3 0.6B 368.2 407.9 23445 23677

Read the table as a whole: golem reaches llama.cpp's level here, and that is the entire claim. This is one card, one driver, one afternoon. A kernel that wins on RDNA 4 at these shapes need not win on another architecture or another checkpoint, and nobody has run it there. The absolute rates do not travel either, so the column worth reading is the difference between the two engines rather than the rate itself.

Qwen3.8 27B is absent on purpose. At 15.65 GiB on a heap of 15.92 it leaves no room on a card that is also driving a desktop, and what a table would compare there is memory pressure rather than two engines. qwen35/README.md has that measurement.

On an i7-9700K with eight threads and Q4_0 weights: Gemma E2B draws 22.6 tokens a second and reads 204, the 12B does 5.0 and 42, the 26B A4B does 13.1 and 51, Qwen3 4B does 14.6 and 110. Against llama.cpp on the same machine those four are a tie on generation and between ×1.06 and ×1.33 reading a prompt.

The one model golem loses is Qwen3 0.6B, and qwen/README.md says why: at 320 MB the weights fit close enough that memory stops being the limit, and what is left is arithmetic. This engine is built for the regime where reading the weights is the cost, and it says so where it is not.

ARCHITECTURE.md has the kernel work behind all of this, the per-op breakdown of one pass against llama.cpp's, and what the next bottleneck is.

Three things that are not in other engines

A mixture's experts need not be on the card

Gemma 4 26B A4B keeps 12.85 GB of experts and reads eight matrices out of a hundred and twenty-eight per block. Eleven of those twelve gigabytes sit untouched on any given token, which means the cost of streaming a mixture is what a token activates rather than what the model has.

GOLEM_MOE_EXPERTS_HOST=1 leaves the expert stacks in system memory that the card can address and lets the kernels read them where they lie. No shader knows the difference. That takes the model's footprint on the card from 13.6 GiB to 1.3, and a cache of the experts a token keeps asking for buys the speed back:

experts kept in VRAM that much VRAM tokens/s
2 of 128 (the floor) 0.2 GB 7.8
32 of 128 3.2 GB 30.2
51 of 128 5.1 GB 56.2
all 128 (fully resident) 12.9 GB 131.6

Two fifths of the pool buys four fifths of the tokens, and the answers are identical in every row. The full curve, the block-by-block residency that runs a 27 GB checkpoint on a 16 GB card, and the reason the router had to move onto the GPU are in gemma/README.md.

Qwen3.8 drafts its own next token

The Qwen3.8 checkpoint ships a sixty-fifth block whose job is to guess the token after the one just decided. Guess right and the next pass verifies two tokens for the price of one, because the card reads a block's weights once whether the pass carries one column or two. Guess wrong and it costs nothing beyond the pass it rode on. Every token returned is drawn from the model's own distribution, so none of the accept-reject correction that drafting with a separate model needs applies here, and a test asserts the answer is the same either way.

On an RX 9070 XT: 27.6 tokens a second one at a time, 40.1 drafting, 70% of drafts accepted. On the CPU it is refused rather than offered, and qwen35/README.md has the measurement that settles it.

Its own weight format

golem reads GGUF like everyone else. It also writes .golem, which trades speed for size. On Qwen3-4B against the same bf16 reference, 4088 tokens of wikitext:

size perplexity KL divergence top-1 agreement
bf16 7.5 GiB 19.29
Q4_K_M 2.33 GiB 20.04 0.0715 90.1 %
.golem T4G 1.96 GiB 20.38 0.0526 90.6 %
Q3_K_M 1.93 GiB 24.03 0.2453 79.8 %
.golem T3G 1.57 GiB 21.11 0.1771 83.1 %

T3G is 18 % smaller than Q3_K_M and ahead of it on every column. The codebook is a state machine rather than a table, so a weight is twelve bits of a code stream put through four instructions and there is nothing to look up.

golemquant -model Qwen3-4B-BF16.gguf -out Qwen3-4B.golem -bits 3 -calib wiki.txt

The cost is generation speed, and it is not small: 82.5 tokens a second against Q4_0's 130.4 on the same card, because a trellis weight is decoded one at a time where a nibble format decodes eight per instruction. Prompts are unaffected since a tiled product decodes each weight once for a whole tile. The trade is worth taking when memory is what is short and not otherwise. compress/README.md has the method, the thirteen things that were tried to close the speed gap, and the eleven that made it worse.

How it is known to be right

No layer is deemed correct until its intermediate activations match the reference implementation.

Scripts load the real weights, inject a deterministic input, and write every intermediate quantity into testdata/. The Go tests read those files back, so they need neither Python nor llama.cpp at test time.

For gemma/ the reference is llama.cpp itself, instrumented, because a bf16 reference would bury a mistake under its own quantization error. For pockettts/ and stt/ it is PyTorch in float32, layer by layer, to a few parts in a million end to end.

Who wrote this

The code in this repository was written by an AI agent, Claude, directed and reviewed by a human. The git history says so on the commits themselves.

That is worth stating plainly rather than leaving to be discovered, and it is also the reason the section above exists. An agent will happily produce a layer that runs, returns plausible tokens and is quietly wrong in the fourth decimal place, and reading the diff does not catch that. Recorded activations do. Every claim in these files is either a test in the repository or a benchmark in it, because on this project that is the only kind of claim worth making.

Judge it the way you would judge any dependency you did not write: run the tests, check the numbers, read the parts you are about to trust.

Project structure

  • cmd/golem-cli, cmd/golem-server, cmd/pocket-tts, cmd/golemquant, cmd/golemtune are the commands.
  • engine/ reads the architecture out of a GGUF and opens the engine that implements it.
  • gemma/, qwen/, qwen35/, pockettts/, stt/, nomic/ are standalone engines. They do not import one another.
  • nn/ and vk/ are the shared kernels: quantized AVX2 and NEON, and Vulkan compute.
  • compress/ is the .golem format: calibration, the trellis codec, and the conversion pipeline.
  • grammar/ is GBNF and the JSON Schema converter that feeds it.
  • internal/kyutai/ is the Mimi codec, shared by both directions of speech.
  • tensors/, token/, chat/, sample/, audio/, imageio/ are the rest of the shared layer.
  • ref/ is what recorded each test fixture, and how to record it again.

Tests

go test ./...       # correctness on every model and the card, about 17 minutes

Weights are not in this repository, and every test that needs one skips cleanly when it cannot find it. That is why the same command is safe on a machine with no models and no card, and why CI runs it.

Use -p 1 if you have a GPU. Three of these packages put whole models on the card, two at once want more than 16 GB, and what comes back is a lost device in whichever test binary happened to be second.

Anything slower than thirty seconds waits behind GOLEM_FULL_TEST=1 and runs a package at a time. CONTRIBUTING.md says which tests those are and why.

Contributing

The thing we need most is ARM benchmarks. The arm64 kernels are correct and tuned by nobody: written and verified under emulation, never once timed on real hardware. If you have Apple Silicon or a Graviton instance, run ./benchmark-arm.sh and share what it prints.

After that, bug reports (especially a parity test failing on your setup) and NEON tuning. CONTRIBUTING.md has the rest.

License and credits

Golem is MIT Licensed.

Standing on the shoulders of giants: llama.cpp and ggml, Kyutai Pocket TTS, Google Gemma, QTIP for the bitshift trellis under .golem, and FreeToken, which asked the expert-caching question first and answered it differently.

Directories

Path Synopsis
audio
decode
Package decode turns an encoded sound file into samples.
Package decode turns an encoded sound file into samples.
mel
Package mel turns a waveform into the log-mel spectrogram a speech model expects.
Package mel turns a waveform into the log-mel spectrogram a speech model expects.
resample
Package resample makes a signal mono and puts it at the rate a model wants.
Package resample makes a signal mono and puts it at the rate a model wants.
wav
Package chat holds the shape of a conversation, and the interface an engine implements to write one out.
Package chat holds the shape of a conversation, and the interface an engine implements to write one out.
cmd
golem-cli command
Command golem-cli holds a conversation with a GGUF model, on the CPU or, with -vulkan, with its blocks and its logit head on a Vulkan device.
Command golem-cli holds a conversation with a GGUF model, on the CPU or, with -vulkan, with its blocks and its logit head on a Vulkan device.
golem-server command
Command golem-server answers an OpenAI-compatible API over a GGUF, on the CPU or, with -vulkan, with the model's blocks and logit head on a Vulkan device.
Command golem-server answers an OpenAI-compatible API over a GGUF, on the CPU or, with -vulkan, with the model's blocks and logit head on a Vulkan device.
golemquant command
golemtune command
golemtune finds the shape of the trellis mat-vec this card is fastest at.
golemtune finds the shape of the trellis mat-vec this card is fastest at.
kldiff command
krea2 command
Command krea2 draws a picture from a sentence with Krea 2, the way the ComfyUI mobile front draws it: the same three files, the same sampler, and for the same seed the same picture.
Command krea2 draws a picture from a sentence with Krea 2, the way the ComfyUI mobile front draws it: the same three files, the same sampler, and for the same seed the same picture.
pocket-tts command
Command pocket-tts synthesizes text into a WAV file.
Command pocket-tts synthesizes text into a WAV file.
tha3-pose command
Command tha3-pose renders one frame of a THA3 character: a picture, a pose given as name=value pairs, a PNG out, and how long each network took.
Command tha3-pose renders one frame of a THA3 character: a picture, a pose given as name=value pairs, a PNG out, and how long each network took.
vqbuild command
vqdiff command
vqeval command
Package engine picks which engine reads a file, so that a command does not have to.
Package engine picks which engine reads a file, so that a command does not have to.
Package gemma runs Google's Gemma 4 language models from a GGUF file, in Go, with no cgo: E2B, the 12B, and the 26B A4B mixture, text, vision and audio.
Package gemma runs Google's Gemma 4 language models from a GGUF file, in Go, with no cgo: E2B, the 12B, and the 26B A4B mixture, text, vision and audio.
Package grammar constrains what a model may say next.
Package grammar constrains what a model may say next.
schema
Package schema turns a JSON Schema into a GBNF grammar.
Package schema turns a JSON Schema into a GBNF grammar.
Package imageio decodes images and puts them in the shape a vision encoder reads: planar RGB, one float per channel per pixel.
Package imageio decodes images and puts them in the shape a vision encoder reads: planar RGB, one float per channel per pixel.
internal
heavy
Package heavy says whether the tests that load this machine may run.
Package heavy says whether the tests that load this machine may run.
version
Package version says which build of golem this is.
Package version says which build of golem this is.
Package jinja renders the Jinja a model file carries as its chat template.
Package jinja renders the Jinja a model file carries as its chat template.
Package krea2 draws a picture from a sentence with Krea 2, the way ComfyUI draws it from the same three files: the Qwen3-VL-4B text encoder, the 12B single-stream DiT in fp8, and the Wan 2.1 VAE, sampled with ER-SDE over the flow schedule.
Package krea2 draws a picture from a sentence with Krea 2, the way ComfyUI draws it from the same three files: the Qwen3-VL-4B text encoder, the 12B single-stream DiT in fp8, and the Wan 2.1 VAE, sampled with ER-SDE over the flow schedule.
Package nomic runs nomic-embed-text-v2-moe, the embedder GGUF calls nomic-bert-moe: text in, one vector out.
Package nomic runs nomic-embed-text-v2-moe, the embedder GGUF calls nomic-bert-moe: text in, one vector out.
Package pockettts synthesizes speech from text with the Kyutai Pocket TTS models, in pure Go.
Package pockettts synthesizes speech from text with the Kyutai Pocket TTS models, in pure Go.
Package qwen runs Qwen3 dense language models from a GGUF file, in Go, with no cgo: nothing here calls into C, and the one thing it does not do alone is reach a Vulkan device, which vk binds through purego.
Package qwen runs Qwen3 dense language models from a GGUF file, in Go, with no cgo: nothing here calls into C, and the one thing it does not do alone is reach a Vulkan device, which vk binds through purego.
Package qwen35 runs Qwen3.8 from a GGUF file, in Go, with no cgo.
Package qwen35 runs Qwen3.8 from a GGUF file, in Go, with no cgo.
Package sample turns a row of logits into a token.
Package sample turns a row of logits into a token.
Package tha3 runs Talking Head(?) Anime from a Single Image 3, by Pramook Khungurn: one picture of an anime character and 45 numbers in, the same character with that expression and that head pose out.
Package tha3 runs Talking Head(?) Anime from a Single Image 3, by Pramook Khungurn: one picture of an anime character and 45 numbers in, the same character with that expression and that head pose out.
token
bpe
Package bpe reads Gemma 4's tokenizer out of a GGUF and applies it.
Package bpe reads Gemma 4's tokenizer out of a GGUF and applies it.
bytebpe
Package bytebpe reads a byte-level BPE tokenizer out of a GGUF and applies it — the kind GPT-2 introduced and Qwen uses.
Package bytebpe reads a byte-level BPE tokenizer out of a GGUF and applies it — the kind GPT-2 introduced and Qwen uses.
merge
Package merge applies a ranked byte-pair merge table to a list of symbols.
Package merge applies a ranked byte-pair merge table to a list of symbols.
special
Package special cuts a vocabulary's special tokens out of text before the rest of the tokenizer sees it.
Package special cuts a vocabulary's special tokens out of text before the rest of the tokenizer sees it.
ugm
Package ugm is SentencePiece's unigram tokenizer as llama.cpp runs it for a vocabulary it calls "t5": XLM-RoBERTa's, and therefore nomic-embed-text-v2's.
Package ugm is SentencePiece's unigram tokenizer as llama.cpp runs it for a vocabulary it calls "t5": XLM-RoBERTa's, and therefore nomic-embed-text-v2's.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL