# Pantheon **An inference engine that assumes you don't have enough.** Not enough VRAM, not enough host RAM, not enough cores, not enough context, and not enough budget for current-generation GPUs. Pantheon is a memory-aware inference engine and OpenAI-compatible model server built for that case: a small host, older or mixed cards, several models per process. It is pure C11 on top of [Foundry](https://git.teamide.dev/David/Foundry.git), its companion tensor library (included as a git submodule). Beyond Foundry it needs only libc, libm, pthreads and libdl; BLAS (Accelerate on macOS, OpenBLAS on Linux) is optional, and the optional Metal backend links Apple's frameworks. Each backend builds to a single binary. --- ## What "memory-aware" actually means A common behavior when an engine runs out of memory is that the request fails. Pantheon treats running out as the *normal operating condition* and degrades on a ranked ladder, least-harmful first (`src/core/mem/pn_ladder.h`): | Pressure | What gives | |---|---| | **WATCH** | force Q4 KV cache — near-zero quality cost, 4–7× VRAM win | | **TIGHT** | halve concurrency · clamp the prefill window · evict cold KV | | **CRITICAL** | floor concurrency to one slot — a single request always still completes | Admission is checked before every load *and* every turn, against live host-RAM and VRAM pressure, so the engine refuses work it cannot finish instead of accepting it and falling over halfway. That layer (`src/core/mem/`) is about 2,100 lines of the codebase, which is a fair measure of how central it is. **It tells the client the truth.** A prompt past the context cap is refused with a typed `context_over_budget` carrying the actual KV numbers, so the caller can compact and retry. Nothing is truncated silently: for an agent built on top, silent truncation would be silent corruption. ## What that buys you - **Many models, one process, many GPUs.** One port hosts N models across N devices, routed by the request's `model` field. Repeat a `--name` on another device and it becomes a replica, load-balanced least-busy. Load and unload at runtime without a restart. This is what lets a 2-core / 8 GB host drive several cards. - **Portable session state.** Conversation state — recurrent SSM *and* attention KV — serializes to a versioned blob stamped with a model-identity tag the restore side validates. Hybrid state is hard to serialize because the recurrent half isn't position-addressable. It means session state can live on the user's machine instead of your disk. - **Reasoning that terminates.** `--reasoning-budget N` forces `` after N tokens, so a reasoning model that runs away still produces an answer instead of burning your context. - **Embeddable.** C11, no C++ runtime, no exceptions, auditable dependency tree, links as a static library. ## Old hardware The CUDA backend targets **`sm_37` — Kepler, 2014**. Measured by the author on one Tesla K80 (both GK210 dies) in a 2-core / 8 GB host: a **35B-A3B hybrid MoE served at 32K–128K context**: Qwen3.6-35B-A3B and Nex-N2-mini (Q3_K_M) both decode at ~21 tok/s and prefill ~110-120 tok/s up to ~8k tokens ([K80 quick start](#qwen36-and-nex-n2-mini-on-a-tesla-k80), [supported models](docs/reference/supported-models.md)). Correctness on that path, stated precisely: - With the fused attention kernels off (`QWEN35_FUSE=0x37`) and flash prefill off (`QWEN35_FLASH=0`), the author measured the 2-die GPU forward's logits **bit-identical to the CPU forward** (argmax and every logit). - The default flash prefill is argmax-exact, **not** bit-exact (~1e-6 relative drift; see [env flags](docs/reference/env-flags.md)). - The in-tree CUDA gates (`tests/test_spec_qwen35`, `tests/test_users_qwen35`, `tests/test_prefix_cache_qwen35`) check token-for-token identity between decode paths under `QWEN35_FUSE=0x37`. They need a model and a GPU and are not part of `make test`. Kepler is the extreme case, not the point. The point is that hardware you already own should keep working. --- ## Quick start Prerequisites: a C11 compiler and `make`. On Linux, `USE_BLAS=1` also needs OpenBLAS (`apt install libopenblas-dev`); on macOS it uses Accelerate. The model download below uses the Hugging Face CLI (`pip install -U huggingface_hub`); any way of fetching the `.gguf` works. Build on the machine you will run on: the default flags include `-march=native`, so a binary built on a newer CPU can fail with `Illegal instruction` on an older one. To build a binary you can copy between x86-64 machines, add `PORTABLE=1` (`-march=x86-64-v2`, no AVX). Per-platform details: [`docs/guides/install.md`](docs/guides/install.md). ```bash git clone --recursive https://git.teamide.dev/David/Pantheon.git pantheon cd pantheon && make USE_BLAS=1 pantheon # already cloned without --recursive? git submodule update --init hf download bartowski/SmolLM2-135M-Instruct-GGUF \ SmolLM2-135M-Instruct-Q8_0.gguf --local-dir models/ ./pantheon --model models/SmolLM2-135M-Instruct-Q8_0.gguf \ --prompt "Hello, my name is" --max-tokens 32 ``` Interactive chat, with the model's own template detected automatically: ```bash ./pantheon --model models/SmolLM2-135M-Instruct-Q8_0.gguf --chat ``` ### Ornith 1.5 on a Tenstorrent card Ornith 1.5 9B is the only model supported on Tenstorrent cards for now. On Linux with a Blackhole p150a: ```bash git clone --recursive https://git.teamide.dev/David/Pantheon.git pantheon cd pantheon && ./install.sh # build pantheon-tt, set up card access (asks before sudo) ./run_ornith.sh # download the model (~10 GB, once), serve on 127.0.0.1:8080 ``` `run_ornith.sh` fetches the files from [dcherrera/Ornith-1.5-9B-Pantheon-TT](https://huggingface.co/dcherrera/Ornith-1.5-9B-Pantheon-TT) and checks their sha256. The first start converts the model for the card (~10 GB more on disk); later starts skip that. It serves an OpenAI-compatible API, model id `ornith`. Options are in the script's header. ### Qwen3.6 and Nex-N2-mini on a Tesla K80 On Linux with a Tesla K80 and the NVIDIA 470 driver. **Supported K80 models: Qwen3.6-35B-A3B and Nex-N2-mini (both Q3_K_M), one model per card** (both dies): ```bash git clone --recursive https://git.teamide.dev/David/Pantheon.git pantheon cd pantheon && ./install.sh --k80 # build pantheon-cuda (no CUDA toolkit needed) ./k80_qwen36.sh # or ./k80_nexn2.sh; downloads ~17 GB once, serves on 127.0.0.1 ``` Measured on one K80 in a 2-core / 8 GB host (OpenAI streaming client, warm card, runs back to back): | prompt | Qwen3.6 first token | Nex-N2-mini first token | decode (both) | |---|---|---|---| | 1k tokens | 8 s (121 tok/s) | 9 s (107 tok/s) | ~21 tok/s | | 8k tokens | 69 s (116 tok/s) | 67 s (119 tok/s) | ~18 tok/s | | 14k tokens | 3.0 min (79 tok/s) | 3.2 min (74 tok/s) | ~15 tok/s | | 30k tokens | 8.4 min (60 tok/s) | 9.6 min (53 tok/s) | ~10 tok/s | **Good for:** chat, writing, Q&A and background jobs — short prompts, long answers. Prefix caching is on: send a `session_id`, and send each earlier assistant turn back with its `reasoning_content`; later turns then only prefill what's new (a 4k-token conversation's second turn: ~38 s without the cache, 2-5 s with it). opencode does both on its own: point an `@ai-sdk/openai-compatible` provider at the server and its second turn (14k-token system prompt) measured 9 s instead of 170 s. One session id may carry several conversations (opencode's title request shares it); each keeps its own cache slot. **Not good for:** pasting long documents or codebases every request; prefill is the K80's weak spot. Options are in each script's header. ## Serving One model, no GPU required: ```bash ./pantheon serve --model models/SmolLM2-135M-Instruct-Q8_0.gguf --port 8080 ``` That's a complete OpenAI-compatible server. Point any client at `http://127.0.0.1:8080/v1` and it works. Everything below is optional. `--name` defaults to the file name, and `--dies` is CUDA-only — omit it and the engine places the model itself. Repeat `--model` to serve several from one process: ```bash ./pantheon serve \ --model models/big.gguf --name chat --ctx-cap 32768 \ --model models/small.gguf --name draft \ --port 8080 ``` Clients choose with the `model` field in a normal OpenAI request body. On a multi-GPU box you can pin placement, and repeating a `--name` on another device makes it a **replica** that load-balances least-busy: ```bash ./pantheon serve \ --model models/big.gguf --name chat --dies 0,1 \ --model models/small.gguf --name worker --dies 2 \ --model models/small.gguf --name worker --dies 3 \ --port 8080 ``` Two `worker` requests then occupy devices 2 and 3 concurrently, with nothing in front of the process doing the routing. ### Bringing up more than a couple of models On a constrained host, launching every model at once is how you OOM. The pattern that works is to start `serve` with one model and add the rest through [`POST /admin/models/load`](docs/reference/http-api.md) **one at a time**, each gated on free VRAM *and* host-RAM headroom, then settle-waited — visible in `/v1/models` — before starting the next. Bring-up becomes idempotent and restartable: after a failure you re-run and it picks up where it stopped, keeping what already loaded. [`examples/deploy.sh`](examples/deploy.sh) is a working template that does exactly this, driven by a manifest ([`examples/models.conf`](examples/models.conf)): ``` chat | /models/chat.gguf | ctx_cap=32768 max_sessions=4 max_concurrent=2 draft | /models/draft.gguf | ``` ```bash ./examples/deploy.sh --plan # dry run — print the plan, change nothing ./examples/deploy.sh # reconcile (idempotent) ./examples/deploy.sh --prune # also unload anything not in the manifest ``` It needs **bash and curl, nothing else** — no jq, no Python, no `nvidia-smi`. GPU state comes from the engine's own `/admin/dies`, so the same script works unchanged on CPU, Metal and CUDA. Copy it and adapt the gates; every per-model option the `serve` command line accepts can be set per manifest line. | | | |---|---| | **Endpoints** | `/v1/chat/completions` (SSE), `/v1/models`, `/admin/models/*`, `/admin/dies` | | **Per-model caps** | `--ctx-cap`, `--max-sessions`, `--max-concurrent`, `--reasoning-budget` | | **Scheduling** | batch scheduler with a bounded queue, then an honest `429` + `Retry-After` | | **Caching** | prefix cache with longest-common-prefix reuse across turns | | **Structured output** | grammar-constrained decoding for tool calls and `response_format` | | **Ops** | load/unload without restart; `/v1/models` stays live | A `pantheon router` subcommand fans across separate serve processes. ## Supported models | Family | Covers | |---|---| | **Llama** | Llama, Qwen2, Mistral, Mixtral — one shared forward pass | | **Phi-3** | fused QKV, NeoX RoPE | | **Qwen3.5 / 3.6 MoE** | hybrid gated-delta SSM + attention, grouped experts, 2-die split | | **RWKV-X** | hybrid recurrent + sparse attention, WKV-7 kernel | | **Pixtral** | vision-language, continuous-batch decoder for bulk OCR | **Formats:** GGUF (v2/v3, K-quants and legacy) and safetensors, mmap'd zero-copy. Per-model quants, measured throughput and a runnable command: [`docs/reference/supported-models.md`](docs/reference/supported-models.md). ## Backends | Backend | Flag | Notes | |---|---|---| | CPU | *(default)* | NEON, AVX2, scalar | | BLAS | `USE_BLAS=1` | Accelerate on macOS, OpenBLAS on Linux | | Threads | `USE_THREADS=1` | pthread pool | | Metal | `USE_METAL=1` | Apple Silicon | | CUDA | `USE_CUDA=1` | `sm_37`+, `dlopen`ed with kernels embedded as PTX — builds without nvcc. **Supported on Tesla K80: Qwen3.6-35B-A3B and Nex-N2-mini** | | Tenstorrent | `USE_TT=1` | Blackhole p150a, Linux only; programs embedded, no TT software stack needed to build. **Supported model: Ornith 1.5 9B only** — other models may load on the card but are not supported | Because CUDA is `dlopen`ed, one binary runs on a machine with no NVIDIA driver at all and lights up the GPU path on a machine that has one. ```bash make USE_BLAS=1 pantheon # CLI make USE_BLAS=1 libpantheon.a # static library, to embed make USE_BLAS=1 test # test suites make DEBUG=1 USE_BLAS=1 pantheon ``` ### The CLI binary is named for its backend Each accelerator backend writes its **own** file, and `pantheon` is a symlink to whichever was built last: | Build | Binary | `pantheon` symlink | |---|---|---| | `make` | `pantheon-cpu` | → `pantheon-cpu` | | `make USE_CUDA=1` | `pantheon-cuda` | → `pantheon-cuda` | | `make USE_TT=1` | `pantheon-tt` | → `pantheon-tt` | | `make USE_METAL=1` | `pantheon-metal` | → `pantheon-metal` | They used to be one file, so the last build silently owned the deployed path — which is how a CPU-only binary came to serve a 35B MoE with every GPU die idle. A build for one backend can no longer overwrite another's binary; at worst it re-points the symlink, and `ln -sfn pantheon-cuda pantheon` puts that back with no rebuild. Ask a binary what it is rather than inferring it from its path: ```bash ./pantheon --backend # -> cuda|tt|metal|cpu (one token, for scripts) ./pantheon --version # -> Pantheon v0.4.0 / backend=cuda build= curl -s localhost:8080/ | jq .backend # a running serve scripts/assert_backend.sh ./pantheon cuda # deploy-time assertion; exit 1 on mismatch ``` `pantheon serve` prints `backend=... build=... accelerator=...` as its first line and the visible die count as its second, and it **refuses to start** when `--dies` names dies this binary has no backend for. To serve on the CPU deliberately, simply do not pass `--dies`. ## Embedding Pantheon is a library first and a CLI second. The public API is [`include/pantheon.h`](include/pantheon.h) — opaque types, explicit ownership, error codes rather than exceptions, no global state. Link `libpantheon.a` from C or any language with a C FFI. ## What Pantheon is not Being clear about this saves everyone time: - **It is not built for speed on modern datacenter GPUs.** Engines designed for that hardware will be faster there. - **Prefix caching, structured output, n-gram speculative decoding and continuous batching are table stakes**, not differentiators. Everyone has them. They are here because an engine without them isn't serious, not because they're novel. - **It is not a distributed serving framework.** One process is self-routing and bounded; you scale by adding servers, not by packing more into one. - **It is one person's project.** Model coverage is narrower than that of larger projects. ## Documentation [`docs/`](docs/) — start at [`docs/README.md`](docs/README.md). - [Quickstart](docs/guides/quickstart.md) · [Chat templates](docs/guides/chat.md) · [.pth converter](docs/guides/pth-convert.md) - [Environment flags](docs/reference/env-flags.md) · [CLI flags](docs/reference/cli.md) · [HTTP API](docs/reference/http-api.md) · [Supported models](docs/reference/supported-models.md) - Architecture: [engine](docs/architecture/engine.md) · [serve layer](docs/architecture/serve.md) · [memory-aware layer](docs/architecture/memory-aware.md) · [die manager](docs/architecture/accel-foundry.md) · [GGUF](docs/architecture/gguf.md) · [Llama forward](docs/architecture/llama-forward.md) · [Qwen3.5-MoE](docs/architecture/models-qwen35moe.md) · [RWKV-X](docs/architecture/rwkvx.md) ## Built on Foundry Pantheon uses **Foundry** for tensor compute, safetensors, tokenization, quantization, attention primitives and the CUDA layer. Same author, same license. Foundry is a git submodule at `foundry/` ([`https://git.teamide.dev/David/Foundry.git`](https://git.teamide.dev/David/Foundry.git)). The Makefile resolves it as `FOUNDRY=/path`, then `./foundry` (the submodule, or a symlink), then `../foundry` (a sibling checkout); if none exists it stops with a message saying so rather than a wall of missing headers. ## License **AGPL-3.0** — see [`LICENSE`](LICENSE). Contributions come in under the CLA in [`CLA.md`](CLA.md), with a DCO sign-off on every commit — see [`CONTRIBUTING.md`](CONTRIBUTING.md). You keep your copyright; signing is one entry in [`.cla/signatures.md`](.cla/signatures.md), added in your first pull request. Copyright © 2026 David (Corey) Herrera and the Pantheon contributors. See [`NOTICE`](NOTICE).