This file provides guidance to coding agents (Claude Code, OpenCode) when working with code in this repository.
A PowerShell wrapper around upstream llama.cpp, pinned as a submodule at vendor/llama.cpp/. No original C/C++ lives here — only .ps1 scripts driving CMake + MSVC + Conda. The session shell is bash on Windows; the project's own scripts must run from pwsh/powershell.
./rebuild_llama.cpp.ps1 # auto-detects CUDA vs OpenBLAS
./rebuild_llama.cpp.ps1 -version "b1138" # pin a tag / commit
./rebuild_llama.cpp.ps1 -pullRequest "18675" # build a PR
./rebuild_llama.cpp.ps1 -target "llama-server" # CMake target subset
./rebuild_llama.cpp.ps1 -blasAccelerator OFF # OpenBLAS | CUDA | OFF
./examples/server.ps1 -model ".\vendor\llama.cpp\models\<x>.gguf"
Get-Help -Detailed ./examples/server.ps1 # full option listBinaries land in ./vendor/llama.cpp/build/bin/Release/. Conda env llama.cpp (Python 3.12) must already exist — the scripts call conda activate llama.cpp themselves.
No tests, no linter. Verify changes by running an example script against a real GGUF model.
- The submodule always shows dirty.
rebuild_llama.cpp.ps1prepends an OpenBLAS linking shim tovendor/llama.cpp/CMakeLists.txt(idempotent; workaround forfind_package(BLAS)failing on Windows)..gitmodulessetsignore = dirtyfor this reason — don't "clean it up." - Each build wipes the submodule back to
origin/masterthen checks out the requested-version/ PR. Any local edits undervendor/llama.cpp/are lost by design. The reset/--remotestep is scoped tovendor/llama.cpponly — other submodules (e.g.vendor/Qwen-Fixed-Chat-Templates, default branchmain) stay at the SHA pinned in the superproject and are never advanced by the build script. To bump them, do it manually:git -C vendor/Qwen-Fixed-Chat-Templates fetch && git -C vendor/Qwen-Fixed-Chat-Templates checkout <sha> && git add vendor/Qwen-Fixed-Chat-Templates && git commit. Once the pin is committed, the nextrebuild_llama.cpp.ps1mirrors it into the working tree (auto-discovered from.gitmodules,--force); hand-edits inside the submodule do not survive a rebuild. ml64.exe(MASM) must be passed as-DCMAKE_ASM_COMPILER. Upstreamggml/CMakeLists.txtsetscmake_policy(SET CMP0194 NEW)and declaresproject(... ASM); on CMake 4.1+ with the VS generator this rejectscl.exeas the ASM compiler. The script locatesml64.exeviavswhere.exe. Don't remove. Thevswherecall passes-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64alongside-latest— this is deliberate, not redundant:-latestalone returns the newest-installed instance by timestamp, which on a machine with multiple instances may be a Build Tools install lacking the C++ workload, so-findreturns nothing and the build throws "ml64.exe not found" even though another instance (e.g. Community) has it (#3).-requiresnarrows-latestto instances that actually carry the MSVC x64 toolset, matching the pattern upstream uses in.github/workflows/build-cpu.yml. Don't drop the-requiresfilter.- CUDA is selected iff both
nvidia-smiandnvccare on PATH. Missing either silently falls back to OpenBLAS. - CUDA builds pass
-DGGML_CUDA_FA_ALL_QUANTS=ON. Without it the CUDA flash-attention path compiles only four symmetric KV kernels —f16/f16,q4_0/q4_0,q8_0/q8_0,bf16/bf16(vendor/llama.cpp/ggml/src/ggml-cuda/CMakeLists.txt:119-124) — and the dispatcher returnsBEST_FATTN_KERNEL_NONE->GGML_ABORTfor any other K type or any mismatched K/V pair (ggml-cuda/fattn.cu:424-446). The flag pulls in the fullfattn-vec*.cuset so asymmetric / q5 / q4_1 caches work (the presets useq5_0K +q4_1V). Costs extranvcccompile time. - Build parallelism is SMT-aware.
cmake --build --parallelis fed a count derived fromWin32_Processor. Upstream'sUseMultiToolTask=true+EnforceProcessCountAcrossBuilds=true(vendor/llama.cpp/CMakeLists.txt:92-93) makes this the single cap on concurrentcl.exe/nvcc— no per-project/MPmultiplication. On SMT CPUs it uses physical cores (Sum(NumberOfCores)): dropping the logical siblings avoids starving the scheduler / ~doubling peaknvccRAM (no throughput gain) and leaves them free so the machine stays usable. On non-SMT CPUs where physical == logical (hybrid Arrow/Lunar Lake; e.g. Core Ultra 9 285HX = 8P+16E, 24 threads) using all cores would peg the box at 100%, so it backs off to 80% of physical (floor(cores * 0.8)= 19 on the 285HX) to keep the machine usable during builds. Override with-parallelJobs N. requirements_override.txtlayers on top of upstreamvendor/llama.cpp/requirements.txt. It pinstorchto a CUDA 12.6 wheel, addstiktoken(missing upstream, required for GLM), pinstransformers==5.3.0, and narrowsnumpyto resolve anopencv-python-headlessconflict. When bumping any of these, verify both constraints still hold.server.ps1reads GGUF metadata by shelling out tovendor/llama.cpp/gguf-py/gguf/scripts/gguf_dump.py. Upstream has moved this path before (CHANGELOG 1.24.0) — if server startup fails with "Failed to extract model details", check the path first.server.ps1 -additionalArgumentssplits on whitespace and re-pairs tokens into key/value flags. Values that contain spaces will not survive this parser.speed-bench.ps1drives a router-mode server, not a single model — it shells out to the vendoredvendor/llama.cpp/tools/server/bench/speed-bench/speed_bench.py(wiped/refreshed each rebuild, so it tracks the built binary) and sweeps the-modelspreset ids in order, pre-warming each via the router-only/models/loadendpoint and lazy-swapping through--models-max 1. Comparison anchors on the first id; models that fail to load are excluded, not fatal. Needs thedatasetspackage (deliberately not in the main requirements) plus network access for thenvidia/SPEED-Benchdataset. The router-only/v1/modelsand/models/loadendpoints mean it does not work against a plain single-model server. If startup fails reading the script after a rebuild, check whether upstream movedtools/server/bench/speed-bench/(same failure mode as thegguf_dump.pynote above).- Rebuild aborts on running build-tree processes. Before any destructive op,
rebuild_llama.cpp.ps1checksGet-Processfor any EXE undervendor/llama.cpp/build/and throws with the PID list. Catches the forgot-to-stop-llama-server.execase.
VRAM-tier presets: presets/models_16GB_VRAM.ini, presets/models_24GB_VRAM.ini.
See presets/README.md for the user-facing quick-start; notes below are for editing.
-
mmproj-offload = truefails silently at startup on a saturated GPU. CLIP's warmup compute buffer OOMs but the server keeps running — only image requests error at generation time. Setfalseon tiers where LLM + KV already saturate VRAM. -
All Qwen 3.6 entries pin
chat-template-file = vendor\Qwen-Fixed-Chat-Templates\chat_template.jinja. Required, not redundant withjinja = true—chat-template-filereplaces the GGUF-embedded template entirely (vendor/llama.cpp/common/arg.cpp:3142,params.chat_template = read_file(value)). The upstream embedded template has documented issues with tool calls, role handling,<think>block rendering, agentic loops, and llama.cpp KV-prefix cache stability; the vendored template fixes all of them (full list invendor/Qwen-Fixed-Chat-Templates/README.md). Since v19 the template is a single unified file covering both Qwen 3.5 and 3.6 variants (the oldqwen3.5/andqwen3.6/subdirectories now live underarchive/). The template adds a<|think_on|>/<|think_off|>toggle, and v19 defaultspreserve_thinkingtotrue(past<think>blocks are kept chronologically for 100% KV prefix cache stability and agentic reasoning continuity). To strip past<think>blocks instead, setchat-template-kwargs = {"preserve_thinking":false}— at the cost of a lower KV cache hit rate. Path is repo-relative, sollama-servermust be launched from the repo root —read_file()resolves against the process CWD, not the INI file's directory.Qwen3-Coder-Nextentries deliberately keep their GGUF-embedded template; froggeric's README only claims compatibility for Qwen 3.5 / 3.6 variants. -
All gemma-4 entries pin
chat-template-file = vendor\llama.cpp\models\templates\google-gemma-4-31B-it.jinja. This is Google's fixed official template as aligned by upstream (#21704) — the exact file upstream'stests/test-chat.cpplocks against the native gemma4 chat handler (vendor/llama.cpp/common/chat.cpp:1216), so parser and template always come from the same submodule commit (each rebuild resets the submodule to master, mirroring the built binary). GGUF-embedded templates from conversions predating Google's template fixes lack the{#- OpenAI Chat Completions:marker; llama.cpp then logs "detected an outdated gemma4 chat template" and rewrites messages via C++ compatibility workarounds (common/chat.cpp:2250-2258) — the pin avoids that path. One file covers the whole series (12B / 26B-A4B / 31B, incl.<|image|>/<|audio|>placeholders), andreasoning = onmaps to the template'senable_thinkingkwarg (common/arg.cpp:3167-3175), so nochat-template-kwargsare needed. Unlike the Qwen template, past<|channel>thoughtblocks are stripped from history by design — Gemma 4 is trained that way — so cross-turn KV-prefix invalidation is inherent (ctx-checkpointsmitigates); do not add a preserve-thinking hack. If startup fails reading the template after a rebuild, check whether upstream movedmodels/templates/(same failure mode as thegguf_dump.pynote above).
ngram-mod speculative decoding (--spec-type ngram-mod): model-agnostic, works on any model.
- All models:
spec-ngram-mod-n-match = 24,spec-ngram-mod-n-min = 48,spec-ngram-mod-n-max = 64(matches the struct defaults incommon/common.h:329-337and what--spec-defaultproduces atcommon/arg.cpp:4065-4074; ggerganov confirmed post-merge in PR #19164 that the min/max "likely don't need to be changed from the recommended values"; MoEs require long drafts and dense models tolerate them without noticeable cost). Flags were renamed from--draft-min/--draft-max/--spec-ngram-size-nin upstream PR #22397; the old names now error at startup. n_match < 16logs a "too small — poor quality is possible" warning atvendor/llama.cpp/common/speculative.cpp:1031-1034; parser accepts1..1024(common/arg.cpp:3606-3615), so 16 is the lowest non-warning value, not a hard floor. Min/max parsers accept0..1024(common/arg.cpp:3587-3605).- Memory overhead: ~16 MiB total, shared across all server slots
(single
common_ngram_modinstance allocated atcommon/speculative.cpp:1026). - Pool auto-resets on
begin()if occupancy > 25 %, and after 3 consecutive rounds with acceptance < 50 % (common/speculative.cpp:720-728,:790-806). Smallern_matchmakes these resets fire more often and wipes ngrams learned from the current prompt — another reason to stay atn_match ≥ 24.
- One bullet = one physical line. Never insert manual line-breaks; let the editor soft-wrap.
- Format:
- [Component] <verb> <thing>(Added / Changed / Fixed / Removed). - No rationale, no file paths, no line numbers, no explanatory prose. Rationale lives in AGENTS.md "Non-obvious behavior" or in the commit message.
- PR refs as bare
#NNNNN, at most once per release. - Canonical examples: [1.21.0] – [1.27.0] in CHANGELOG.md.
Non-committed agent artifacts (diffs, trace outputs, generated reports, experimental scripts) go under .tmp/sessions/<session-id>/ at the repo root; .tmp/ is gitignored. <session-id> is SESSION_ID when the platform injects it, otherwise a minted YYYYMMDD-HHMMSS-<random6>. Never write scratch files to .claude/, the repo root, or vendor/.