Working reference for how this repo is structured, built up while
investigating video-input support. Not official docs (see docs/ for
those) and not a plan — a durable map of how the pieces fit together, so a
future session (human or Claude) doesn't have to re-derive it from scratch.
Update this file as understanding improves; it will drift out of date
otherwise.
Ollama serves models through exactly two backends, chosen per-model based on
the manifest's layer format (Model.IsMLX(), server/images.go: true when
Config.ModelFormat == "safetensors").
This is the backend for the vast majority of models (Gemma, Qwen*-VL,
Llama, Mistral, etc. in their GGUF form). There is no Go-native CGO model
implementation anymore — it was removed in commit 9db4bdbad6
("runner: Remove CGO engines, use llama-server exclusively for GGML
models", #16031). Before that commit, Ollama had its own Go model/runner
code under model/models/* and runner/ollamarunner/; those packages no
longer exist. Any PR or reference material written against that
architecture (e.g. abandoned upstream PR ollama#12962) targets a
subsystem that is gone and cannot be merged as-is.
Today, GGUF inference works by Ollama spawning a real llama-server
subprocess (llama.cpp's own HTTP server binary) and talking to it over
HTTP — /completion and /v1/chat/completions. Ollama's job is: convert
models to GGUF (convert/), manage the manifest/capabilities
(server/images.go), launch and supervise the subprocess with the right
flags (llm/llama_server.go, server/sched.go), and translate between
Ollama's own API shapes and llama-server's wire format.
A second, independent, Go-native inference path for models kept in
safetensors format, under x/models/* (model implementations) and
x/mlxrunner/ (the runner/server harness, analogous in role to
llm/llama_server.go but talking to Ollama's own in-process MLX-backed Go
code rather than a subprocess). This is a real, actively developed path —
it's not legacy or secondary. Some model families exist on both backends
(e.g. Gemma4 has both a GGUF converter and an x/models/gemma4/ MLX
implementation); others are MLX-only (e.g. Qwen3.8/qwen4_exp — no GGUF
converter exists for it at all, and its architecture has components with no
equivalent in llama.cpp — see "Model-specific findings" below).
When investigating "how does X work for model Y", always check which
backend Y actually uses first — the two paths share some upstream
plumbing (api.Message, llm.MediaData/MediaKind, server/prompt.go's
[img-N] tagging) but diverge completely below that point. Mixing up which
backend a model uses is the single easiest way to investigate the wrong
code path.
This is a common point of confusion, worth getting right:
llama/vendor/is not committed source. It's the on-disk name CMake uses as a fetch destination; there's no git history for it because nothing under it is tracked (seellama/.gitignore). If yougrepunderllama/vendor/expecting to find llama.cpp source, it will only be there if someone has already run a configure step in this working directory — and if it's there, it reflects whatever was fetched at that time, which can silently be stale relative to the currentLLAMA_CPP_VERSIONfile if nobody has reconfigured since the pin last changed. Always checkLLAMA_CPP_VERSION's current value, and when in doubt, do a fresh configure rather than trust an existing checkout.- The actual fetch:
LLAMA_CPP_VERSION(single line, repo root) is read byllama/server/CMakeLists.txtand passed asGIT_TAGto a CMakeFetchContent_Declare(llama_cpp GIT_REPOSITORY https://github.com/ggml-org/llama.cpp.git GIT_TAG ...). Runningcmake -S llama/server --preset cpu(or another backend preset) clones the pinned ref intobuild/<preset-name>/_deps/llama_cpp-src. Configuring is a real network operation (full clone, though shallow) — budget a few minutes. - Ollama patches llama.cpp before building it, via
llama/compat/— seellama/compat/compat.cmakeandllama/compat/llama-ollama-compat.cpp(a large C++ shim, not just a patch file) plus.patchfiles. This handles things like: translating Ollama's own GGUF tensor-naming convention into what llama.cpp's loader expects for models where Ollama's converter and llama.cpp's expected format diverge (detect_ollama_gemma4 ()/handle_gemma4_clip()are examples — real, production code, not stubs), RPC changes, DRM-based VRAM detection, etc. - Ollama also overrides several of llama.cpp's own CMake options,
forcing some on and some off regardless of llama.cpp's own defaults — see
llama/server/CMakeLists.txt:164-185. This is where the video-support investigation hit a real wall (see below): llama.cpp's ownLLAMA_SUBPROCESSoption defaults to ON on Linux/macOS, but Ollama forces it OFF with an explicit comment explaining a real portability bug (glibc symbolposix_spawn_file_actions_addchdir_npmissing before glibc 2.29, breaking Ollama's AlmaLinux 8 (glibc 2.28) build images) — and a second override,MTMD_VIDEO OFF, exists specifically because mtmd's video code depends on the same subprocess header. This means bumpingLLAMA_CPP_VERSIONalone never enables llama.cpp's native video support in Ollama's build, no matter how new the pinned version is, until this override is revisited. llama/README.mddocuments the supported workflow for bumping the pin: configure, verify the fetched ref, diff the upstream changes, rebuild, run Go tests, and run integration tests on real hardware. It's a real, moderately involved process (model loading, GPU discovery, scheduler inputs, and streaming behavior can all be affected by a version bump), not a one-line edit — treat it accordingly.
For the GGML/llama-server path specifically (MLX has its own parallel but
structurally similar pipeline under x/mlxrunner/media.go +
per-model PrepareMedia/EncodeMedia):
api.Message.Images []ImageData(api/types.go) is a generic media-bytes carrier, not image-specific despite the name — audio already piggybacks on it (sniffed by content, not a separate field).server/prompt.go'simageTaggedMessages()is the single conversion point from "raw bytes on a message" to "[img-N]markers in the rendered prompt + a parallel[]llm.MediaDataslice." Both chat-prompt-building code paths inserver/routes.goconverge here.llm/media.go'sDetectMediaKind()sniffs each blob's kind (currentlyMediaKindImage/MediaKindAudio/MediaKindUnknown— no video kind yet) via magic bytes /http.DetectContentType.llm/llama_server.gosends the tagged prompt + media to thellama-serversubprocess, either via/completion(Ollama's own[img-N]markers get swapped for llama-server's internal marker, media sent as base64 in aMultimodalDataarray) or/v1/chat/completions(OpenAI-styleimage_url/input_audiocontent parts).- llama-server's mtmd library (
llama/vendor/tools/mtmd/once fetched) does the actual vision/audio encoding — CLIP-style vision towers per architecture (e.g.models/gemma4v.cppfor Gemma4, with its ownPROJECTOR_TYPE_GEMMA4V), auto-wired via--mmprojwhenllm/llama_server.go'scompatClipArchesallowlist includes the model's architecture.
Capability advertisement (model.CapabilityVision/CapabilityAudio) is
purely derived from GGUF metadata presence (vision.block_count /
audio.block_count KV keys, server/images.go ggufCapabilities()) — not
manually maintained per model family.
Findings from investigating how to add this, current as of this session:
- Video is unimplemented anywhere in Ollama's own Go code — no
MediaKindVideo, no video-aware renderer logic (thoughmodel/renderers/qwen35.go/qwen3vl.gohave literal// TODO: support videosmarkers at the point images are tagged). - llama.cpp itself gained real native video support upstream: PR
ggml-org/llama.cpp#24269 ("xsn/mtmd-helper-video-input") merged June 8,
2026 (commit
8f83d6c271d1...) intomaster, and is present in thev0.4.1release tag (verified by actually fetching it locally). It adds:mtmd_helper_video_init*/mtmd_bitmap_init_lazy— mtmd decodes video itself (via ffmpeg as a subprocess, not a linked codec library) and lazily expands one input marker into multiple frame chunks during tokenization. Frames still go through the ordinary per-image bitmap/CLIP path — no new temporal position-encoding was added to any vision transformer graph (clip-graph.h:48still has// TODO [QWEN_VIDEO]: improve this in the future).- Server-side: a real
"input_video"OpenAI-compat content-part type (tools/server/server-common.cpp), gated byopt.allow_video, with per-model capability exposed as"video": meta.has_inp_video. - CLI:
mtmd-cli.cpphas a/video <path>command.
- But it's compiled out in Ollama's build, specifically and
deliberately, via the
LLAMA_SUBPROCESS/MTMD_VIDEOCMake overrides described above (llama/server/CMakeLists.txt:179-185). Verified locally: bumpingLLAMA_CPP_VERSIONtov0.4.1and reconfiguring still showsMTMD_VIDEO:BOOL=OFFinCMakeCache.txt, becauseLLAMA_SUBPROCESSis force-off regardless of the pinned version. - Open question, not yet resolved: how to get real video support while
keeping the AlmaLinux 8 (glibc 2.28) build target working. Candidate
directions, none evaluated in depth yet:
- Check whether a newer llama.cpp version fixed the missing glibc
version-guard around
posix_spawn_file_actions_addchdir_npinsubprocess.h— if so,LLAMA_SUBPROCESSmight be safely re-enabled without breaking the AlmaLinux 8 build. Needs checking against the actual current upstream source, not assumed. - Whether AlmaLinux 8 support is still a hard requirement, or could be relaxed for a build variant — a product/release decision, not a technical one.
- Whether
MTMD_VIDEOcould be decoupled from genericLLAMA_SUBPROCESS/router-mode machinery via anllama/compat/patch, e.g. making mtmd's video helper spawn ffmpeg through a narrower path that doesn't trip the glibc issue, rather than the general-purpose subprocess header. Not scoped. - Fallback: decompose video into frames on Ollama's Go side instead
(before the request reaches llama-server), reusing the existing
per-image
[img-N]pipeline unchanged, extracting frames via a runtimeexec.LookPath("ffmpeg")shell-out (no CGO, no vendored ffmpeg build). This avoids the glibc/subprocess issue entirely since it never touches llama.cpp's C++ subprocess code, at the cost of duplicating (in Go) something llama.cpp now does natively in C++. A full design for this was drafted in an earlier planning pass this session; treat it as a known-viable fallback, not the preferred direction, now that native upstream support is confirmed to exist.
- Check whether a newer llama.cpp version fixed the missing glibc
version-guard around
- Gemma4: has a complete, working GGUF vision pipeline already (see pipeline section above) — no new C++ work needed to serve Gemma4 images today. Good target for follow-on multimodal work generally.
- Qwen3.8 /
qwen4_exp: MLX-only, no GGUF converter exists. Its architecture has three components with no equivalent anywhere in llama.cpp: "hyper-connections" (a learned multi-stream residual mixing mechanism, replacing the usual single residual stream — would require pervasive changes to llama.cpp's graph-construction scaffolding, not a single new op), an "Engram" hashed n-gram embedding cache (token-ID XOR/hash → sharded embedding table, persisted across forward calls — no analog to any existing llama.cpp cache type), and "QSA" sparse attention (structurally similar to DeepSeek's sparse-attention indexer, which llama.cpp has tensor-loading plumbing for but never wired into an actual compute graph —glm-dsa.cppinheritsdeepseek2.cpp's dense-only graph builder). Porting this would be genuine multi-week new C++ graph-construction work in llama.cpp, not a converter-writing exercise. Ruled out as a target for this reason. - Ornith 9B: no dedicated model code at all — purely a renderer/parser
wrapper (
model/renderers/ornith.go) around Qwen3.5's existing renderer and parser. Rides on whatever backend Qwen3.5 uses (both GGUF and MLX exist for that family).
LLAMA_CPP_VERSIONwas changed fromb10740tov0.4.1in the working tree (uncommitted) during this investigation, to verify the video API's presence. This is a plausible independent improvement (newer llama.cpp) but was not requested or confirmed as a standalone change — don't assume it should be committed without checking with the user and running the fullllama/README.mdvalidation workflow first (a version bump can affect model loading, GPU discovery, scheduler inputs, and streaming behavior well beyond video).- A
build/llama-server-cpu/directory exists locally from a configure step done during this investigation (gitignored, not committed, safe to delete/reconfigure).